From 4745eaa275e26b183573ecab63dde0d6d3eac8ea Mon Sep 17 00:00:00 2001 From: Sneaken <37062838+Sneaken@users.noreply.github.com> Date: Tue, 10 Jan 2023 20:01:21 +0800 Subject: [PATCH 01/30] fix: add missing types in TS project when global is true (#2631) --- examples/react-mui/tsconfig.json | 3 ++- examples/react-storybook/tsconfig.json | 3 ++- examples/react-testing-lib-msw/src/App.test.tsx | 1 - examples/react-testing-lib-msw/tsconfig.json | 3 ++- examples/react-testing-lib/tsconfig.json | 3 ++- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/react-mui/tsconfig.json b/examples/react-mui/tsconfig.json index de8ea24d7a02..c211c84456f0 100644 --- a/examples/react-mui/tsconfig.json +++ b/examples/react-mui/tsconfig.json @@ -15,6 +15,7 @@ "strict": true, "target": "ESNext", "useDefineForClassFields": true, - "useUnknownInCatchVariables": false + "useUnknownInCatchVariables": false, + "types": ["vitest/globals"] } } diff --git a/examples/react-storybook/tsconfig.json b/examples/react-storybook/tsconfig.json index 69ae1b6b8d87..8b48e8dfee78 100644 --- a/examples/react-storybook/tsconfig.json +++ b/examples/react-storybook/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "types": ["vitest/globals"] }, "include": ["./src", "./.storybook"] } diff --git a/examples/react-testing-lib-msw/src/App.test.tsx b/examples/react-testing-lib-msw/src/App.test.tsx index c31a5a95f946..4463aa6bae93 100644 --- a/examples/react-testing-lib-msw/src/App.test.tsx +++ b/examples/react-testing-lib-msw/src/App.test.tsx @@ -1,4 +1,3 @@ -import { expect, it } from 'vitest' import { ApolloProvider } from '@apollo/client' import { client } from './ApolloClient' import { render, screen, userEvent, waitForElementToBeRemoved } from './utils/test-utils' diff --git a/examples/react-testing-lib-msw/tsconfig.json b/examples/react-testing-lib-msw/tsconfig.json index 9f83659970ba..445d0b656554 100644 --- a/examples/react-testing-lib-msw/tsconfig.json +++ b/examples/react-testing-lib-msw/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "types": ["vitest/globals"] }, "include": ["./src"] } diff --git a/examples/react-testing-lib/tsconfig.json b/examples/react-testing-lib/tsconfig.json index 9f83659970ba..445d0b656554 100644 --- a/examples/react-testing-lib/tsconfig.json +++ b/examples/react-testing-lib/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "types": ["vitest/globals"] }, "include": ["./src"] } From 83da2ec45077d54f885d94b712a37215e19f1148 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 10 Jan 2023 16:19:08 +0100 Subject: [PATCH 02/30] fix: always report failed test in junit reporter (#2632) --- packages/vitest/src/node/reporters/junit.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vitest/src/node/reporters/junit.ts b/packages/vitest/src/node/reporters/junit.ts index 79649db18b54..7fccb602281b 100644 --- a/packages/vitest/src/node/reporters/junit.ts +++ b/packages/vitest/src/node/reporters/junit.ts @@ -167,7 +167,8 @@ export class JUnitReporter implements Reporter { await this.logger.log('') if (task.result?.state === 'fail') { - const promises = task.result.errors?.map(async (error) => { + const errors = task.result.errors?.length ? task.result.errors : [task.result.error] + for (const error of errors) { await this.writeElement('failure', { message: error?.message, type: error?.name ?? error?.nameStr, @@ -177,8 +178,7 @@ export class JUnitReporter implements Reporter { await this.writeErrorDetails(error) }) - }) || [] - await Promise.all(promises) + } } }) } From 9b8afb204dac3d7e0ccf7e7b0226c985f2810aad Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 10 Jan 2023 16:40:43 +0100 Subject: [PATCH 03/30] docs: add CLI arguments to config docs (#2636) * docs: add CLI argu,ents to config docs * chore: cleanup * chore: cleanup * Update docs/guide/cli.md Co-authored-by: Anjorin Damilare Co-authored-by: Anjorin Damilare --- docs/config/index.md | 73 ++++++++++++++++++++++--- docs/guide/cli.md | 21 ++++++- packages/vitest/src/node/cli-wrapper.ts | 4 +- packages/vitest/src/node/cli.ts | 56 +++++++++---------- 4 files changed, 114 insertions(+), 40 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index d77d3c911219..c23bc8e47a06 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -203,6 +203,7 @@ Define custom aliases when running inside tests. They will be merged with aliase - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--globals`, `--globals=false` By default, `vitest` does not provide global APIs for explicitness. If you prefer to use the APIs globally like Jest, you can pass the `--globals` option to CLI or add `globals: true` in the config. @@ -249,6 +250,7 @@ export default defineConfig({ - **Type:** `'node' | 'jsdom' | 'happy-dom' | 'edge-runtime' | string` - **Default:** `'node'` +- **CLI:** `--environment=` The environment that will be used for testing. The default environment in Vitest is a Node.js environment. If you are building a web application, you can use @@ -296,7 +298,7 @@ test('use jsdom in this test file', () => { }) ``` -If you are running Vitest with [`--no-threads`](#threads) flag, your tests will be run in this order: `node`, `jsdom`, `happy-dom`, `edge-runtime`, `custom environments`. Meaning, that every test with the same environment is grouped together, but is still running sequentially. +If you are running Vitest with [`--threads=false`](#threads) flag, your tests will be run in this order: `node`, `jsdom`, `happy-dom`, `edge-runtime`, `custom environments`. Meaning, that every test with the same environment is grouped together, but is still running sequentially. Starting from 0.23.0, you can also define custom environment. When non-builtin environment is used, Vitest will try to load package `vitest-environment-${name}`. That package should export an object with the shape of `Environment`: @@ -329,6 +331,7 @@ These options are passed down to `setup` method of current [`environment`](#envi - **Type:** `boolean` - **Default:** `false` +- **CLI:** `-u`, `--update`, `--update=false` Update snapshot files. This will update all changed snapshots and delete obsolete ones. @@ -336,12 +339,14 @@ Update snapshot files. This will update all changed snapshots and delete obsolet - **Type:** `boolean` - **Default:** `true` +- **CLI:** `-w`, `--watch`, `--watch=false` Enable watch mode ### root - **Type:** `string` +- **CLI:** `-r `, `--root=` Project root @@ -349,6 +354,7 @@ Project root - **Type:** `Reporter | Reporter[]` - **Default:** `'default'` +- **CLI:** `--reporter=`, `--reporter= --reporter=` Custom reporters for output. Reporters can be [a Reporter instance](https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/types/reporter.ts) or a string to select built in reporters: @@ -364,7 +370,7 @@ Custom reporters for output. Reporters can be [a Reporter instance](https://gith - **Type:** `number` - **Default:** `stdout.columns || 80` -- **CLI:** `--outputTruncateLength `, `--output-truncate-length ` +- **CLI:** `--outputTruncateLength=`, `--output-truncate-length=` Truncate the size of diff line up to `stdout.columns` or `80` number of characters. You may wish to tune this, depending on your terminal window width. Vitest includes `+-` characters and spaces for this. For example, you might see this diff, if you set this to `6`: @@ -378,7 +384,7 @@ Truncate the size of diff line up to `stdout.columns` or `80` number of characte - **Type:** `number` - **Default:** `15` -- **CLI:** `--outputDiffLines `, `--output-diff-lines ` +- **CLI:** `--outputDiffLines=`, `--output-diff-lines=` Limit the number of single output diff lines up to `15`. Vitest counts all `+-` lines when determining when to stop. For example, you might see diff like this, if you set this property to `3`: @@ -397,7 +403,7 @@ Limit the number of single output diff lines up to `15`. Vitest counts all `+-` - **Type:** `number` - **Default:** `50` -- **CLI:** `--outputDiffMaxLines `, `--output-diff-max-lines ` +- **CLI:** `--outputDiffMaxLines=`, `--output-diff-max-lines=` - **Version:** Since Vitest 0.26.0 The maximum number of lines to display in diff window. Beware that if you have a large object with many small diffs, you might not see all of them at once. @@ -406,7 +412,7 @@ The maximum number of lines to display in diff window. Beware that if you have a - **Type:** `number` - **Default:** `10000` -- **CLI:** `--outputDiffMaxSize `, `--output-diff-max-size ` +- **CLI:** `--outputDiffMaxSize=`, `--output-diff-max-size=` - **Version:** Since Vitest 0.26.0 The maximum length of the stringified object before the diff happens. Vitest tries to stringify an object before doing a diff, but if the object is too large, it will reduce the depth of the object to fit within this limit. Because of this, if the object is too big or nested, you might not see the diff. @@ -416,16 +422,16 @@ Increasing this limit can increase the duration of diffing. ### outputFile - **Type:** `string | Record` +- **CLI:** `--outputFile=`, `--outputFile.json=./path` -Write test results to a file when the `--reporter=json` or `--reporter=junit` option is also specified. +Write test results to a file when the `--reporter=json`, `--reporter=html` or `--reporter=junit` option is also specified. By providing an object instead of a string you can define individual outputs when using multiple reporters. -To provide object via CLI command, use the following syntax: `--outputFile.json=./path --outputFile.junit=./other-path`. - ### threads - **Type:** `boolean` - **Default:** `true` +- **CLI:** `--threads`, `--threads=false` Enable multi-threading using [tinypool](https://github.com/tinylibs/tinypool) (a lightweight fork of [Piscina](https://github.com/piscinajs/piscina)) @@ -474,6 +480,7 @@ Default timeout to wait for close when Vitest shuts down, in milliseconds - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--silent`, `--silent=false` Silent console output from tests @@ -486,7 +493,7 @@ Path to setup files. They will be run before each test file. You can use `process.env.VITEST_POOL_ID` (integer-like string) inside to distinguish between threads (will always be `'1'`, if run with `threads: false`). :::tip -Note, that if you are running [`--no-threads`](#threads), this setup file will be run in the same global scope multiple times. Meaning, that you are accessing the same global object before each test, so make sure you are not doing the same thing more than you need. +Note, that if you are running [`--threads=false`](#threads), this setup file will be run in the same global scope multiple times. Meaning, that you are accessing the same global object before each test, so make sure you are not doing the same thing more than you need. ::: For example, you may rely on a global variable: @@ -556,6 +563,7 @@ Make sure that your files are not excluded by `watchExclude`. - **Type:** `boolean` - **Default:** `true` +- **CLI:** `--isolate`, `--isolate=false` Isolate environment for each test file. Does not work if you disable [`--threads`](#threads). @@ -563,10 +571,21 @@ Isolate environment for each test file. Does not work if you disable [`--threads You can use [`c8`](https://github.com/bcoe/c8) or [`istanbul`](https://istanbul.js.org/) for coverage collection. +You can provide coverage options to CLI with dot notation: + +```sh +npx vitest --coverage.enabled --coverage.provider=istanbul --coverage.all +``` + +::: warning +If you are using coverage options with dot notation, don't forget to specify `--coverage.enabled`. Do not provide a single `--coverage` option in that case. +::: + #### provider - **Type:** `'c8' | 'istanbul'` - **Default:** `'c8'` +- **CLI:** `--coverage.provider=` Use `provider` to select the tool for coverage collection. @@ -575,6 +594,7 @@ Use `provider` to select the tool for coverage collection. - **Type:** `boolean` - **Default:** `false` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.enabled`, `--coverage.enabled=false` Enables coverage collection. Can be overriden using `--coverage` CLI option. @@ -583,6 +603,7 @@ Enables coverage collection. Can be overriden using `--coverage` CLI option. - **Type:** `string[]` - **Default:** `['**']` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.include=`, `--coverage.include= --coverage.include=` List of files included in coverage as glob patterns @@ -591,6 +612,7 @@ List of files included in coverage as glob patterns - **Type:** `string | string[]` - **Default:** `['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte']` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.extension=`, `--coverage.extension= --coverage.extension=` #### exclude @@ -613,6 +635,7 @@ List of files included in coverage as glob patterns ] ``` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.exclude=`, `--coverage.exclude= --coverage.exclude=` List of files excluded from coverage as glob patterns. @@ -621,6 +644,7 @@ List of files excluded from coverage as glob patterns. - **Type:** `boolean` - **Default:** `false` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.all`, --coverage.all=false` Whether to include all files, including the untested ones into report. @@ -629,6 +653,7 @@ Whether to include all files, including the untested ones into report. - **Type:** `boolean` - **Default:** `true` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.clean`, `--coverage.clean=false` Clean coverage results before running tests @@ -637,6 +662,7 @@ Clean coverage results before running tests - **Type:** `boolean` - **Default:** `true` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.cleanOnRerun`, `--coverage.cleanOnRerun=false` Clean coverage report on watch rerun @@ -645,6 +671,7 @@ Clean coverage report on watch rerun - **Type:** `string` - **Default:** `'./coverage'` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.reportsDirectory=` Directory to write coverage report to. When using `c8` provider a temporary `/tmp` directory is created for [V8 coverage results](https://nodejs.org/api/cli.html#coverage-output). @@ -654,6 +681,7 @@ When using `c8` provider a temporary `/tmp` directory is created for [V8 coverag - **Type:** `string | string[]` - **Default:** `['text', 'html', 'clover', 'json']` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.reporter=`, `--coverage.reporter= --coverage.reporter=` Coverage reporters to use. See [istanbul documentation](https://istanbul.js.org/docs/advanced/alternative-reporters/) for detailed list of all reporters. @@ -663,6 +691,7 @@ Coverage reporters to use. See [istanbul documentation](https://istanbul.js.org/ - **Type:** `boolean` - **Default:** `false` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.skipFull`, `--coverage.skipFull=false` Do not show files with 100% statement, branch, and function coverage. @@ -671,6 +700,7 @@ Do not show files with 100% statement, branch, and function coverage. - **Type:** `boolean` - **Default:** `false` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.perFile`, `--coverage.perFile=false` Check thresholds per file. See `lines`, `functions`, `branches` and `statements` for the actual thresholds. @@ -679,6 +709,7 @@ See `lines`, `functions`, `branches` and `statements` for the actual thresholds. - **Type:** `number` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.lines=` Threshold for lines. See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-thresholds) for more information. @@ -687,6 +718,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol - **Type:** `number` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.functions=` Threshold for functions. See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-thresholds) for more information. @@ -695,6 +727,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol - **Type:** `number` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.branches=` Threshold for branches. See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-thresholds) for more information. @@ -703,6 +736,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol - **Type:** `number` - **Available for providers:** `'c8' | 'istanbul'` +- **CLI:** `--coverage.statements=` Threshold for statements. See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-thresholds) for more information. @@ -712,6 +746,7 @@ See [istanbul documentation](https://github.com/istanbuljs/nyc#coverage-threshol - **Type:** `boolean` - **Default:** `false` - **Available for providers:** `'c8'` +- **CLI:** `--coverage.allowExternal`, `--coverage.allowExternal=false` Allow files from outside of your cwd. @@ -720,6 +755,7 @@ Allow files from outside of your cwd. - **Type:** `boolean` - **Default:** `true` - **Available for providers:** `'c8'` +- **CLI:** `--coverage.excludeNodeModules`, `--coverage.excludeNodeModules=false` Exclude coverage under `/node_modules/`. @@ -728,6 +764,7 @@ Exclude coverage under `/node_modules/`. - **Type:** `string[]` - **Default:** `process.cwd()` - **Available for providers:** `'c8'` +- **CLI:** `--coverage.src=` Specifies the directories that are used when `--all` is enabled. @@ -736,6 +773,7 @@ Specifies the directories that are used when `--all` is enabled. - **Type:** `boolean` - **Default:** `false` - **Available for providers:** `'c8'` +- **CLI:** `--coverage.100`, `--coverage.100=false` Shortcut for `--check-coverage --lines 100 --functions 100 --branches 100 --statements 100`. @@ -744,6 +782,7 @@ Shortcut for `--check-coverage --lines 100 --functions 100 --branches 100 --stat - **Type:** `string[]` - **Default:** `[]` - **Available for providers:** `'istanbul'` +- **CLI:** `--coverage.ignoreClassMethods=` Set to array of class method names to ignore for coverage. See [istanbul documentation](https://github.com/istanbuljs/nyc#ignoring-methods) for more information. @@ -779,6 +818,7 @@ Watermarks for statements, lines, branches and functions. See [istanbul document ### testNamePattern - **Type** `string | RegExp` +- **CLI:** `-t `, `--testNamePattern=`, `--test-name-pattern=` Run tests with full names matching the pattern. If you add `OnlyRunThis` to this property, tests not containing the word `OnlyRunThis` in the test name will be skipped. @@ -801,6 +841,7 @@ test('doNotRun', () => { - **Type:** `boolean` - **Default:** `false` +- **CLI:** `--open`, `--open=false` Open Vitest UI (WIP) @@ -808,6 +849,7 @@ Open Vitest UI (WIP) - **Type:** `boolean | number` - **Default:** `false` +- **CLI:** `--api`, `--api.port`, `--api.host`, `--api.strictPort` Listen to port and serve API. When set to true, the default port is 51204 @@ -911,6 +953,7 @@ export default defineConfig({ - **Type**: `boolean` - **Default**: `false` +- **CLI:** `--allowOnly`, `--allowOnly=false` Allow tests and suites that are marked as only. @@ -918,6 +961,7 @@ Allow tests and suites that are marked as only. - **Type**: `boolean` - **Default**: `false` +- **CLI:** `--dangerouslyIgnoreUnhandledErrors` `--dangerouslyIgnoreUnhandledErrors=false` Ignore any unhandled errors that occur. @@ -925,6 +969,7 @@ Ignore any unhandled errors that occur. - **Type**: `boolean` - **Default**: `false` +- **CLI:** `--passWithNoTests`, `--passWithNoTests=false` Vitest will not fail, if no tests will be found. @@ -932,6 +977,7 @@ Vitest will not fail, if no tests will be found. - **Type**: `boolean` - **Default**: `false` +- **CLI:** `--logHeapUsage`, `--logHeapUsage=false` Show heap usage after each test. Useful for debugging memory leaks. @@ -1007,6 +1053,12 @@ Path to cache directory. Options for how tests should be sorted. +You can provide sequence options to CLI with dot notation: + +```sh +npx vitest --sequence.shuffle --sequence.seed=1000 +``` + #### sequence.sequencer - **Type**: `TestSequencerConstructor` @@ -1020,6 +1072,7 @@ Sharding is happening before sorting, and only if `--shard` option is provided. - **Type**: `boolean` - **Default**: `false` +- **CLI**: `--sequence.shuffle`, `--sequence.shuffle=false` If you want tests to run randomly, you can enable it with this option, or CLI argument [`--sequence.shuffle`](/guide/cli). @@ -1029,6 +1082,7 @@ Vitest usually uses cache to sort tests, so long running tests start earlier - t - **Type**: `number` - **Default**: `Date.now()` +- **CLI**: `--sequence.seed=1000` Sets the randomization seed, if tests are running in random order. @@ -1036,6 +1090,7 @@ Sets the randomization seed, if tests are running in random order. - **Type**: `'stack' | 'list' | 'parallel'` - **Default**: `'parallel'` +- **CLI**: `--sequence.hooks=` Changes the order in which hooks are executed. diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 63a0754cce08..9e06f9fa131e 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -16,7 +16,7 @@ You can pass an additional argument as the filter of the test files to run. For vitest foobar ``` -Will run only the test file that contains `foobar` in their paths. +Will run only the test file that contains `foobar` in their paths. This filter only checks inclusion and doesn't support regexp or glob patterns (unless your terminal processes it before Vitest receives the filter). ### `vitest run` @@ -83,6 +83,25 @@ vitest related /src/index.ts /src/hello-world.js | `--inspect-brk` | Enables Node.js inspector with break | | `-h, --help` | Display available CLI options | +::: tip +Vitest supports both camel case and kebab case for CLI arguments. For example, `--passWithNoTests` and `--pass-with-no-tests` will both work (`--no-color` and `--inspect-brk` are the exceptions). + +Vitest also supports different ways of specifying the value: `--reporter dot` and `--reporter=dot` are both valid. + +If option supports an array of values, you need to pass the option multiple times: + +``` +vitest --reporter=dot --reporter=default +``` + +Boolean options can be negated with `no-` prefix. Specifying the value as `false` also works: + +``` +vitest --no-api +vitest --api=false +``` +::: + ### changed - **Type**: `boolean | string` diff --git a/packages/vitest/src/node/cli-wrapper.ts b/packages/vitest/src/node/cli-wrapper.ts index bad0ef6faf04..c8ff746a093f 100644 --- a/packages/vitest/src/node/cli-wrapper.ts +++ b/packages/vitest/src/node/cli-wrapper.ts @@ -50,11 +50,11 @@ async function main() { } else { for (let i = 0; i < args.length; i++) { - if (args[i].startsWith('--segfault-retry=')) { + if (args[i].startsWith('--segfault-retry=') || args[i].startsWith('--segfaultRetry=')) { retries = +args[i].split('=')[1] break } - else if (args[i] === '--segfault-retry' && args[i + 1]?.match(/^\d+$/)) { + else if ((args[i] === '--segfault-retry' || args[i] === '--segfaultRetry') && args[i + 1]?.match(/^\d+$/)) { retries = +args[i + 1] break } diff --git a/packages/vitest/src/node/cli.ts b/packages/vitest/src/node/cli.ts index 54b0ca3d4204..d2a03f80c52c 100644 --- a/packages/vitest/src/node/cli.ts +++ b/packages/vitest/src/node/cli.ts @@ -11,40 +11,40 @@ const cli = cac('vitest') cli .version(version) - .option('-r, --root ', 'root path') - .option('-c, --config ', 'path to config file') - .option('-u, --update', 'update snapshot') - .option('-w, --watch', 'watch mode') - .option('-t, --testNamePattern ', 'run tests with full names matching the specified pattern') - .option('--dir ', 'base directory to scan for the test files') - .option('--ui', 'enable UI') - .option('--open', 'open UI automatically (default: !process.env.CI))') - .option('--api [api]', 'serve API, available options: --api.port , --api.host [host] and --api.strictPort') - .option('--threads', 'enabled threads (default: true)') - .option('--silent', 'silent console output from tests') - .option('--isolate', 'isolate environment for each test file (default: true)') - .option('--reporter ', 'reporter') - .option('--outputDiffMaxSize ', 'object diff output max size (default: 10000)') - .option('--outputDiffMaxLines ', 'max lines in diff output window (default: 50)') - .option('--outputTruncateLength ', 'diff output line length (default: 80)') - .option('--outputDiffLines ', 'number of lines in single diff (default: 15)') - .option('--outputFile ', 'write test results to a file when the --reporter=json or --reporter=junit option is also specified, use cac\'s dot notation for individual outputs of multiple reporters') - .option('--coverage', 'enable coverage report') - .option('--run', 'do not watch') - .option('--mode ', 'override Vite mode (default: test)') - .option('--globals', 'inject apis globally') - .option('--dom', 'mock browser api with happy-dom') - .option('--browser', 'run tests in browser') - .option('--environment ', 'runner environment (default: node)') - .option('--passWithNoTests', 'pass when no tests found') - .option('--logHeapUsage', 'show the size of heap for each test') + .option('-r, --root ', 'Root path') + .option('-c, --config ', 'Path to config file') + .option('-u, --update', 'Update snapshot') + .option('-w, --watch', 'Enable watch mode') + .option('-t, --testNamePattern ', 'Run tests with full names matching the specified regexp pattern') + .option('--dir ', 'Base directory to scan for the test files') + .option('--ui', 'Enable UI') + .option('--open', 'Open UI automatically (default: !process.env.CI))') + .option('--api [api]', 'Serve API, available options: --api.port , --api.host [host] and --api.strictPort') + .option('--threads', 'Enabled threads (default: true)') + .option('--silent', 'Silent console output from tests') + .option('--isolate', 'Isolate environment for each test file (default: true)') + .option('--reporter ', 'Specify reporters') + .option('--outputDiffMaxSize ', 'Object diff output max size (default: 10000)') + .option('--outputDiffMaxLines ', 'Max lines in diff output window (default: 50)') + .option('--outputTruncateLength ', 'Diff output line length (default: 80)') + .option('--outputDiffLines ', 'Number of lines in single diff (default: 15)') + .option('--outputFile ', 'Write test results to a file when supporter reporter is also specified, use cac\'s dot notation for individual outputs of multiple reporters') + .option('--coverage', 'Enable coverage report') + .option('--run', 'Disable watch mode') + .option('--mode ', 'Override Vite mode (default: test)') + .option('--globals', 'Inject apis globally') + .option('--dom', 'Mock browser api with happy-dom') + .option('--browser', 'Run tests in browser') + .option('--environment ', 'Specify runner environment (default: node)') + .option('--passWithNoTests', 'Pass when no tests found') + .option('--logHeapUsage', 'Show the size of heap for each test') .option('--allowOnly', 'Allow tests and suites that are marked as only (default: !process.env.CI)') .option('--dangerouslyIgnoreUnhandledErrors', 'Ignore any unhandled errors that occur') .option('--shard ', 'Test suite shard to execute in a format of /') .option('--changed [since]', 'Run tests that are affected by the changed files (default: false)') .option('--sequence ', 'Define in what order to run tests (use --sequence.shuffle to run tests in random order)') + .option('--segfaultRetry ', 'Return tests on segment fault (default: 0)', { default: 0 }) .option('--no-color', 'Removes colors from the console output') - .option('--segfault-retry ', 'Return tests on segment fault (default: 0)', { default: 0 }) .option('--inspect', 'Enable Node.js inspector') .option('--inspect-brk', 'Enable Node.js inspector with break') .help() From 0163dc8013073547dc1a5c285986c044f900de13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Tue, 10 Jan 2023 19:19:57 +0200 Subject: [PATCH 04/30] fix(coverage): prevent c8 from crashing on invalid sourcemaps (#2634) --- packages/coverage-c8/src/provider.ts | 1 + test/coverage-test/test/no-esbuild-transform.test.js | 10 ++++++++++ test/coverage-test/testing.mjs | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 test/coverage-test/test/no-esbuild-transform.test.js diff --git a/packages/coverage-c8/src/provider.ts b/packages/coverage-c8/src/provider.ts index f88630666ead..9797511a4435 100644 --- a/packages/coverage-c8/src/provider.ts +++ b/packages/coverage-c8/src/provider.ts @@ -86,6 +86,7 @@ export class C8CoverageProvider implements CoverageProvider { entry.map.mappings.length > 0 && entry.map.sourcesContent && entry.map.sourcesContent.length > 0 + && entry.map.sourcesContent[0] && entry.map.sourcesContent[0].length > 0 ) }) as SourceMapMeta[] diff --git a/test/coverage-test/test/no-esbuild-transform.test.js b/test/coverage-test/test/no-esbuild-transform.test.js new file mode 100644 index 000000000000..e6167c52b975 --- /dev/null +++ b/test/coverage-test/test/no-esbuild-transform.test.js @@ -0,0 +1,10 @@ +import { expect, test, vi } from 'vitest' +import { add } from '../src/utils' + +vi.mock('../src/utils', async () => ({ + add: vi.fn().mockReturnValue('mocked'), +})) + +test('mocking in Javascript test should not break sourcemaps', () => { + expect(add(1, 2)).toBe('mocked') +}) diff --git a/test/coverage-test/testing.mjs b/test/coverage-test/testing.mjs index 8a0dc86f7a42..f419f1acbcab 100644 --- a/test/coverage-test/testing.mjs +++ b/test/coverage-test/testing.mjs @@ -8,7 +8,7 @@ const provider = getArgument('--provider') const configs = [ // Run test cases. Generates coverage report. ['test/', { - include: ['test/*.test.ts'], + include: ['test/*.test.*'], exclude: ['coverage-report-tests/**/*'], }], From efbff2a2827c307a580ec039bc184c731afe4fb1 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 10 Jan 2023 18:24:07 +0100 Subject: [PATCH 05/30] fix: change Vite root, if test.root is used (#2637) * fix: change Vite root, if test.root is used * chore: cleanup --- packages/vitest/src/node/plugins/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index 839f460935f5..485e6be506a5 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -90,6 +90,7 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t open = '/' const config: ViteConfig = { + root: viteConfig.test?.root || options.root, esbuild: { sourcemap: 'external', From a186a7e12f318527493618487aa6a554549851fe Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 10 Jan 2023 18:24:50 +0100 Subject: [PATCH 06/30] fix: don't use ownKeys, when interoping a module (#2629) --- examples/mocks/package.json | 1 + examples/mocks/test/nested-default.spec.ts | 4 ++++ packages/vite-node/src/client.ts | 15 --------------- packages/vitest/src/runtime/mocker.ts | 2 +- packages/vitest/src/utils/base.ts | 14 ++++++++++---- pnpm-lock.yaml | 6 ++++++ 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/examples/mocks/package.json b/examples/mocks/package.json index b16de7816f43..106d7e1716d4 100644 --- a/examples/mocks/package.json +++ b/examples/mocks/package.json @@ -19,6 +19,7 @@ "devDependencies": { "@vitest/ui": "latest", "react": "^18.0.0", + "sweetalert2": "^11.6.16", "vite": "latest", "vitest": "latest", "vue": "^3.2.39", diff --git a/examples/mocks/test/nested-default.spec.ts b/examples/mocks/test/nested-default.spec.ts index e242f17ab889..aad8ea323695 100644 --- a/examples/mocks/test/nested-default.spec.ts +++ b/examples/mocks/test/nested-default.spec.ts @@ -1,9 +1,13 @@ // @vitest-environment jsdom +import sweetalert from 'sweetalert2' import * as modDefaultCjs from '../src/external/default-cjs.cjs' +vi.mock('sweetalert2') vi.mock('../src/external/default-cjs.cjs') test('default is mocked', () => { expect(vi.isMockFunction(modDefaultCjs.default.fn)).toBe(true) + expect(vi.isMockFunction(sweetalert)).toBe(true) + expect(vi.isMockFunction(sweetalert.clickCancel)).toBe(true) }) diff --git a/packages/vite-node/src/client.ts b/packages/vite-node/src/client.ts index 6286f0a87c40..b49d43f9dc28 100644 --- a/packages/vite-node/src/client.ts +++ b/packages/vite-node/src/client.ts @@ -424,14 +424,6 @@ export class ViteNodeRunner { const { mod, defaultExport } = interopModule(importedModule) - const modKeys = Reflect.ownKeys(mod) - let defaultKeys = !isPrimitive(defaultExport) ? Reflect.ownKeys(defaultExport) : [] - // remove reserved keys from default keys - if (typeof mod !== 'function' && typeof defaultExport === 'function') { - const reservedKeys = ['arguments', 'caller', 'prototype', 'name', 'length'] - defaultKeys = defaultKeys.filter(n => typeof n === 'string' && !reservedKeys.includes(n)) - } - return new Proxy(mod, { get(mod, prop) { if (prop === 'default') @@ -443,13 +435,6 @@ export class ViteNodeRunner { return defaultExport !== undefined return prop in mod || (defaultExport && prop in defaultExport) }, - // this is needed for mocker to know what is available to mock - ownKeys() { - if (!defaultExport || isPrimitive(defaultExport)) - return modKeys - const allKeys = [...modKeys, 'default', ...defaultKeys] - return Array.from(new Set(allKeys)) - }, getOwnPropertyDescriptor(mod, prop) { const descriptor = Reflect.getOwnPropertyDescriptor(mod, prop) if (descriptor) diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index d111f0839570..4c9ba61e9d8d 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -224,7 +224,7 @@ export class VitestMocker { const mockPropertiesOf = (container: Record, newContainer: Record) => { const containerType = getType(container) const isModule = containerType === 'Module' || !!container.__esModule - for (const { key: property, descriptor } of getAllMockableProperties(container)) { + for (const { key: property, descriptor } of getAllMockableProperties(container, isModule)) { // Modules define their exports as getters. We want to process those. if (!isModule && descriptor.get) { try { diff --git a/packages/vitest/src/utils/base.ts b/packages/vitest/src/utils/base.ts index fe8765cbffb8..dd4faa6838c3 100644 --- a/packages/vitest/src/utils/base.ts +++ b/packages/vitest/src/utils/base.ts @@ -11,8 +11,8 @@ function collectOwnProperties(obj: any, collector: Set | ((key: Object.getOwnPropertySymbols(obj).forEach(collect) } -export function getAllMockableProperties(obj: any) { - const allProps = new Set<{ key: string | symbol; descriptor: PropertyDescriptor }>() +export function getAllMockableProperties(obj: any, isModule: boolean) { + const allProps = new Map() let curr = obj do { // we don't need properties from these @@ -22,11 +22,17 @@ export function getAllMockableProperties(obj: any) { collectOwnProperties(curr, (key) => { const descriptor = Object.getOwnPropertyDescriptor(curr, key) if (descriptor) - allProps.add({ key, descriptor }) + allProps.set(key, { key, descriptor }) }) // eslint-disable-next-line no-cond-assign } while (curr = Object.getPrototypeOf(curr)) - return Array.from(allProps) + // default is not specified in ownKeys, if module is interoped + if (isModule && !allProps.has('default') && 'default' in obj) { + const descriptor = Object.getOwnPropertyDescriptor(obj, 'default') + if (descriptor) + allProps.set('default', { key: 'default', descriptor }) + } + return Array.from(allProps.values()) } export function notNullish(v: T | null | undefined): v is NonNullable { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fedf0cb7336..e5302ae48c39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -201,6 +201,7 @@ importers: '@vueuse/integrations': ^8.5.0 axios: ^0.26.1 react: ^18.0.0 + sweetalert2: ^11.6.16 tinyspy: ^0.3.2 vite: ^4.0.0 vitest: workspace:* @@ -213,6 +214,7 @@ importers: devDependencies: '@vitest/ui': link:../../packages/ui react: 18.2.0 + sweetalert2: 11.6.16 vite: 4.0.0 vitest: link:../../packages/vitest vue: 3.2.39 @@ -19473,6 +19475,10 @@ packages: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} dev: true + /sweetalert2/11.6.16: + resolution: {integrity: sha512-T2FO8LptErsjE4r0WMfiSk4YbeUvPadNaUZ/cADMEOnws000znrf8zFX9S5e/spvzJDyRI5En73WQyDZhGypxQ==} + dev: true + /swr/1.3.0_react@18.2.0: resolution: {integrity: sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==} peerDependencies: From 09d6222669433423a916d086022972761415d245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20B=C3=B6hme?= <61323346+richardboehme@users.noreply.github.com> Date: Tue, 10 Jan 2023 18:25:21 +0100 Subject: [PATCH 07/30] fix: cut duplicate error in negated toHaveBeenCalled (#2638) Previously the error message when failing a negated `toHaveBeenCalled` or `toHaveBeenCalledWith` expectation was duplicated. This commit removes the duplication. --- packages/expect/src/jest-expect.ts | 4 ++-- test/core/test/jest-expect.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index ed6f0af3d2fe..e57f6e9c574f 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -418,7 +418,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { ], ) if (called && isNot) - msg += formatCalls(spy, msg) + msg = formatCalls(spy, msg) if ((called && isNot) || (!called && !isNot)) { const err = new Error(msg) @@ -443,7 +443,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { ) if ((pass && isNot) || (!pass && !isNot)) { - msg += formatCalls(spy, msg, args) + msg = formatCalls(spy, msg, args) const err = new Error(msg) err.name = 'AssertionError' throw err diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 450403fa244a..f6d763cd903b 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -463,6 +463,32 @@ describe('toSatisfy()', () => { }) }) +describe('toHaveBeenCalled', () => { + describe('negated', () => { + it('fails if called', () => { + const mock = vi.fn() + mock() + + expect(() => { + expect(mock).not.toHaveBeenCalled() + }).toThrow(/^expected "spy" to not be called at all[^e]/) + }) + }) +}) + +describe('toHaveBeenCalledWith', () => { + describe('negated', () => { + it('fails if called', () => { + const mock = vi.fn() + mock(3) + + expect(() => { + expect(mock).not.toHaveBeenCalledWith(3) + }).toThrow(/^expected "spy" to not be called with arguments: \[ 3 \][^e]/) + }) + }) +}) + describe('async expect', () => { it('resolves', async () => { await expect((async () => 'true')()).resolves.toBe('true') From 7e38890394b127f97393bed3432e4113d8a77f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Wed, 11 Jan 2023 14:16:32 +0200 Subject: [PATCH 08/30] fix(coverage): istanbul provider to use `coverage.extension` (#2641) --- packages/coverage-istanbul/src/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts index 77e4e5602be8..150527aa0e68 100644 --- a/packages/coverage-istanbul/src/provider.ts +++ b/packages/coverage-istanbul/src/provider.ts @@ -66,7 +66,7 @@ export class IstanbulCoverageProvider implements CoverageProvider { include: typeof this.options.include === 'undefined' ? undefined : [...this.options.include], exclude: [...defaultExclude, ...defaultInclude, ...this.options.exclude], excludeNodeModules: true, - extension: configDefaults.coverage.extension, + extension: this.options.extension, }) } From 9967645ab7ade16a2b2495c7ee0277d180f866f1 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 11 Jan 2023 17:28:49 +0100 Subject: [PATCH 09/30] fix: always update last HMR ms on a module --- packages/vitest/src/node/core.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 74104d8df29d..b5392e6ab78f 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -403,9 +403,6 @@ export class Vitest { private _rerunTimer: any private async scheduleRerun(triggerId: string) { - const mod = this.server.moduleGraph.getModuleById(triggerId) - if (mod) - mod.lastHMRTimestamp = Date.now() const currentCount = this.restartsCount clearTimeout(this._rerunTimer) await this.runningPromise @@ -447,8 +444,15 @@ export class Vitest { private unregisterWatcher = noop private registerWatcher() { + const updateLastChanged = (id: string) => { + const mod = this.server.moduleGraph.getModuleById(id) + if (mod) + mod.lastHMRTimestamp = Date.now() + } + const onChange = (id: string) => { id = slash(id) + updateLastChanged(id) const needsRerun = this.handleFileChanged(id) if (needsRerun) this.scheduleRerun(id) @@ -467,6 +471,7 @@ export class Vitest { } const onAdd = async (id: string) => { id = slash(id) + updateLastChanged(id) if (await this.isTargetFile(id)) { this.changedTests.add(id) await this.cache.stats.updateStats(id) From ece434a38c00eb9f8c09df74f03c6875f669ca97 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 11 Jan 2023 17:30:29 +0100 Subject: [PATCH 10/30] fix: terminate workers, when closing process (#2645) * feat: destroy workers, when closing process * chore: terminate all workers when closing pool --- packages/vitest/src/node/pool.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vitest/src/node/pool.ts b/packages/vitest/src/node/pool.ts index 457d65cac2c0..6561b2436edd 100644 --- a/packages/vitest/src/node/pool.ts +++ b/packages/vitest/src/node/pool.ts @@ -127,7 +127,9 @@ export function createPool(ctx: Vitest): WorkerPool { return { runTests: runWithFiles('run'), - close: async () => {}, // TODO: not sure why this will cause Node crash: pool.destroy(), + close: async () => { + await Promise.all(pool.threads.map(w => w.terminate())) + }, } } From 866f449463daa253c1efcd71e14c2bcaf5f327a8 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 11 Jan 2023 17:30:39 +0100 Subject: [PATCH 11/30] feat: show error, when process.exit is called (#2643) * feat: throw error, when process.exit is called * chore: show exit error, but still exit process --- packages/vitest/src/node/pool.ts | 3 ++- packages/vitest/src/runtime/worker.ts | 7 ++----- packages/vitest/src/types/worker.ts | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/vitest/src/node/pool.ts b/packages/vitest/src/node/pool.ts index 6561b2436edd..9a86b9088e97 100644 --- a/packages/vitest/src/node/pool.ts +++ b/packages/vitest/src/node/pool.ts @@ -140,7 +140,8 @@ function createChannel(ctx: Vitest) { createBirpc<{}, WorkerRPC>( { - onWorkerExit(code) { + async onWorkerExit(error, code) { + await ctx.logger.printError(error, false, 'Unexpected Exit') process.exit(code || 1) }, snapshotSaved(snapshot) { diff --git a/packages/vitest/src/runtime/worker.ts b/packages/vitest/src/runtime/worker.ts index 20264039c600..ce9d8326be20 100644 --- a/packages/vitest/src/runtime/worker.ts +++ b/packages/vitest/src/runtime/worker.ts @@ -23,12 +23,9 @@ async function startViteNode(ctx: WorkerContext) { const processExit = process.exit - process.on('beforeExit', (code) => { - rpc().onWorkerExit(code) - }) - process.exit = (code = process.exitCode || 0): never => { - rpc().onWorkerExit(code) + const error = new Error(`process.exit called with "${code}"`) + rpc().onWorkerExit(error, code) return processExit(code) } diff --git a/packages/vitest/src/types/worker.ts b/packages/vitest/src/types/worker.ts index 17a121d80da4..e08575a2c2b5 100644 --- a/packages/vitest/src/types/worker.ts +++ b/packages/vitest/src/types/worker.ts @@ -27,7 +27,7 @@ export interface WorkerRPC { getSourceMap: (id: string, force?: boolean) => Promise onFinished: (files: File[], errors?: unknown[]) => void - onWorkerExit: (code?: number) => void + onWorkerExit: (error: unknown, code?: number) => void onPathsCollected: (paths: string[]) => void onUserConsoleLog: (log: UserConsoleLog) => void onUnhandledRejection: (err: unknown) => void From 1ffb0ef5489bcf873e879d61f3bdcebd364430c2 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 11 Jan 2023 17:30:49 +0100 Subject: [PATCH 12/30] feat: add more information about unhandler error (#2642) * feat: add more information about unhandler error * chore: fix type issue --- packages/vitest/src/node/error.ts | 17 +++++++++++++---- packages/vitest/src/runtime/worker.ts | 15 +++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index 31c30fa03781..e5f696169434 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -66,6 +66,17 @@ export async function printError(error: unknown, ctx: Vitest, options: PrintErro }) } + const testPath = (e as any).VITEST_TEST_PATH + const testName = (e as any).VITEST_TEST_NAME + // testName has testPath inside + if (testPath && !testName) + ctx.logger.error(c.red(`This error originated in "${c.bold(testPath)}" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.`)) + if (testName) { + ctx.logger.error(c.red(`The latest test that migh've cause the error is "${c.bold(testName)}". It might mean one of the following:` + + '\n- The error was thrown, while Vitest was running this test.' + + '\n- This was the last recorder test before the error was thrown, if error originated after test finished its execution.')) + } + if (typeof e.cause === 'object' && e.cause && 'name' in e.cause) { (e.cause as any).name = `Caused by: ${(e.cause as any).name}` await printError(e.cause, ctx, { fullStack, showCodeFrame: false }) @@ -97,6 +108,8 @@ const skipErrorProperties = new Set([ 'showDiff', 'actual', 'expected', + 'VITEST_TEST_NAME', + 'VITEST_TEST_PATH', ...Object.getOwnPropertyNames(Error.prototype), ...Object.getOwnPropertyNames(Object.prototype), ]) @@ -183,10 +196,6 @@ function printStack( logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, c.dim(`${path}:${frame.line}:${frame.column}`)].filter(Boolean).join(' ')}`)) onStack?.(frame) - - // reached at test file, skip the follow stack - if (frame.file in ctx.state.filesMap) - break } logger.error() const hasProperties = Object.keys(errorProperties).length > 0 diff --git a/packages/vitest/src/runtime/worker.ts b/packages/vitest/src/runtime/worker.ts index ce9d8326be20..245bcdebe7af 100644 --- a/packages/vitest/src/runtime/worker.ts +++ b/packages/vitest/src/runtime/worker.ts @@ -1,7 +1,8 @@ -import { resolve } from 'pathe' +import { relative, resolve } from 'pathe' import { createBirpc } from 'birpc' import { workerId as poolId } from 'tinypool' import { ModuleCacheMap } from 'vite-node/client' +import { isPrimitive } from 'vite-node/utils' import type { ResolvedConfig, WorkerContext, WorkerRPC } from '../types' import { distDir } from '../constants' import { getWorkerState } from '../utils' @@ -21,6 +22,8 @@ async function startViteNode(ctx: WorkerContext) { if (_viteNode) return _viteNode + const { config } = ctx + const processExit = process.exit process.exit = (code = process.exitCode || 0): never => { @@ -30,11 +33,15 @@ async function startViteNode(ctx: WorkerContext) { } process.on('unhandledRejection', (err) => { - rpc().onUnhandledRejection(processError(err)) + const worker = getWorkerState() + const error = processError(err) + if (worker.filepath && !isPrimitive(error)) { + error.VITEST_TEST_NAME = worker.current?.name + error.VITEST_TEST_PATH = relative(config.root, worker.filepath) + } + rpc().onUnhandledRejection(error) }) - const { config } = ctx - const { run } = (await executeInViteNode({ files: [ resolve(distDir, 'entry.js'), From 94968a6f9c032e415ff18e391970969e9f5b6a06 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 11 Jan 2023 17:31:01 +0100 Subject: [PATCH 13/30] feat: display running processes, if vitest closes with timeout (#2633) * feat: display running processes, if vitest closes with timeout * refactor: move hanging-process to reporter * chore: cleanup --- docs/config/index.md | 1 + packages/vitest/package.json | 3 ++- packages/vitest/src/node/core.ts | 6 ++++-- .../src/node/reporters/hanging-process.ts | 15 +++++++++++++++ packages/vitest/src/node/reporters/index.ts | 2 ++ packages/vitest/src/types/reporter.ts | 2 ++ pnpm-lock.yaml | 19 +++++++++++++++++++ 7 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 packages/vitest/src/node/reporters/hanging-process.ts diff --git a/docs/config/index.md b/docs/config/index.md index c23bc8e47a06..581498e0eb6c 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -364,6 +364,7 @@ Custom reporters for output. Reporters can be [a Reporter instance](https://gith - `'junit'` - JUnit XML reporter (you can configure `testsuites` tag name with `VITEST_JUNIT_SUITE_NAME` environmental variable) - `'json'` - give a simple JSON summary - `'html'` - outputs HTML report based on [`@vitest/ui`](/guide/ui) + - `'hanging-process'` - displays a list of hanging processes, if Vitest cannot exit process safely. This might be a heavy operation, enable it only if Vitest consistently cannot exit process - path of a custom reporter (e.g. `'./path/to/reporter.ts'`, `'@scope/reporter'`) ### outputTruncateLength diff --git a/packages/vitest/package.json b/packages/vitest/package.json index b906f7c67faf..47856a593163 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -122,7 +122,8 @@ "tinypool": "^0.3.0", "tinyspy": "^1.0.2", "vite": "^3.0.0 || ^4.0.0", - "vite-node": "workspace:*" + "vite-node": "workspace:*", + "why-is-node-running": "^2.2.2" }, "devDependencies": { "@antfu/install-pkg": "^0.1.1", diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index b5392e6ab78f..eeff77a45e11 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -553,8 +553,10 @@ export class Vitest { */ async exit(force = false) { setTimeout(() => { - console.warn(`close timed out after ${this.config.teardownTimeout}ms`) - process.exit() + this.report('onProcessTimeout').then(() => { + console.warn(`close timed out after ${this.config.teardownTimeout}ms`) + process.exit() + }) }, this.config.teardownTimeout).unref() await this.close() diff --git a/packages/vitest/src/node/reporters/hanging-process.ts b/packages/vitest/src/node/reporters/hanging-process.ts new file mode 100644 index 000000000000..41c7b4938429 --- /dev/null +++ b/packages/vitest/src/node/reporters/hanging-process.ts @@ -0,0 +1,15 @@ +import { createRequire } from 'module' +import type { Reporter } from '../../types' + +export class HangingProcessReporter implements Reporter { + whyRunning: (() => void) | undefined + + onInit(): void { + const _require = createRequire(import.meta.url) + this.whyRunning = _require('why-is-node-running') + } + + onProcessTimeout() { + this.whyRunning?.() + } +} diff --git a/packages/vitest/src/node/reporters/index.ts b/packages/vitest/src/node/reporters/index.ts index 664f2afcad58..91e4020aba84 100644 --- a/packages/vitest/src/node/reporters/index.ts +++ b/packages/vitest/src/node/reporters/index.ts @@ -5,6 +5,7 @@ import { VerboseReporter } from './verbose' import { TapReporter } from './tap' import { JUnitReporter } from './junit' import { TapFlatReporter } from './tap-flat' +import { HangingProcessReporter } from './hanging-process' export { DefaultReporter } @@ -16,6 +17,7 @@ export const ReportersMap = { 'tap': TapReporter, 'tap-flat': TapFlatReporter, 'junit': JUnitReporter, + 'hanging-process': HangingProcessReporter, } export type BuiltinReporters = keyof typeof ReportersMap diff --git a/packages/vitest/src/types/reporter.ts b/packages/vitest/src/types/reporter.ts index 9eb6b2b1292a..07e65804c450 100644 --- a/packages/vitest/src/types/reporter.ts +++ b/packages/vitest/src/types/reporter.ts @@ -16,6 +16,8 @@ export interface Reporter { onServerRestart?: (reason?: string) => Awaitable onUserConsoleLog?: (log: UserConsoleLog) => Awaitable + + onProcessTimeout?: () => Awaitable } export type { Vitest } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5302ae48c39..f2067b7174dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -875,6 +875,7 @@ importers: typescript: ^4.9.4 vite: ^4.0.0 vite-node: workspace:* + why-is-node-running: ^2.2.2 ws: ^8.12.0 dependencies: '@types/chai': 4.3.4 @@ -894,6 +895,7 @@ importers: tinyspy: 1.0.2 vite: 4.0.0_@types+node@18.7.13 vite-node: link:../vite-node + why-is-node-running: 2.2.2 devDependencies: '@antfu/install-pkg': 0.1.1 '@edge-runtime/vm': 2.0.2 @@ -18766,6 +18768,10 @@ packages: object-inspect: 1.12.2 dev: true + /siginfo/2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: false + /sigmund/1.0.1: resolution: {integrity: sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==} dev: true @@ -19093,6 +19099,10 @@ packages: escape-string-regexp: 2.0.0 dev: true + /stackback/0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: false + /stackframe/1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} dev: true @@ -21421,6 +21431,15 @@ packages: dependencies: isexe: 2.0.0 + /why-is-node-running/2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: false + /wide-align/1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: From db9e8f1128439fa0fd627b2864ca225d3d9349c7 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 11 Jan 2023 17:31:49 +0100 Subject: [PATCH 14/30] chore: release v0.27.1 --- package.json | 2 +- packages/browser/package.json | 2 +- packages/coverage-c8/package.json | 2 +- packages/coverage-istanbul/package.json | 2 +- packages/expect/package.json | 2 +- packages/spy/package.json | 2 +- packages/ui/package.json | 2 +- packages/utils/package.json | 2 +- packages/vite-node/package.json | 2 +- packages/vitest/package.json | 2 +- packages/web-worker/package.json | 2 +- packages/ws-client/package.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index ca523cbf07e6..fc968cec9f4b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/monorepo", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "private": true, "packageManager": "pnpm@7.23.0", "description": "A blazing fast unit test framework powered by Vite", diff --git a/packages/browser/package.json b/packages/browser/package.json index 392a6e9d5c20..81bfbb43289b 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/browser", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "Browser running for Vitest", "repository": { "type": "git", diff --git a/packages/coverage-c8/package.json b/packages/coverage-c8/package.json index 8903f949da0f..60a8af18e2f6 100644 --- a/packages/coverage-c8/package.json +++ b/packages/coverage-c8/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/coverage-c8", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "C8 coverage provider for Vitest", "author": "Anthony Fu ", "license": "MIT", diff --git a/packages/coverage-istanbul/package.json b/packages/coverage-istanbul/package.json index 8caafe06e67f..b970f21995f7 100644 --- a/packages/coverage-istanbul/package.json +++ b/packages/coverage-istanbul/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/coverage-istanbul", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "Istanbul coverage provider for Vitest", "author": "Anthony Fu ", "license": "MIT", diff --git a/packages/expect/package.json b/packages/expect/package.json index 12f9c0acea64..28037fbc1cde 100644 --- a/packages/expect/package.json +++ b/packages/expect/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/expect", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "Jest's expect matchers as a Chai plugin", "license": "MIT", "repository": { diff --git a/packages/spy/package.json b/packages/spy/package.json index a98a546f4269..f42323163745 100644 --- a/packages/spy/package.json +++ b/packages/spy/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/spy", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "Lightweight Jest compatible spy implementation", "license": "MIT", "repository": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 85bc4dde5b38..f171c0e76c8f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/ui", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "UI for Vitest", "license": "MIT", "repository": { diff --git a/packages/utils/package.json b/packages/utils/package.json index be0442ca0a5c..aa1c543ade5d 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/utils", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "Shared Vitest utility functions", "license": "MIT", "repository": { diff --git a/packages/vite-node/package.json b/packages/vite-node/package.json index 3378c4e6b5f5..5c781b2c5d84 100644 --- a/packages/vite-node/package.json +++ b/packages/vite-node/package.json @@ -1,6 +1,6 @@ { "name": "vite-node", - "version": "0.27.0", + "version": "0.27.1", "description": "Vite as Node.js runtime", "author": "Anthony Fu ", "license": "MIT", diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 47856a593163..a2bf6b139b23 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -1,7 +1,7 @@ { "name": "vitest", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "A blazing fast unit test framework powered by Vite", "author": "Anthony Fu ", "license": "MIT", diff --git a/packages/web-worker/package.json b/packages/web-worker/package.json index 63e280180b93..aee7f1d42a0d 100644 --- a/packages/web-worker/package.json +++ b/packages/web-worker/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/web-worker", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "Web Worker support for testing in Vitest", "repository": { "type": "git", diff --git a/packages/ws-client/package.json b/packages/ws-client/package.json index 2a8a98d3ca91..8cdbb53bd349 100644 --- a/packages/ws-client/package.json +++ b/packages/ws-client/package.json @@ -1,7 +1,7 @@ { "name": "@vitest/ws-client", "type": "module", - "version": "0.27.0", + "version": "0.27.1", "description": "WebSocket client wrapper for communicating with Vitest", "author": "Anthony Fu ", "license": "MIT", From 78e26e980febba467594817fbf329d853160c158 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Wed, 11 Jan 2023 17:33:47 +0100 Subject: [PATCH 15/30] chore: update lisence and auto-imports --- packages/ui/client/auto-imports.d.ts | 1 + packages/vitest/LICENSE.md | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/ui/client/auto-imports.d.ts b/packages/ui/client/auto-imports.d.ts index 58e175b362a0..66af6974a71f 100644 --- a/packages/ui/client/auto-imports.d.ts +++ b/packages/ui/client/auto-imports.d.ts @@ -107,6 +107,7 @@ declare global { const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] const useArraySome: typeof import('@vueuse/core')['useArraySome'] + const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique'] const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] const useAttrs: typeof import('vue')['useAttrs'] diff --git a/packages/vitest/LICENSE.md b/packages/vitest/LICENSE.md index 0cb365ba35d7..60565f1af0d4 100644 --- a/packages/vitest/LICENSE.md +++ b/packages/vitest/LICENSE.md @@ -2138,24 +2138,25 @@ By: Einar Otto Stangvik Repository: websockets/ws > Copyright (c) 2011 Einar Otto Stangvik +> Copyright (c) 2013 Arnout Kazemier and contributors +> Copyright (c) 2016 Luigi Pinca and contributors > -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: > > The above copyright notice and this permission notice shall be included in all > copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------- From 2daf44af2e22cd4727447848ff47ff8fea7f460d Mon Sep 17 00:00:00 2001 From: Robbe Claessens Date: Thu, 12 Jan 2023 10:17:08 +0100 Subject: [PATCH 16/30] docs: add describe.skipIf documentation (#2558) * add describe.skipIf documentation * Update index.md * Apply suggestions from code review Co-authored-by: Anjorin Damilare Co-authored-by: Robbe Co-authored-by: Vladimir Co-authored-by: Anjorin Damilare --- docs/api/index.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/api/index.md b/docs/api/index.md index fa00357268d1..14eca79345d5 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -497,6 +497,26 @@ When you use `test` or `bench` in the top level of file, they are collected as p }) ``` +### describe.skipIf + +- **Type:** `(condition: any) => void` + + In some cases, you might run suites multiple times with different environments, and some of the suites might be environment-specific. Instead of wrapping the suite with `if`, you can use `describe.skipIf` to skip the suite whenever the condition is truthy. + + ```ts + import { assert, test } from 'vitest' + + const isDev = process.env.NODE_ENV === 'development' + + describe.skipIf(isDev)('prod only test', () => { + // this test only runs in production + }) + ``` + +::: warning +You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). +::: + ### describe.only - **Type:** `(name: string, fn: TestFunction, options?: number | TestOptions) => void` From 1ac4bb8d50904d06a6fceb6567b8f1dd44ad67eb Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 12 Jan 2023 12:12:52 +0100 Subject: [PATCH 17/30] fix: document.defaultView references the same window as the global one (#2649) * fix: document.defaultView references the same window as the global one * chore: add a guard --- packages/vitest/src/integrations/env/utils.ts | 9 +++++++++ test/core/test/dom.test.ts | 5 +++++ test/core/test/happy-dom.test.ts | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/packages/vitest/src/integrations/env/utils.ts b/packages/vitest/src/integrations/env/utils.ts index 1b1a08de1a2a..230f1f2bbd55 100644 --- a/packages/vitest/src/integrations/env/utils.ts +++ b/packages/vitest/src/integrations/env/utils.ts @@ -72,6 +72,15 @@ export function populateGlobal(global: any, win: any, options: PopulateOptions = if (global.global) global.global = global + // rewrite defaultView to reference the same global context + if (global.document && global.document.defaultView) { + Object.defineProperty(global.document, 'defaultView', { + get: () => global, + enumerable: true, + configurable: true, + }) + } + skipKeys.forEach(k => keys.add(k)) return { diff --git a/test/core/test/dom.test.ts b/test/core/test/dom.test.ts index 261cf1d5cf92..27fcd7b977fa 100644 --- a/test/core/test/dom.test.ts +++ b/test/core/test/dom.test.ts @@ -130,10 +130,15 @@ it('can call global functions without window works as expected', async () => { }) it('globals are the same', () => { + expect(window).toBe(globalThis) + expect(window).toBe(global) expect(window.globalThis).toBe(globalThis) expect(window.Blob).toBe(globalThis.Blob) expect(window.globalThis.Blob).toBe(globalThis.Blob) expect(Blob).toBe(globalThis.Blob) + expect(document.defaultView).toBe(window) + const el = document.createElement('div') + expect(el.ownerDocument.defaultView).toBe(globalThis) }) it('can extend global class', () => { diff --git a/test/core/test/happy-dom.test.ts b/test/core/test/happy-dom.test.ts index 5dbb079a6fda..516ea02ebdd5 100644 --- a/test/core/test/happy-dom.test.ts +++ b/test/core/test/happy-dom.test.ts @@ -102,8 +102,14 @@ it('can call global functions without window works as expected', async () => { }) it('globals are the same', () => { + expect(window).toBe(globalThis) + expect(window).toBe(global) expect(window.globalThis).toBe(globalThis) expect(window.Blob).toBe(globalThis.Blob) expect(window.globalThis.Blob).toBe(globalThis.Blob) expect(Blob).toBe(globalThis.Blob) + expect(document.defaultView).toBe(window) + expect(document.defaultView).toBe(globalThis) + const el = document.createElement('div') + expect(el.ownerDocument.defaultView).toBe(globalThis) }) From d6225764856dd62cb1b21ec5bf4619052609c244 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Thu, 12 Jan 2023 13:07:30 +0100 Subject: [PATCH 18/30] docs: show Vladimir's sponsors --- README.md | 8 ++++++++ docs/.vitepress/components/HomePage.vue | 14 ++++++++++++++ docs/.vitepress/scripts/fetch-avatars.ts | 1 + 3 files changed, 23 insertions(+) diff --git a/README.md b/README.md index 45ee8e64922f..6d25c0cd3f1c 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,14 @@ $ npx vitest

+### Vladimir Sponsors + +

+ + + +

+ ### Patak Sponsors

diff --git a/docs/.vitepress/components/HomePage.vue b/docs/.vitepress/components/HomePage.vue index 22eae54943e1..534c1add4576 100644 --- a/docs/.vitepress/components/HomePage.vue +++ b/docs/.vitepress/components/HomePage.vue @@ -41,6 +41,20 @@ import { teamMembers } from '../contributors' > +

Patak's Sponsors diff --git a/docs/.vitepress/scripts/fetch-avatars.ts b/docs/.vitepress/scripts/fetch-avatars.ts index a8df3ba495b9..9692b49c9896 100644 --- a/docs/.vitepress/scripts/fetch-avatars.ts +++ b/docs/.vitepress/scripts/fetch-avatars.ts @@ -32,6 +32,7 @@ async function fetchSponsors() { await fs.ensureDir(dirSponsors) await download('https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg', join(dirSponsors, 'antfu.svg')) await download('https://cdn.jsdelivr.net/gh/patak-dev/static/sponsors.svg', join(dirSponsors, 'patak-dev.svg')) + await download('https://cdn.jsdelivr.net/gh/sheremet-va/static/sponsors.svg', join(dirSponsors, 'sheremet-va.svg')) } fetchAvatars() From d3dcbdc88dcb762e74e64e06224cbf511c2d6904 Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Fri, 13 Jan 2023 18:35:05 +0800 Subject: [PATCH 19/30] fix: trim input filename and test name (#2650) --- packages/vitest/src/node/stdin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vitest/src/node/stdin.ts b/packages/vitest/src/node/stdin.ts index fce2a739e0bb..0bfc603b0226 100644 --- a/packages/vitest/src/node/stdin.ts +++ b/packages/vitest/src/node/stdin.ts @@ -83,7 +83,7 @@ export function registerConsoleShortcuts(ctx: Vitest) { message: 'Input test name pattern (RegExp)', initial: ctx.config.testNamePattern?.source || '', }]) - await ctx.changeNamePattern(filter, undefined, 'change pattern') + await ctx.changeNamePattern(filter.trim(), undefined, 'change pattern') on() } @@ -95,8 +95,8 @@ export function registerConsoleShortcuts(ctx: Vitest) { message: 'Input filename pattern', initial: latestFilename, }]) - latestFilename = filter - await ctx.changeFilenamePattern(filter) + latestFilename = filter.trim() + await ctx.changeFilenamePattern(filter.trim()) on() } From 61ddebae61677f1a52208d9e598038fb5f51fbda Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 13 Jan 2023 13:54:50 +0100 Subject: [PATCH 20/30] fix(typecheck): log tests with verbose reporter, correctly show "pass" tests (#2656) --- packages/vitest/src/typecheck/typechecker.ts | 41 ++++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/vitest/src/typecheck/typechecker.ts b/packages/vitest/src/typecheck/typechecker.ts index 354f76532bbe..c8590b03193e 100644 --- a/packages/vitest/src/typecheck/typechecker.ts +++ b/packages/vitest/src/typecheck/typechecker.ts @@ -3,7 +3,7 @@ import type { ExecaChildProcess } from 'execa' import { execa } from 'execa' import { extname, resolve } from 'pathe' import { SourceMapConsumer } from 'source-map' -import { ensurePackageInstalled } from '../utils' +import { ensurePackageInstalled, getTasks } from '../utils' import type { Awaitable, File, ParsedStack, Task, TaskResultPack, TaskState, TscErrorInfo, Vitest } from '../types' import { getRawErrsMapFromTsCompile, getTsconfig } from './parse' import { createIndexMap } from './utils' @@ -77,6 +77,26 @@ export class Typechecker { return tests } + protected markPassed(file: File) { + if (!file.result?.state) { + file.result = { + state: 'pass', + } + } + const markTasks = (tasks: Task[]): void => { + for (const task of tasks) { + if ('tasks' in task) + markTasks(task.tasks) + if (!task.result?.state && task.mode === 'run') { + task.result = { + state: 'pass', + } + } + } + } + markTasks(file.tasks) + } + protected async prepareResults(output: string) { const typeErrors = await this.parseTscLikeOutput(output) const testFiles = new Set(this.getFiles()) @@ -91,18 +111,20 @@ export class Typechecker { const { file, definitions, map, parsed } = this._tests![path] const errors = typeErrors.get(path) files.push(file) - if (!errors) + if (!errors) { + this.markPassed(file) return + } const sortedDefinitions = [...definitions.sort((a, b) => b.start - a.start)] // has no map for ".js" files that use // @ts-check const mapConsumer = map && new SourceMapConsumer(map) const indexMap = createIndexMap(parsed) - const markFailed = (task: Task) => { + const markState = (task: Task, state: TaskState) => { task.result = { - state: task.mode === 'run' || task.mode === 'only' ? 'fail' : task.mode, + state: task.mode === 'run' || task.mode === 'only' ? state : task.mode, } if (task.suite) - markFailed(task.suite) + markState(task.suite, state) } errors.forEach(({ error, originalError }) => { const originalPos = mapConsumer?.generatedPositionFor({ @@ -121,8 +143,10 @@ export class Typechecker { } errors.push(error) if (state === 'fail' && suite.suite) - markFailed(suite.suite) + markState(suite.suite, 'fail') }) + + this.markPassed(file) }) typeErrors.forEach((errors, path) => { @@ -247,6 +271,9 @@ export class Typechecker { } public getTestPacks() { - return Object.values(this._tests || {}).map(i => [i.file.id, undefined] as TaskResultPack) + return Object.values(this._tests || {}) + .map(({ file }) => getTasks(file)) + .flat() + .map(i => [i.id, undefined] as TaskResultPack) } } From 13e53ac7b3c2b0a5eab01ba2d5c2860ccc8667e1 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 13 Jan 2023 16:30:54 +0100 Subject: [PATCH 21/30] fix: increase default teardownTimeout --- docs/config/index.md | 2 +- packages/vitest/src/defaults.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 581498e0eb6c..a61b5f51906f 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -473,7 +473,7 @@ Default timeout of a hook in milliseconds ### teardownTimeout - **Type:** `number` -- **Default:** `1000` +- **Default:** `10000` Default timeout to wait for close when Vitest shuts down, in milliseconds diff --git a/packages/vitest/src/defaults.ts b/packages/vitest/src/defaults.ts index 61d51640da0c..5aef9bc4bdaf 100644 --- a/packages/vitest/src/defaults.ts +++ b/packages/vitest/src/defaults.ts @@ -67,7 +67,7 @@ const config = { exclude: defaultExclude, testTimeout: 5000, hookTimeout: 10000, - teardownTimeout: 1000, + teardownTimeout: 10000, isolate: true, watchExclude: ['**/node_modules/**', '**/dist/**'], forceRerunTriggers: [ From 6145d7bf0d2b3ed0b55efcacd845b211bc3b724e Mon Sep 17 00:00:00 2001 From: Tomas Chmelevskij Date: Sat, 14 Jan 2023 11:30:07 +0100 Subject: [PATCH 22/30] docs: typo (#2663) --- docs/api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/index.md b/docs/api/index.md index 14eca79345d5..6c76e70721fe 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -2901,7 +2901,7 @@ import.meta.env.NODE_ENV === 'development' ### vi.stubGlobal -- **Type:** `(name: stirng | number | symbol, value: uknown) => Vitest` +- **Type:** `(name: string | number | symbol, value: uknown) => Vitest` Changes the value of global variable. You can restore its original value by calling `vi.unstubAllGlobals`. From 40187bdbbd96b6b0e00851e4f9984b87b2091e75 Mon Sep 17 00:00:00 2001 From: guillaumeduboc Date: Mon, 16 Jan 2023 09:21:43 +0100 Subject: [PATCH 23/30] feat: add runAllTimersAsync from sinonjs (#2209) close https://github.com/vitest-dev/vitest/issues/1804 --- docs/api/index.md | 65 +++ .../vitest/src/integrations/mock/timers.ts | 28 ++ packages/vitest/src/integrations/vi.ts | 20 + test/core/test/timers.test.ts | 388 ++++++++++++++++++ 4 files changed, 501 insertions(+) diff --git a/docs/api/index.md b/docs/api/index.md index 6c76e70721fe..a5f3af069f55 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -2553,6 +2553,19 @@ Vitest provides utility functions to help you out through it's **vi** helper. Yo vi.advanceTimersByTime(150) ``` +### vi.advanceTimersByTimeAsync + +- **Type:** `(ms: number) => Promise` + + Works just like `runAllTimersAsync`, but will end after passed milliseconds. This will include asynchronously set timers. For example this will log `1, 2, 3` and will not throw: + + ```ts + let i = 0 + setInterval(() => Promise.resolve().then(() => console.log(++i)), 50) + + await vi.advanceTimersByTimeAsync(150) + ``` + ### vi.advanceTimersToNextTimer - **Type:** `() => Vitest` @@ -2568,6 +2581,21 @@ Vitest provides utility functions to help you out through it's **vi** helper. Yo .advanceTimersToNextTimer() // log 3 ``` +### vi.advanceTimersToNextTimerAsync + +- **Type:** `() => Promise` + + Will call next available timer even if it was set asynchronously. Useful to make assertions between each timer call. You can chain call it to manage timers by yourself. + + ```ts + let i = 0 + setInterval(() => Promise.resolve().then(() => console.log(++i)), 50) + + vi.advanceTimersToNextTimerAsync() // log 1 + .advanceTimersToNextTimerAsync() // log 2 + .advanceTimersToNextTimerAsync() // log 3 + ``` + ### vi.getTimerCount - **Type:** `() => number` @@ -2984,6 +3012,21 @@ IntersectionObserver === undefined vi.runAllTimers() ``` +### vi.runAllTimersAsync + +- **Type:** `() => Promise` + + This method will asynchronously invoke every initiated timer until the timers queue is empty. It means that every timer called during `runAllTimersAsync` will be fired even asynchronous timers. If you have an infinite interval, + it will throw after 10 000 tries. For example this will log `result`: + + ```ts + setTimeout(async () => { + console.log(await Promise.resolve('result')) + }, 100) + + await vi.runAllTimersAsync() + ``` + ### vi.runOnlyPendingTimers - **Type:** `() => Vitest` @@ -2997,6 +3040,28 @@ IntersectionObserver === undefined vi.runOnlyPendingTimers() ``` +### vi.runOnlyPendingTimersAsync + +- **Type:** `() => Promise` + + This method will asynchronously call every timer that was initiated after `vi.useFakeTimers()` call, even asynchronous ones. It will not fire any timer that was initiated during its call. For example this will log `2, 3, 3, 1`: + + ```ts + setTimeout(() => { + console.log(1) + }, 100) + setTimeout(() => { + Promise.resolve().then(() => { + console.log(2) + setInterval(() => { + console.log(3) + }, 40) + }) + }, 10) + + await vi.runOnlyPendingTimersAsync() + ``` + ### vi.setSystemTime - **Type**: `(date: string | number | Date) => void` diff --git a/packages/vitest/src/integrations/mock/timers.ts b/packages/vitest/src/integrations/mock/timers.ts index 58881a93286e..2731204dc2e8 100644 --- a/packages/vitest/src/integrations/mock/timers.ts +++ b/packages/vitest/src/integrations/mock/timers.ts @@ -52,11 +52,21 @@ export class FakeTimers { this._clock.runAll() } + async runAllTimersAsync(): Promise { + if (this._checkFakeTimers()) + await this._clock.runAllAsync() + } + runOnlyPendingTimers(): void { if (this._checkFakeTimers()) this._clock.runToLast() } + async runOnlyPendingTimersAsync(): Promise { + if (this._checkFakeTimers()) + await this._clock.runToLastAsync() + } + advanceTimersToNextTimer(steps = 1): void { if (this._checkFakeTimers()) { for (let i = steps; i > 0; i--) { @@ -70,11 +80,29 @@ export class FakeTimers { } } + async advanceTimersToNextTimerAsync(steps = 1): Promise { + if (this._checkFakeTimers()) { + for (let i = steps; i > 0; i--) { + await this._clock.nextAsync() + // Fire all timers at this point: https://github.com/sinonjs/fake-timers/issues/250 + this._clock.tick(0) + + if (this._clock.countTimers() === 0) + break + } + } + } + advanceTimersByTime(msToRun: number): void { if (this._checkFakeTimers()) this._clock.tick(msToRun) } + async advanceTimersByTimeAsync(msToRun: number): Promise { + if (this._checkFakeTimers()) + await this._clock.tickAsync(msToRun) + } + runAllTicks(): void { if (this._checkFakeTimers()) { // @ts-expect-error method not exposed diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index 474befedd8c8..398fa4c47142 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -61,11 +61,21 @@ class VitestUtils { return this } + public async runOnlyPendingTimersAsync() { + await this._timers.runOnlyPendingTimersAsync() + return this + } + public runAllTimers() { this._timers.runAllTimers() return this } + public async runAllTimersAsync() { + await this._timers.runAllTimersAsync() + return this + } + public runAllTicks() { this._timers.runAllTicks() return this @@ -76,11 +86,21 @@ class VitestUtils { return this } + public async advanceTimersByTimeAsync(ms: number) { + await this._timers.advanceTimersByTimeAsync(ms) + return this + } + public advanceTimersToNextTimer() { this._timers.advanceTimersToNextTimer() return this } + public async advanceTimersToNextTimerAsync() { + await this._timers.advanceTimersToNextTimerAsync() + return this + } + public getTimerCount() { return this._timers.getTimerCount() } diff --git a/test/core/test/timers.test.ts b/test/core/test/timers.test.ts index 1ff9e1e005a9..85d821ddffc9 100644 --- a/test/core/test/timers.test.ts +++ b/test/core/test/timers.test.ts @@ -336,6 +336,136 @@ describe('FakeTimers', () => { }) }) + describe('runAllTimersAsync', () => { + it('runs all timers in order', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + const mock5 = vi.fn(() => runOrder.push('mock5')) + const mock6 = vi.fn(() => runOrder.push('mock6')) + + global.setTimeout(mock1, 100) + global.setTimeout(mock2, NaN) + global.setTimeout(mock3, 0) + const intervalHandler = global.setInterval(() => { + mock4() + global.clearInterval(intervalHandler) + }, 200) + global.setTimeout(mock5, Infinity) + global.setTimeout(mock6, -Infinity) + + await timers.runAllTimersAsync() + expect(runOrder).toEqual([ + 'mock2', + 'mock3', + 'mock5', + 'mock6', + 'mock1', + 'mock4', + ]) + }) + + it('warns when trying to advance timers while real timers are used', async () => { + const timers = new FakeTimers({ + config: { + rootDir: __dirname, + }, + global, + }) + await expect(timers.runAllTimersAsync()).rejects.toThrow(/Timers are not mocked/) + }) + + it('only runs a setTimeout callback once (ever)', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const fn = vi.fn() + global.setTimeout(fn, 0) + expect(fn).toHaveBeenCalledTimes(0) + + await timers.runAllTimersAsync() + expect(fn).toHaveBeenCalledTimes(1) + + await timers.runAllTimersAsync() + expect(fn).toHaveBeenCalledTimes(1) + }) + + it('runs callbacks with arguments after the interval', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const fn = vi.fn() + global.setTimeout(fn, 0, 'mockArg1', 'mockArg2') + + await timers.runAllTimersAsync() + expect(fn).toHaveBeenCalledTimes(1) + expect(fn).toHaveBeenCalledWith('mockArg1', 'mockArg2') + }) + + it('throws before allowing infinite recursion', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global, config: { loopLimit: 20 } }) + timers.useFakeTimers() + + global.setTimeout(function infinitelyRecursingCallback() { + global.setTimeout(infinitelyRecursingCallback, 0) + }, 0) + + await expect( + timers.runAllTimersAsync(), + ).rejects.toThrow( + 'Aborting after running 20 timers, assuming an infinite loop!', + ) + }) + + it('also clears ticks', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const fn = vi.fn() + global.setTimeout(() => { + process.nextTick(fn) + }, 0) + expect(fn).toHaveBeenCalledTimes(0) + + await timers.runAllTimersAsync() + expect(fn).toHaveBeenCalledTimes(1) + }) + + it('all callbacks are called when setTimeout calls asynchronous method', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder = [] + const mock2 = vi.fn(async () => { + runOrder.push('mock2') + return global.Promise.resolve(true) + }) + const mock1 = vi.fn(async () => { + await mock2() + runOrder.push('mock1') + }) + + global.setTimeout(mock1, 100) + await timers.runAllTimersAsync() + + expect(runOrder).toEqual([ + 'mock2', + 'mock1', + ]) + }) + }) + describe('advanceTimersByTime', () => { it('runs timers in order', () => { const global = { Date: FakeDate, clearTimeout, process, setTimeout } @@ -385,6 +515,55 @@ describe('FakeTimers', () => { }) }) + describe('advanceTimersByTimeAsync', () => { + it('runs timers in order', async () => { + const global = { Date: FakeDate, clearTimeout, clearInterval, process, setTimeout, setInterval, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + + global.setTimeout(mock1, 100) + global.setTimeout(mock2, 0) + global.setTimeout(mock3, 0) + global.setInterval(() => { + mock4() + }, 200) + + // Move forward to t=50 + await timers.advanceTimersByTimeAsync(50) + expect(runOrder).toEqual(['mock2', 'mock3']) + + // Move forward to t=60 + await timers.advanceTimersByTimeAsync(10) + expect(runOrder).toEqual(['mock2', 'mock3']) + + // Move forward to t=100 + await timers.advanceTimersByTimeAsync(40) + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']) + + // Move forward to t=200 + await timers.advanceTimersByTimeAsync(100) + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4']) + + // Move forward to t=400 + await timers.advanceTimersByTimeAsync(200) + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4', 'mock4']) + }) + + it('does nothing when no timers have been scheduled', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + await timers.advanceTimersByTimeAsync(100) + }) + }) + describe('advanceTimersToNextTimer', () => { it('runs timers in order', () => { const global = { Date: FakeDate, clearTimeout, process, setTimeout } @@ -487,6 +666,108 @@ describe('FakeTimers', () => { }) }) + describe('advanceTimersToNextTimerAsync', () => { + it('runs timers in order', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder: Array = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + + global.setTimeout(mock1, 100) + global.setTimeout(mock2, 0) + global.setTimeout(mock3, 0) + global.setInterval(() => { + mock4() + }, 200) + + await timers.advanceTimersToNextTimer() + // Move forward to t=0 + expect(runOrder).toEqual(['mock2', 'mock3']) + + await timers.advanceTimersToNextTimer() + // Move forward to t=100 + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']) + + await timers.advanceTimersToNextTimer() + // Move forward to t=200 + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4']) + + await timers.advanceTimersToNextTimer() + // Move forward to t=400 + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4', 'mock4']) + }) + + it('run correct amount of steps', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder: Array = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + + global.setTimeout(mock1, 100) + global.setTimeout(mock2, 0) + global.setTimeout(mock3, 0) + global.setInterval(() => { + mock4() + }, 200) + + // Move forward to t=100 + await timers.advanceTimersToNextTimer(2) + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']) + + // Move forward to t=600 + await timers.advanceTimersToNextTimer(3) + expect(runOrder).toEqual([ + 'mock2', + 'mock3', + 'mock1', + 'mock4', + 'mock4', + 'mock4', + ]) + }) + + it('setTimeout inside setTimeout', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder: Array = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + + global.setTimeout(mock1, 0) + global.setTimeout(() => { + mock2() + global.setTimeout(mock3, 50) + }, 25) + global.setTimeout(mock4, 100) + + // Move forward to t=75 + await timers.advanceTimersToNextTimer(3) + expect(runOrder).toEqual(['mock1', 'mock2', 'mock3']) + }) + + it('does nothing when no timers have been scheduled', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + await timers.advanceTimersToNextTimer() + }) + }) + describe('reset', () => { it('resets all pending setTimeouts', () => { const global = { Date: FakeDate, clearTimeout, process, setTimeout } @@ -639,6 +920,113 @@ describe('FakeTimers', () => { }) }) + describe('runOnlyPendingTimersAsync', () => { + it('runs all existing timers', async () => { + const global = { + Date: FakeDate, + clearTimeout, + process, + setTimeout, + Promise, + } + + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const spies = [vi.fn(), vi.fn()] + global.setTimeout(spies[0], 10) + global.setTimeout(spies[1], 50) + + await timers.runOnlyPendingTimersAsync() + + expect(spies[0]).toBeCalled() + expect(spies[1]).toBeCalled() + }) + + it('runs all timers in order', async () => { + const global = { + Date: FakeDate, + clearTimeout, + process, + setImmediate, + setTimeout, + Promise, + } + + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder = [] + + global.setTimeout(function cb() { + runOrder.push('mock1') + global.setTimeout(cb, 100) + }, 100) + + global.setTimeout(function cb() { + runOrder.push('mock2') + global.setTimeout(cb, 50) + }, 0) + + global.setInterval(() => { + runOrder.push('mock3') + }, 200) + + global.setImmediate(() => { + runOrder.push('mock4') + }) + + global.setImmediate(function cb() { + runOrder.push('mock5') + global.setTimeout(cb, 400) + }) + + await timers.runOnlyPendingTimersAsync() + const firsRunOrder = [ + 'mock4', + 'mock5', + 'mock2', + 'mock2', + 'mock1', + 'mock2', + 'mock2', + 'mock3', + 'mock1', + 'mock2', + ] + + expect(runOrder).toEqual(firsRunOrder) + + await timers.runOnlyPendingTimersAsync() + expect(runOrder).toEqual([ + ...firsRunOrder, + 'mock2', + 'mock1', + 'mock2', + 'mock2', + 'mock3', + 'mock5', + 'mock1', + 'mock2', + ]) + }) + + it('does not run timers that were cleared in another timer', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const fn = vi.fn() + const timer = global.setTimeout(fn, 10) + global.setTimeout(() => { + global.clearTimeout(timer) + }, 0) + + await timers.runOnlyPendingTimersAsync() + expect(fn).not.toBeCalled() + }) + }) + describe('useRealTimers', () => { it('resets native timer APIs', () => { const nativeSetTimeout = vi.fn() From f22ca93f4de0a62d7175064c5661f612a0d5064b Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 16 Jan 2023 13:07:57 +0100 Subject: [PATCH 24/30] chore: disable playwright test on windows --- examples/playwright/test/basic.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/playwright/test/basic.test.ts b/examples/playwright/test/basic.test.ts index ca1a00b5bce5..d5aa181f8379 100644 --- a/examples/playwright/test/basic.test.ts +++ b/examples/playwright/test/basic.test.ts @@ -5,7 +5,8 @@ import { chromium } from 'playwright' import type { Browser, Page } from 'playwright' import { expect } from '@playwright/test' -describe('basic', async () => { +// unstable in Windows, TODO: investigate +describe.runIf(process.platform !== 'win32')('basic', async () => { let server: PreviewServer let browser: Browser let page: Page From 6c1a26a6a3411c9a33c78cc7d44cddb2dd0edd6b Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 16 Jan 2023 13:10:23 +0100 Subject: [PATCH 25/30] fix: mock css files imported with "require" (#2679) This is usually happens inside UI libraries --- packages/vitest/src/runtime/setup.ts | 7 +++++++ test/core/src/file-css.css | 3 +++ test/core/src/file-sass.sass | 2 ++ test/core/src/file-scss.scss | 3 +++ test/core/test/require.test.ts | 15 +++++++++++++++ 5 files changed, 30 insertions(+) create mode 100644 test/core/src/file-css.css create mode 100644 test/core/src/file-sass.sass create mode 100644 test/core/src/file-scss.scss create mode 100644 test/core/test/require.test.ts diff --git a/packages/vitest/src/runtime/setup.ts b/packages/vitest/src/runtime/setup.ts index c6d7b679f4e1..3d6185b0b00f 100644 --- a/packages/vitest/src/runtime/setup.ts +++ b/packages/vitest/src/runtime/setup.ts @@ -1,3 +1,5 @@ +/* eslint-disable n/no-deprecated-api */ + import { installSourcemapsSupport } from 'vite-node/source-map' import { environments } from '../integrations/env' import type { Environment, ResolvedConfig } from '../types' @@ -23,6 +25,11 @@ export async function setupGlobalEnv(config: ResolvedConfig) { if (globalSetup) return + // always mock "required" `css` files, because we cannot process them + require.extensions['.css'] = () => ({}) + require.extensions['.scss'] = () => ({}) + require.extensions['.sass'] = () => ({}) + globalSetup = true if (isNode) { diff --git a/test/core/src/file-css.css b/test/core/src/file-css.css new file mode 100644 index 000000000000..c6dd2c88fabe --- /dev/null +++ b/test/core/src/file-css.css @@ -0,0 +1,3 @@ +.red { + color: red; +} diff --git a/test/core/src/file-sass.sass b/test/core/src/file-sass.sass new file mode 100644 index 000000000000..dcb2babacb1c --- /dev/null +++ b/test/core/src/file-sass.sass @@ -0,0 +1,2 @@ +.red + color: red; diff --git a/test/core/src/file-scss.scss b/test/core/src/file-scss.scss new file mode 100644 index 000000000000..c6dd2c88fabe --- /dev/null +++ b/test/core/src/file-scss.scss @@ -0,0 +1,3 @@ +.red { + color: red; +} diff --git a/test/core/test/require.test.ts b/test/core/test/require.test.ts new file mode 100644 index 000000000000..a26e1f59e177 --- /dev/null +++ b/test/core/test/require.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest' + +const _require = require + +describe('using "require" to import a module', () => { + it('importing css files works, but doesn\'t process them', () => { + const css = _require('./../src/file-css.css') + const sass = _require('./../src/file-sass.sass') + const scss = _require('./../src/file-scss.scss') + + expect(css).toEqual({}) + expect(sass).toEqual({}) + expect(scss).toEqual({}) + }) +}) From 45cc34230beb2041dfe8aa10ece969b8a0f5c428 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 16 Jan 2023 13:22:39 +0100 Subject: [PATCH 26/30] perf: don't resolve import path, if it was already resolved (#2659) --- packages/vite-node/src/client.ts | 8 ++++---- packages/vite-node/src/server.ts | 2 +- packages/vite-node/src/utils.ts | 27 ++++++++++++++++----------- test/core/test/file-path.test.ts | 16 ++++++++-------- test/core/test/imports.test.ts | 11 ++++++++++- test/vite-node/test/server.test.ts | 3 +++ 6 files changed, 42 insertions(+), 25 deletions(-) diff --git a/packages/vite-node/src/client.ts b/packages/vite-node/src/client.ts index b49d43f9dc28..de78b7d6a012 100644 --- a/packages/vite-node/src/client.ts +++ b/packages/vite-node/src/client.ts @@ -213,14 +213,15 @@ export class ViteNodeRunner { if (importee && id.startsWith(VALID_ID_PREFIX)) importee = undefined id = normalizeRequestId(id, this.options.base) - if (!this.options.resolveId) - return [id, toFilePath(id, this.root)] + const { path, exists } = toFilePath(id, this.root) + if (!this.options.resolveId || exists) + return [id, path] const resolved = await this.options.resolveId(id, importee) const resolvedId = resolved ? normalizeRequestId(resolved.id, this.options.base) : id // to be compatible with dependencies that do not resolve id - const fsPath = resolved ? resolvedId : toFilePath(id, this.root) + const fsPath = resolved ? resolvedId : path return [resolvedId, fsPath] } @@ -278,7 +279,6 @@ export class ViteNodeRunner { if (id in requestStubs) return requestStubs[id] - // eslint-disable-next-line prefer-const let { code: transformed, externalize } = await this.options.fetchModule(id) if (externalize) { diff --git a/packages/vite-node/src/server.ts b/packages/vite-node/src/server.ts index f8e1c10d42fe..9a067208406e 100644 --- a/packages/vite-node/src/server.ts +++ b/packages/vite-node/src/server.ts @@ -125,7 +125,7 @@ export class ViteNodeServer { private async _fetchModule(id: string): Promise { let result: FetchResult - const filePath = toFilePath(id, this.server.config.root) + const { path: filePath } = toFilePath(id, this.server.config.root) const module = this.server.moduleGraph.getModuleById(id) const timestamp = module ? module.lastHMRTimestamp : null diff --git a/packages/vite-node/src/utils.ts b/packages/vite-node/src/utils.ts index e11f22f6ffaf..424acf1d43fd 100644 --- a/packages/vite-node/src/utils.ts +++ b/packages/vite-node/src/utils.ts @@ -61,27 +61,32 @@ export function isPrimitive(v: any) { return v !== Object(v) } -export function toFilePath(id: string, root: string): string { - let absolute = (() => { +export function toFilePath(id: string, root: string): { path: string; exists: boolean } { + let { absolute, exists } = (() => { if (id.startsWith('/@fs/')) - return id.slice(4) + return { absolute: id.slice(4), exists: true } + // check if /src/module.js -> /src/module.js if (!id.startsWith(root) && id.startsWith('/')) { const resolved = resolve(root, id.slice(1)) - // The resolved path can have query values. Remove them before checking - // the file path. - if (existsSync(resolved.replace(/\?.*$/, ''))) - return resolved + if (existsSync(cleanUrl(resolved))) + return { absolute: resolved, exists: true } } - return id + else if (id.startsWith(root) && existsSync(cleanUrl(id))) { + return { absolute: id, exists: true } + } + return { absolute: id, exists: false } })() if (absolute.startsWith('//')) absolute = absolute.slice(1) // disambiguate the `:/` on windows: see nodejs/node#31710 - return isWindows && absolute.startsWith('/') - ? slash(fileURLToPath(pathToFileURL(absolute.slice(1)).href)) - : absolute + return { + path: isWindows && absolute.startsWith('/') + ? slash(fileURLToPath(pathToFileURL(absolute.slice(1)).href)) + : absolute, + exists, + } } /** diff --git a/test/core/test/file-path.test.ts b/test/core/test/file-path.test.ts index c0b1a4f37319..98a59ec75091 100644 --- a/test/core/test/file-path.test.ts +++ b/test/core/test/file-path.test.ts @@ -82,7 +82,7 @@ describe('toFilePath', () => { const expected = 'C:/path/to/project/node_modules/pkg/file.js' const processSpy = vi.spyOn(process, 'cwd').mockReturnValue(root) - const filePath = toFilePath(id, root) + const { path: filePath } = toFilePath(id, root) processSpy.mockRestore() expect(slash(filePath)).toEqual(expected) @@ -94,7 +94,7 @@ describe('toFilePath', () => { const expected = 'C:/path/to/project/node_modules/pkg/file.js' const processSpy = vi.spyOn(process, 'cwd').mockReturnValue(root) - const filePath = toFilePath(id, root) + const { path: filePath } = toFilePath(id, root) processSpy.mockRestore() expect(slash(filePath)).toEqual(expected) @@ -110,7 +110,7 @@ describe('toFilePath', () => { const processSpy = vi.spyOn(process, 'cwd').mockReturnValue(root) const existsSpy = vi.mocked(existsSync).mockReturnValue(true) - const filePath = toFilePath(id, root) + const { path: filePath } = toFilePath(id, root) processSpy.mockRestore() existsSpy.mockRestore() @@ -124,7 +124,7 @@ describe('toFilePath', () => { const processSpy = vi.spyOn(process, 'cwd').mockReturnValue(root) const existsSpy = vi.mocked(existsSync).mockReturnValue(true) - const filePath = toFilePath(id, root) + const { path: filePath } = toFilePath(id, root) processSpy.mockRestore() existsSpy.mockRestore() @@ -138,7 +138,7 @@ describe('toFilePath', () => { const processSpy = vi.spyOn(process, 'cwd').mockReturnValue(root) const existsSpy = vi.mocked(existsSync).mockReturnValue(true) - const filePath = toFilePath(id, root) + const { path: filePath } = toFilePath(id, root) processSpy.mockRestore() existsSpy.mockRestore() @@ -152,7 +152,7 @@ describe('toFilePath', () => { const processSpy = vi.spyOn(process, 'cwd').mockReturnValue(root) const existsSpy = vi.mocked(existsSync).mockReturnValue(true) - const filePath = toFilePath(id, root) + const { path: filePath } = toFilePath(id, root) processSpy.mockRestore() existsSpy.mockRestore() @@ -166,7 +166,7 @@ describe('toFilePath', () => { const processSpy = vi.spyOn(process, 'cwd').mockReturnValue(root) const existsSpy = vi.mocked(existsSync).mockReturnValue(true) - const filePath = toFilePath(id, root) + const { path: filePath } = toFilePath(id, root) processSpy.mockRestore() existsSpy.mockRestore() @@ -179,7 +179,7 @@ describe('toFilePath', () => { const processSpy = vi.spyOn(process, 'cwd').mockReturnValue(root) const existsSpy = vi.mocked(existsSync).mockReturnValue(false) - const filePath = toFilePath(id, root) + const { path: filePath } = toFilePath(id, root) processSpy.mockRestore() existsSpy.mockRestore() diff --git a/test/core/test/imports.test.ts b/test/core/test/imports.test.ts index eed2418e2640..874f236b0c30 100644 --- a/test/core/test/imports.test.ts +++ b/test/core/test/imports.test.ts @@ -28,7 +28,7 @@ test('dynamic aliased import works', async () => { expect(stringTimeoutMod).toBe(variableTimeoutMod) }) -test('dynamic absolute import works', async () => { +test('dynamic absolute from root import works', async () => { const stringTimeoutMod = await import('./../src/timeout') const timeoutPath = '/src/timeout' @@ -37,6 +37,15 @@ test('dynamic absolute import works', async () => { expect(stringTimeoutMod).toBe(variableTimeoutMod) }) +test('dynamic absolute with extension mport works', async () => { + const stringTimeoutMod = await import('./../src/timeout') + + const timeoutPath = '/src/timeout.ts' + const variableTimeoutMod = await import(timeoutPath) + + expect(stringTimeoutMod).toBe(variableTimeoutMod) +}) + test('data with dynamic import works', async () => { const dataUri = 'data:text/javascript;charset=utf-8,export default "hi"' const { default: hi } = await import(dataUri) diff --git a/test/vite-node/test/server.test.ts b/test/vite-node/test/server.test.ts index cdb9ca1fcbfc..a7f165371110 100644 --- a/test/vite-node/test/server.test.ts +++ b/test/vite-node/test/server.test.ts @@ -10,6 +10,9 @@ describe('server works correctly', async () => { config: { root: '/', }, + moduleGraph: { + idToModuleMap: new Map(), + }, } as any, { transformMode: { web: [/web/], From 2d90270473dbdfc8c162fbf9852a98b35fdfc993 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 16 Jan 2023 13:25:14 +0100 Subject: [PATCH 27/30] docs: add fs-extra to docs dependencies (#2681) --- docs/package.json | 3 ++- pnpm-lock.yaml | 42 ++++++++++++++++++++++-------------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/package.json b/docs/package.json index 0c86036c0ef9..ed418ae91091 100644 --- a/docs/package.json +++ b/docs/package.json @@ -21,12 +21,13 @@ "@vitejs/plugin-vue": "latest", "esno": "^0.16.3", "fast-glob": "^3.2.12", + "fs-extra": "^10.1.0", "https-localhost": "^4.7.1", "unocss": "^0.48.3", "unplugin-vue-components": "^0.22.12", "vite": "^4.0.0", "vite-plugin-pwa": "0.14.1", - "vitepress": "1.0.0-alpha.35", + "vitepress": "1.0.0-alpha.36", "workbox-window": "^6.5.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2067b7174dd..1523951d99bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,13 +98,14 @@ importers: '@vueuse/core': ^9.10.0 esno: ^0.16.3 fast-glob: ^3.2.12 + fs-extra: ^10.1.0 https-localhost: ^4.7.1 jiti: ^1.16.1 unocss: ^0.48.3 unplugin-vue-components: ^0.22.12 vite: ^4.0.0 vite-plugin-pwa: 0.14.1 - vitepress: 1.0.0-alpha.35 + vitepress: 1.0.0-alpha.36 vue: latest workbox-window: ^6.5.4 dependencies: @@ -117,12 +118,13 @@ importers: '@vitejs/plugin-vue': 4.0.0_vite@4.0.0+vue@3.2.45 esno: 0.16.3 fast-glob: 3.2.12 + fs-extra: 10.1.0 https-localhost: 4.7.1 unocss: 0.48.3_vite@4.0.0 unplugin-vue-components: 0.22.12_vue@3.2.45 vite: 4.0.0 vite-plugin-pwa: 0.14.1_hsd7wmhtxdre4ds2vghqydhvau - vitepress: 1.0.0-alpha.35 + vitepress: 1.0.0-alpha.36 workbox-window: 6.5.4 examples/basic: @@ -290,7 +292,7 @@ importers: '@types/react-test-renderer': 17.0.2 '@vitejs/plugin-react': 3.0.1_vite@4.0.0 '@vitest/ui': link:../../packages/ui - happy-dom: 8.1.3 + happy-dom: 8.1.4 jsdom: 21.0.0 react-test-renderer: 17.0.2_react@17.0.2 vite: 4.0.0 @@ -536,11 +538,11 @@ importers: vite: ^4.0.0 vitest: workspace:* devDependencies: - '@sveltejs/vite-plugin-svelte': 2.0.0_svelte@3.55.0+vite@4.0.0 - '@testing-library/svelte': 3.2.2_svelte@3.55.0 + '@sveltejs/vite-plugin-svelte': 2.0.0_svelte@3.55.1+vite@4.0.0 + '@testing-library/svelte': 3.2.2_svelte@3.55.1 '@vitest/ui': link:../../packages/ui jsdom: 21.0.0 - svelte: 3.55.0 + svelte: 3.55.1 vite: 4.0.0 vitest: link:../../packages/vitest @@ -1037,7 +1039,7 @@ importers: devDependencies: '@vitejs/plugin-vue': 4.0.0_vite@4.0.0+vue@3.2.45 '@vue/test-utils': 2.2.7_vue@3.2.45 - happy-dom: 8.1.3 + happy-dom: 8.1.4 vite: 4.0.0 vitest: link:../../packages/vitest vue: 3.2.45 @@ -7105,7 +7107,7 @@ packages: string.prototype.matchall: 4.0.7 dev: true - /@sveltejs/vite-plugin-svelte/2.0.0_svelte@3.55.0+vite@4.0.0: + /@sveltejs/vite-plugin-svelte/2.0.0_svelte@3.55.1+vite@4.0.0: resolution: {integrity: sha512-oUFrYQarRv4fppmxdrv00qw3wX8Ycdj0uv33MfpRZyR8K67dyxiOcHnqkB0zSy5sDJA8RC/2aNtYhXJ8NINVHQ==} engines: {node: ^14.18.0 || >= 16} peerDependencies: @@ -7116,8 +7118,8 @@ packages: deepmerge: 4.2.2 kleur: 4.1.5 magic-string: 0.27.0 - svelte: 3.55.0 - svelte-hmr: 0.15.1_svelte@3.55.0 + svelte: 3.55.1 + svelte-hmr: 0.15.1_svelte@3.55.1 vite: 4.0.0 vitefu: 0.2.3_vite@4.0.0 transitivePeerDependencies: @@ -7234,14 +7236,14 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: true - /@testing-library/svelte/3.2.2_svelte@3.55.0: + /@testing-library/svelte/3.2.2_svelte@3.55.1: resolution: {integrity: sha512-IKwZgqbekC3LpoRhSwhd0JswRGxKdAGkf39UiDXTywK61YyLXbCYoR831e/UUC6EeNW4hiHPY+2WuovxOgI5sw==} engines: {node: '>= 10'} peerDependencies: svelte: 3.x dependencies: '@testing-library/dom': 8.17.1 - svelte: 3.55.0 + svelte: 3.55.1 dev: true /@testing-library/user-event/13.5.0: @@ -13575,8 +13577,8 @@ packages: - encoding dev: true - /happy-dom/8.1.3: - resolution: {integrity: sha512-XC0ZvAmMOfW56X7yw9IrBuVpHKRzVAMDbtoF4MmOHoe17r2zY6yTMhs+lrsz9KLGQUcFyDPianULPAOp1+OnVg==} + /happy-dom/8.1.4: + resolution: {integrity: sha512-mUCzXHhSO6fOQlZwKW6z2f/+rYavKNxNrgY4nJ4dp+r8gTGbTENgMZGfM6eJD0DJPRFF8DFyngXdBF93wF96UA==} dependencies: css.escape: 1.5.1 he: 1.2.0 @@ -19456,17 +19458,17 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /svelte-hmr/0.15.1_svelte@3.55.0: + /svelte-hmr/0.15.1_svelte@3.55.1: resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} engines: {node: ^12.20 || ^14.13.1 || >= 16} peerDependencies: svelte: '>=3.19.0' dependencies: - svelte: 3.55.0 + svelte: 3.55.1 dev: true - /svelte/3.55.0: - resolution: {integrity: sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==} + /svelte/3.55.1: + resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} engines: {node: '>= 8'} dev: true @@ -20957,8 +20959,8 @@ packages: vite: 4.0.0 dev: true - /vitepress/1.0.0-alpha.35: - resolution: {integrity: sha512-tJQjJstq+Ryb4pKtlxV4tD8KhxId+DTbR1FRrtJBhA+Vv4nexFHra5M9EgK9jUmoMc3rkyNaw7P3Kkix0ArP1w==} + /vitepress/1.0.0-alpha.36: + resolution: {integrity: sha512-JH9NlBQpQrcIudF1HM8Sq3K6FbOiOTrD33PI0n+pmJQ7dOu4WAm3YnwevMInW8leNiEEWCAH+coIkOYcf3fhpQ==} hasBin: true dependencies: '@docsearch/css': 3.3.1 From 85096281aad680a0e108554738ac395ef8c80586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Mon, 16 Jan 2023 14:26:41 +0200 Subject: [PATCH 28/30] fix(coverage): watch mode to use `coverage.all` only when all tests are run (#2665) * fix(coverage): always print reporter name * fix(coverage): report on watch mode manually triggered re-runs * fix(coverage): watch mode to use `coverage.all` only when all tests are run --- packages/coverage-c8/src/provider.ts | 14 ++++++++++---- packages/coverage-istanbul/src/provider.ts | 6 +++--- packages/vitest/src/node/core.ts | 20 +++++++++++++++----- packages/vitest/src/types/coverage.ts | 7 ++++++- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/coverage-c8/src/provider.ts b/packages/coverage-c8/src/provider.ts index 9797511a4435..ab6ea99516eb 100644 --- a/packages/coverage-c8/src/provider.ts +++ b/packages/coverage-c8/src/provider.ts @@ -6,7 +6,7 @@ import { extname, resolve } from 'pathe' import type { RawSourceMap } from 'vite-node' import { configDefaults } from 'vitest/config' // eslint-disable-next-line no-restricted-imports -import type { CoverageC8Options, CoverageProvider, ResolvedCoverageOptions } from 'vitest' +import type { CoverageC8Options, CoverageProvider, ReportContext, ResolvedCoverageOptions } from 'vitest' import type { Vitest } from 'vitest/node' // @ts-expect-error missing types import createReport from 'c8/lib/report.js' @@ -44,9 +44,15 @@ export class C8CoverageProvider implements CoverageProvider { takeCoverage() } - async reportCoverage() { + async reportCoverage({ allTestsRun }: ReportContext) { takeCoverage() - const report = createReport(this.ctx.config.coverage) + + const options = { + ...this.options, + all: this.options.all && allTestsRun, + } + + const report = createReport(options) interface MapAndSource { map: RawSourceMap; source: string | undefined } type SourceMapMeta = { url: string; filepath: string } & MapAndSource @@ -136,7 +142,7 @@ export class C8CoverageProvider implements CoverageProvider { } await report.run() - await checkCoverages(this.options, report) + await checkCoverages(options, report) // Note that this will only clean up the V8 reports generated so far. // There will still be a temp directory with some reports when vitest exists, diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts index 150527aa0e68..a208b45ec045 100644 --- a/packages/coverage-istanbul/src/provider.ts +++ b/packages/coverage-istanbul/src/provider.ts @@ -2,7 +2,7 @@ import { existsSync, promises as fs } from 'fs' import { relative, resolve } from 'pathe' import type { TransformPluginContext } from 'rollup' -import type { AfterSuiteRunMeta, CoverageIstanbulOptions, CoverageProvider, ResolvedCoverageOptions, Vitest } from 'vitest' +import type { AfterSuiteRunMeta, CoverageIstanbulOptions, CoverageProvider, ReportContext, ResolvedCoverageOptions, Vitest } from 'vitest' import { configDefaults, defaultExclude, defaultInclude } from 'vitest/config' import libReport from 'istanbul-lib-report' import reports from 'istanbul-reports' @@ -98,14 +98,14 @@ export class IstanbulCoverageProvider implements CoverageProvider { this.coverages = [] } - async reportCoverage() { + async reportCoverage({ allTestsRun }: ReportContext) { const mergedCoverage: CoverageMap = this.coverages.reduce((coverage, previousCoverageMap) => { const map = libCoverage.createCoverageMap(coverage) map.merge(previousCoverageMap) return map }, {}) - if (this.options.all) + if (this.options.all && allTestsRun) await this.includeUntestedFiles(mergedCoverage) includeImplicitElseBranches(mergedCoverage) diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index eeff77a45e11..26597bebf19d 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -238,10 +238,7 @@ export class Vitest { await this.runFiles(files) - if (this.coverageProvider) { - this.logger.log(c.blue(' % ') + c.dim('Coverage report from ') + c.yellow(this.coverageProvider.name)) - await this.coverageProvider.reportCoverage() - } + await this.reportCoverage(true) if (this.config.watch && !this.config.browser) await this.report('onWatcherStart') @@ -357,8 +354,14 @@ export class Vitest { } async rerunFiles(files: string[] = this.state.getFilepaths(), trigger?: string) { + if (this.coverageProvider && this.config.coverage.cleanOnRerun) + await this.coverageProvider.clean() + await this.report('onWatcherRerun', files, trigger) await this.runFiles(files) + + await this.reportCoverage(!trigger) + if (!this.config.browser) await this.report('onWatcherStart') } @@ -435,7 +438,7 @@ export class Vitest { await this.runFiles(files) - await this.coverageProvider?.reportCoverage() + await this.reportCoverage(false) if (!this.config.browser) await this.report('onWatcherStart') @@ -533,6 +536,13 @@ export class Vitest { return rerun } + private async reportCoverage(allTestsRun: boolean) { + if (this.coverageProvider) { + this.logger.log(c.blue(' % ') + c.dim('Coverage report from ') + c.yellow(this.coverageProvider.name)) + await this.coverageProvider.reportCoverage({ allTestsRun }) + } + } + async close() { if (!this.closingPromise) { this.closingPromise = Promise.allSettled([ diff --git a/packages/vitest/src/types/coverage.ts b/packages/vitest/src/types/coverage.ts index 7dd202b6dca5..2602ca0680e7 100644 --- a/packages/vitest/src/types/coverage.ts +++ b/packages/vitest/src/types/coverage.ts @@ -13,7 +13,7 @@ export interface CoverageProvider { onBeforeFilesRun?(): void | Promise onAfterSuiteRun(meta: AfterSuiteRunMeta): void | Promise - reportCoverage(): void | Promise + reportCoverage(reportContext: ReportContext): void | Promise onFileTransform?( sourceCode: string, @@ -22,6 +22,11 @@ export interface CoverageProvider { ): TransformResult | Promise } +export interface ReportContext { + /** Indicates whether all tests were run. False when only specific tests were run. */ + allTestsRun?: boolean +} + export interface CoverageProviderModule { /** * Factory for creating a new coverage provider From 0a31e85ca7f82b173a7995d61b58552bf18a28f9 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 16 Jan 2023 13:56:29 +0100 Subject: [PATCH 29/30] fix: don't start watching files in "run" mode (#2680) --- packages/vitest/src/node/plugins/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index 485e6be506a5..1e6bd5f97690 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -172,6 +172,15 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t for (const name in envs) process.env[name] ??= envs[name] + + // don't watch files in run mode + if (!options.watch) { + viteConfig.server.watch = { + persistent: false, + depth: 0, + ignored: ['**/*'], + } + } }, async configureServer(server) { try { From 8b5012301478dd6d0de9fe84261883106b756f3a Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 16 Jan 2023 15:54:03 +0100 Subject: [PATCH 30/30] docs: split API into separate pages (#2635) * docs: split API into separate pages * Apply suggestions from code review Co-authored-by: Anjorin Damilare Co-authored-by: Anjorin Damilare --- docs/.vitepress/config.ts | 22 +- docs/api/assert-type.md | 18 + docs/api/expect-typeof.md | 491 ++++++ docs/api/expect.md | 1273 ++++++++++++++ docs/api/index.md | 2791 +----------------------------- docs/api/mock.md | 272 +++ docs/api/vi.md | 641 +++++++ docs/guide/extending-matchers.md | 17 + 8 files changed, 2791 insertions(+), 2734 deletions(-) create mode 100644 docs/api/assert-type.md create mode 100644 docs/api/expect-typeof.md create mode 100644 docs/api/expect.md create mode 100644 docs/api/mock.md create mode 100644 docs/api/vi.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 190c3286ebbf..fdb8e7ce6a46 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -181,9 +181,29 @@ export default defineConfig({ text: 'API', items: [ { - text: 'API Reference', + text: 'Test API Reference', link: '/api/', }, + { + text: 'Mock Functions', + link: '/api/mock', + }, + { + text: 'Vi Utility', + link: '/api/vi', + }, + { + text: 'Expect', + link: '/api/expect', + }, + { + text: 'ExpectTypeOf', + link: '/api/expect-typeof', + }, + { + text: 'assertType', + link: '/api/assert-type', + }, ], }, { diff --git a/docs/api/assert-type.md b/docs/api/assert-type.md new file mode 100644 index 000000000000..9bfa028d61bb --- /dev/null +++ b/docs/api/assert-type.md @@ -0,0 +1,18 @@ +# assertType + + - **Type:** `(value: T): void` + + You can use this function as an alternative for [`expectTypeOf`](/api/expect-typeof) to easily assert that the argument type is equal to the generic provided. + + ```ts + import { assertType } from 'vitest' + + function concat(a: string, b: string): string + function concat(a: number, b: number): number + function concat(a: string | number, b: string | number): string | number + + assertType(concat('a', 'b')) + assertType(concat(1, 2)) + // @ts-expect-error wrong types + assertType(concat('a', 2)) + ``` diff --git a/docs/api/expect-typeof.md b/docs/api/expect-typeof.md new file mode 100644 index 000000000000..c73ec85a518c --- /dev/null +++ b/docs/api/expect-typeof.md @@ -0,0 +1,491 @@ +# expectTypeOf + +- **Type:** `(a: unknown) => ExpectTypeOf` + +## not + + - **Type:** `ExpectTypeOf` + + You can negate all assertions, using `.not` property. + +## toEqualTypeOf + + - **Type:** `(expected: T) => void` + + This matcher will check if the types are fully equal to each other. This matcher will not fail if two objects have different values, but the same type. It will fail however if an object is missing a property. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: number }>() + expectTypeOf({ a: 1 }).toEqualTypeOf({ a: 1 }) + expectTypeOf({ a: 1 }).toEqualTypeOf({ a: 2 }) + expectTypeOf({ a: 1, b: 1 }).not.toEqualTypeOf<{ a: number }>() + ``` + +## toMatchTypeOf + + - **Type:** `(expected: T) => void` + + This matcher checks if expect type extends provided type. It is different from `toEqual` and is more similar to [expect's](/api/expect) `toMatchObject()`. With this matcher, you can check if an object “matches” a type. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf({ a: 1, b: 1 }).toMatchTypeOf({ a: 1 }) + expectTypeOf().toMatchTypeOf() + expectTypeOf().not.toMatchTypeOf() + ``` + +## extract + + - **Type:** `ExpectTypeOf` + + You can use `.extract` to narrow down types for further testing. + + ```ts + import { expectTypeOf } from 'vitest' + + type ResponsiveProp = T | T[] | { xs?: T; sm?: T; md?: T } + const getResponsiveProp = (_props: T): ResponsiveProp => ({}) + interface CSSProperties { margin?: string; padding?: string } + + const cssProperties: CSSProperties = { margin: '1px', padding: '2px' } + + expectTypeOf(getResponsiveProp(cssProperties)) + .extract<{ xs?: any }>() // extracts the last type from a union + .toEqualTypeOf<{ xs?: CSSProperties; sm?: CSSProperties; md?: CSSProperties }>() + + expectTypeOf(getResponsiveProp(cssProperties)) + .extract() // extracts an array from a union + .toEqualTypeOf() + ``` + + ::: warning + If no type is found in the union, `.extract` will return `never`. + ::: + +## exclude + + - **Type:** `ExpectTypeOf` + + You can use `.exclude` to remove types from a union for further testing. + + ```ts + import { expectTypeOf } from 'vitest' + + type ResponsiveProp = T | T[] | { xs?: T; sm?: T; md?: T } + const getResponsiveProp = (_props: T): ResponsiveProp => ({}) + interface CSSProperties { margin?: string; padding?: string } + + const cssProperties: CSSProperties = { margin: '1px', padding: '2px' } + + expectTypeOf(getResponsiveProp(cssProperties)) + .exclude() + .exclude<{ xs?: unknown }>() // or just .exclude() + .toEqualTypeOf() + ``` + + ::: warning + If no type is found in the union, `.exclude` will return `never`. + ::: + +## returns + + - **Type:** `ExpectTypeOf` + + You can use `.returns` to extract return value of a function type. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(() => {}).returns.toBeVoid() + expectTypeOf((a: number) => [a, a]).returns.toEqualTypeOf([1, 2]) + ``` + + ::: warning + If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. + ::: + +## parameters + + - **Type:** `ExpectTypeOf` + + You can extract function arguments with `.parameters` to perform assertions on its value. Parameters are returned as an array. + + ```ts + import { expectTypeOf } from 'vitest' + + type NoParam = () => void + type HasParam = (s: string) => void + + expectTypeOf().parameters.toEqualTypeOf<[]>() + expectTypeOf().parameters.toEqualTypeOf<[string]>() + ``` + + ::: warning + If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. + ::: + + ::: tip + You can also use [`.toBeCallableWith`](#tobecallablewith) matcher as a more expressive assertion. + ::: + +## parameter + + - **Type:** `(nth: number) => ExpectTypeOf` + + You can extract a certain function argument with `.parameter(number)` call to perform other assertions on it. + + ```ts + import { expectTypeOf } from 'vitest' + + const foo = (a: number, b: string) => [a, b] + + expectTypeOf(foo).parameter(0).toBeNumber() + expectTypeOf(foo).parameter(1).toBeString() + ``` + + ::: warning + If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. + ::: + +## constructorParameters + + - **Type:** `ExpectTypeOf` + + You can extract constructor parameters as an array of values and perform assertions on them with this method. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(Date).constructorParameters.toEqualTypeOf<[] | [string | number | Date]>() + ``` + + ::: warning + If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. + ::: + + ::: tip + You can also use [`.toBeConstructibleWith`](#tobeconstructiblewith) matcher as a more expressive assertion. + ::: + +## instance + + - **Type:** `ExpectTypeOf` + + This property gives access to matchers that can be performed on an instance of the provided class. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(Date).instance.toHaveProperty('toISOString') + ``` + + ::: warning + If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. + ::: + +## items + + - **Type:** `ExpectTypeOf` + + You can get array item type with `.items` to perform further assertions. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf([1, 2, 3]).items.toEqualTypeOf() + expectTypeOf([1, 2, 3]).items.not.toEqualTypeOf() + ``` + +## resolves + + - **Type:** `ExpectTypeOf` + + This matcher extracts resolved value of a `Promise`, so you can perform other assertions on it. + + ```ts + import { expectTypeOf } from 'vitest' + + const asyncFunc = async () => 123 + + expectTypeOf(asyncFunc).returns.resolves.toBeNumber() + expectTypeOf(Promise.resolve('string')).resolves.toBeString() + ``` + + ::: warning + If used on a non-promise type, it will return `never`, so you won't be able to chain it with other matchers. + ::: + +## guards + + - **Type:** `ExpectTypeOf` + + This matcher extracts guard value (e.g., `v is number`), so you can perform assertions on it. + + ```ts + import { expectTypeOf } from 'vitest' + + const isString = (v: any): v is string => typeof v === 'string' + expectTypeOf(isString).guards.toBeString() + ``` + + ::: warning + Returns `never`, if the value is not a guard function, so you won't be able to chain it with other matchers. + ::: + +## asserts + + - **Type:** `ExpectTypeOf` + + This matcher extracts assert value (e.g., `assert v is number`), so you can perform assertions on it. + + ```ts + import { expectTypeOf } from 'vitest' + + const assertNumber = (v: any): asserts v is number => { + if (typeof v !== 'number') + throw new TypeError('Nope !') + } + + expectTypeOf(assertNumber).asserts.toBeNumber() + ``` + + ::: warning + Returns `never`, if the value is not an assert function, so you won't be able to chain it with other matchers. + ::: + +## toBeAny + + - **Type:** `() => void` + + With this matcher you can check, if provided type is `any` type. If the type is too specific, the test will fail. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf().toBeAny() + expectTypeOf({} as any).toBeAny() + expectTypeOf('string').not.toBeAny() + ``` + +## toBeUnknown + + - **Type:** `() => void` + + This matcher checks, if provided type is `unknown` type. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf().toBeUnknown() + expectTypeOf({} as unknown).toBeUnknown() + expectTypeOf('string').not.toBeUnknown() + ``` + +## toBeNever + + - **Type:** `() => void` + + This matcher checks, if provided type is a `never` type. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf().toBeNever() + expectTypeOf((): never => {}).returns.toBeNever() + ``` + +## toBeFunction + + - **Type:** `() => void` + + This matcher checks, if provided type is a `functon`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(42).not.toBeFunction() + expectTypeOf((): never => {}).toBeFunction() + ``` + +## toBeObject + + - **Type:** `() => void` + + This matcher checks, if provided type is an `object`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(42).not.toBeObject() + expectTypeOf({}).toBeObject() + ``` + +## toBeArray + + - **Type:** `() => void` + + This matcher checks, if provided type is `Array`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(42).not.toBeArray() + expectTypeOf([]).toBeArray() + expectTypeOf([1, 2]).toBeArray() + expectTypeOf([{}, 42]).toBeArray() + ``` + +## toBeString + + - **Type:** `() => void` + + This matcher checks, if provided type is a `string`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(42).not.toBeString() + expectTypeOf('').toBeString() + expectTypeOf('a').toBeString() + ``` + +## toBeBoolean + + - **Type:** `() => void` + + This matcher checks, if provided type is `boolean`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(42).not.toBeBoolean() + expectTypeOf(true).toBeBoolean() + expectTypeOf().toBeBoolean() + ``` + +## toBeVoid + + - **Type:** `() => void` + + This matcher checks, if provided type is `void`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(() => {}).returns.toBeVoid() + expectTypeOf().toBeVoid() + ``` + +## toBeSymbol + + - **Type:** `() => void` + + This matcher checks, if provided type is a `symbol`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(Symbol(1)).toBeSymbol() + expectTypeOf().toBeSymbol() + ``` + +## toBeNull + + - **Type:** `() => void` + + This matcher checks, if provided type is `null`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(null).toBeNull() + expectTypeOf().toBeNull() + expectTypeOf(undefined).not.toBeNull() + ``` + +## toBeUndefined + + - **Type:** `() => void` + + This matcher checks, if provided type is `undefined`. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(undefined).toBeUndefined() + expectTypeOf().toBeUndefined() + expectTypeOf(null).not.toBeUndefined() + ``` + +## toBeNullable + + - **Type:** `() => void` + + This matcher checks, if you can use `null` or `undefined` with provided type. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf<1 | undefined>().toBeNullable() + expectTypeOf<1 | null>().toBeNullable() + expectTypeOf<1 | undefined | null>().toBeNullable() + ``` + +## toBeCallableWith + + - **Type:** `() => void` + + This matcher ensures you can call provided function with a set of parameters. + + ```ts + import { expectTypeOf } from 'vitest' + + type NoParam = () => void + type HasParam = (s: string) => void + + expectTypeOf().toBeCallableWith() + expectTypeOf().toBeCallableWith('some string') + ``` + + ::: warning + If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. + ::: + +## toBeConstructibleWith + + - **Type:** `() => void` + + This matcher ensures you can create a new instance with a set of constructor parameters. + + ```ts + import { expectTypeOf } from 'vitest' + + expectTypeOf(Date).toBeConstructibleWith(new Date()) + expectTypeOf(Date).toBeConstructibleWith('01-01-2000') + ``` + + ::: warning + If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. + ::: + +## toHaveProperty + + - **Type:** `(property: K) => ExpectTypeOf` + + This matcher checks if a property exists on the provided object. If it exists, it also returns the same set of matchers for the type of this property, so you can chain assertions one after another. + + ```ts + import { expectTypeOf } from 'vitest' + + const obj = { a: 1, b: '' } + + expectTypeOf(obj).toHaveProperty('a') + expectTypeOf(obj).not.toHaveProperty('c') + + expectTypeOf(obj).toHaveProperty('a').toBeNumber() + expectTypeOf(obj).toHaveProperty('b').toBeString() + expectTypeOf(obj).toHaveProperty('a').not.toBeString() + ``` diff --git a/docs/api/expect.md b/docs/api/expect.md new file mode 100644 index 000000000000..5bcb3c3a8fa6 --- /dev/null +++ b/docs/api/expect.md @@ -0,0 +1,1273 @@ +# expect + +The following types are used in the type signatures below + +```ts +type Awaitable = T | PromiseLike +``` + + `expect` is used to create assertions. In this context `assertions` are functions that can be called to assert a statement. Vitest provides `chai` assertions by default and also `Jest` compatible assertions build on top of `chai`. + + For example, this code asserts that an `input` value is equal to `2`. If it's not, the assertion will throw an error, and the test will fail. + + ```ts + import { expect } from 'vitest' + + const input = Math.sqrt(4) + + expect(input).to.equal(2) // chai API + expect(input).toBe(2) // jest API + ``` + + Technically this example doesn't use [`test`](/api/#test) function, so in the console you will see Nodejs error instead of Vitest output. To learn more about `test`, please read [Test API Reference](/api/). + + Also, `expect` can be used statically to access matchers functions, described later, and more. + +::: warning +`expect` has no effect on testing types, if the expression doesn't have a type error. If you want to use Vitest as [type checker](/guide/testing-types), use [`expectTypeOf`](/api/expect-typeof) or [`assertType`](/api/assert-type). +::: + +## not + + Using `not` will negate the assertion. For example, this code asserts that an `input` value is not equal to `2`. If it's equal, the assertion will throw an error, and the test will fail. + + ```ts + import { expect, test } from 'vitest' + + const input = Math.sqrt(16) + + expect(input).not.to.equal(2) // chai API + expect(input).not.toBe(2) // jest API + ``` + +## toBe + +- **Type:** `(value: any) => Awaitable` + + `toBe` can be used to assert if primitives are equal or that objects share the same reference. It is equivalent of calling `expect(Object.is(3, 3)).toBe(true)`. If the objects are not the same, but you want to check if their structures are identical, you can use [`toEqual`](#toequal). + + For example, the code below checks if the trader has 13 apples. + + ```ts + import { expect, test } from 'vitest' + + const stock = { + type: 'apples', + count: 13, + } + + test('stock has 13 apples', () => { + expect(stock.type).toBe('apples') + expect(stock.count).toBe(13) + }) + + test('stocks are the same', () => { + const refStock = stock // same reference + + expect(stock).toBe(refStock) + }) + ``` + + Try not to use `toBe` with floating-point numbers. Since JavaScript rounds them, `0.1 + 0.2` is not strictly `0.3`. To reliably assert floating-point numbers, use [`toBeCloseTo`](#tobecloseto) assertion. + +## toBeCloseTo + +- **Type:** `(value: number, numDigits?: number) => Awaitable` + + Use `toBeCloseTo` to compare floating-point numbers. The optional `numDigits` argument limits the number of digits to check _after_ the decimal point. For example: + + ```ts + import { expect, test } from 'vitest' + + test.fails('decimals are not equal in javascript', () => { + expect(0.2 + 0.1).toBe(0.3) // 0.2 + 0.1 is 0.30000000000000004 + }) + + test('decimals are rounded to 5 after the point', () => { + // 0.2 + 0.1 is 0.30000 | "000000000004" removed + expect(0.2 + 0.1).toBeCloseTo(0.3, 5) + // nothing from 0.30000000000000004 is removed + expect(0.2 + 0.1).not.toBeCloseTo(0.3, 50) + }) + ``` + +## toBeDefined + +- **Type:** `() => Awaitable` + + `toBeDefined` asserts that the value is not equal to `undefined`. Useful use case would be to check if function _returned_ anything. + + ```ts + import { expect, test } from 'vitest' + + const getApples = () => 3 + + test('function returned something', () => { + expect(getApples()).toBeDefined() + }) + ``` + +## toBeUndefined + +- **Type:** `() => Awaitable` + + Opposite of `toBeDefined`, `toBeUndefined` asserts that the value _is_ equal to `undefined`. Useful use case would be to check if function hasn't _returned_ anything. + + ```ts + import { expect, test } from 'vitest' + + function getApplesFromStock(stock) { + if (stock === 'Bill') + return 13 + } + + test('mary doesn\'t have a stock', () => { + expect(getApplesFromStock('Mary')).toBeUndefined() + }) + ``` + +## toBeTruthy + +- **Type:** `() => Awaitable` + + `toBeTruthy` asserts that the value is true when converted to boolean. Useful if you don't care for the value, but just want to know it can be converted to `true`. + + For example, having this code you don't care for the return value of `stocks.getInfo` - it maybe a complex object, a string, or anything else. The code will still work. + + ```ts + import { Stocks } from './stocks' + const stocks = new Stocks() + stocks.sync('Bill') + if (stocks.getInfo('Bill')) + stocks.sell('apples', 'Bill') + ``` + + So if you want to test that `stocks.getInfo` will be truthy, you could write: + + ```ts + import { expect, test } from 'vitest' + import { Stocks } from './stocks' + const stocks = new Stocks() + + test('if we know Bill stock, sell apples to him', () => { + stocks.sync('Bill') + expect(stocks.getInfo('Bill')).toBeTruthy() + }) + ``` + + Everything in JavaScript is truthy, except `false`, `0`, `''`, `null`, `undefined`, and `NaN`. + +## toBeFalsy + +- **Type:** `() => Awaitable` + + `toBeFalsy` asserts that the value is false when converted to boolean. Useful if you don't care for the value, but just want to know if it can be converted to `false`. + + For example, having this code you don't care for the return value of `stocks.stockFailed` - it may return any falsy value, but the code will still work. + + ```ts + import { Stocks } from './stocks' + const stocks = new Stocks() + stocks.sync('Bill') + if (!stocks.stockFailed('Bill')) + stocks.sell('apples', 'Bill') + ``` + + So if you want to test that `stocks.stockFailed` will be falsy, you could write: + + ```ts + import { expect, test } from 'vitest' + import { Stocks } from './stocks' + const stocks = new Stocks() + + test('if Bill stock hasn\'t failed, sell apples to him', () => { + stocks.syncStocks('Bill') + expect(stocks.stockFailed('Bill')).toBeFalsy() + }) + ``` + + Everything in JavaScript is truthy, except `false`, `0`, `''`, `null`, `undefined`, and `NaN`. + +## toBeNull + +- **Type:** `() => Awaitable` + + `toBeNull` simply asserts if something is `null`. Alias for `.toBe(null)`. + + ```ts + import { expect, test } from 'vitest' + + function apples() { + return null + } + + test('we don\'t have apples', () => { + expect(apples()).toBeNull() + }) + ``` + +## toBeNaN + +- **Type:** `() => Awaitable` + + `toBeNaN` simply asserts if something is `NaN`. Alias for `.toBe(NaN)`. + + ```ts + import { expect, test } from 'vitest' + + let i = 0 + + function getApplesCount() { + i++ + return i > 1 ? NaN : i + } + + test('getApplesCount has some unusual side effects...', () => { + expect(getApplesCount()).not.toBeNaN() + expect(getApplesCount()).toBeNaN() + }) + ``` + +## toBeTypeOf + +- **Type:** `(c: 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined') => Awaitable` + + `toBeTypeOf` asserts if an actual value is of type of received type. + + ```ts + import { expect, test } from 'vitest' + const actual = 'stock' + + test('stock is type of string', () => { + expect(actual).toBeTypeOf('string') + }) + ``` + +## toBeInstanceOf + +- **Type:** `(c: any) => Awaitable` + + `toBeInstanceOf` asserts if an actual value is instance of received class. + + ```ts + import { expect, test } from 'vitest' + import { Stocks } from './stocks' + const stocks = new Stocks() + + test('stocks are instance of Stocks', () => { + expect(stocks).toBeInstanceOf(Stocks) + }) + ``` + +## toBeGreaterThan + +- **Type:** `(n: number | bigint) => Awaitable` + + `toBeGreaterThan` asserts if actual value is greater than received one. Equal values will fail the test. + + ```ts + import { expect, test } from 'vitest' + import { getApples } from './stock' + + test('have more then 10 apples', () => { + expect(getApples()).toBeGreaterThan(10) + }) + ``` + +## toBeGreaterThanOrEqual + +- **Type:** `(n: number | bigint) => Awaitable` + + `toBeGreaterThanOrEqual` asserts if actual value is greater than received one or equal to it. + + ```ts + import { expect, test } from 'vitest' + import { getApples } from './stock' + + test('have 11 apples or more', () => { + expect(getApples()).toBeGreaterThanOrEqual(11) + }) + ``` + +## toBeLessThan + +- **Type:** `(n: number | bigint) => Awaitable` + + `toBeLessThan` asserts if actual value is less than received one. Equal values will fail the test. + + ```ts + import { expect, test } from 'vitest' + import { getApples } from './stock' + + test('have less then 20 apples', () => { + expect(getApples()).toBeLessThan(20) + }) + ``` + +## toBeLessThanOrEqual + +- **Type:** `(n: number | bigint) => Awaitable` + + `toBeLessThanOrEqual` asserts if actual value is less than received one or equal to it. + + ```ts + import { expect, test } from 'vitest' + import { getApples } from './stock' + + test('have 11 apples or less', () => { + expect(getApples()).toBeLessThanOrEqual(11) + }) + ``` + +## toEqual + +- **Type:** `(received: any) => Awaitable` + + `toEqual` asserts if actual value is equal to received one or has the same structure, if it is an object (compares them recursively). You can see the difference between `toEqual` and [`toBe`](#tobe) in this example: + + ```ts + import { expect, test } from 'vitest' + + const stockBill = { + type: 'apples', + count: 13, + } + + const stockMary = { + type: 'apples', + count: 13, + } + + test('stocks have the same properties', () => { + expect(stockBill).toEqual(stockMary) + }) + + test('stocks are not the same', () => { + expect(stockBill).not.toBe(stockMary) + }) + ``` + + :::warning + A _deep equality_ will not be performed for `Error` objects. To test if something was thrown, use [`toThrowError`](#tothrowerror) assertion. + ::: + +## toStrictEqual + +- **Type:** `(received: any) => Awaitable` + + `toStrictEqual` asserts if the actual value is equal to the received one or has the same structure if it is an object (compares them recursively), and of the same type. + + Differences from [`.toEqual`](#toequal): + + - Keys with `undefined` properties are checked. e.g. `{a: undefined, b: 2}` does not match `{b: 2}` when using `.toStrictEqual`. + - Array sparseness is checked. e.g. `[, 1]` does not match `[undefined, 1]` when using `.toStrictEqual`. + - Object types are checked to be equal. e.g. A class instance with fields `a` and` b` will not equal a literal object with fields `a` and `b`. + + ```ts + import { expect, test } from 'vitest' + + class Stock { + constructor(type) { + this.type = type + } + } + + test('structurally the same, but semantically different', () => { + expect(new Stock('apples')).toEqual({ type: 'apples' }) + expect(new Stock('apples')).not.toStrictEqual({ type: 'apples' }) + }) + ``` + +## toContain + +- **Type:** `(received: string) => Awaitable` + + `toContain` asserts if the actual value is in an array. `toContain` can also check whether a string is a substring of another string. + + ```ts + import { expect, test } from 'vitest' + import { getAllFruits } from './stock' + + test('the fruit list contains orange', () => { + expect(getAllFruits()).toContain('orange') + }) + ``` + +## toContainEqual + +- **Type:** `(received: any) => Awaitable` + + `toContainEqual` asserts if an item with a specific structure and values is contained in an array. + It works like [`toEqual`](#toequal) inside for each element. + + ```ts + import { expect, test } from 'vitest' + import { getFruitStock } from './stock' + + test('apple available', () => { + expect(getFruitStock()).toContainEqual({ fruit: 'apple', count: 5 }) + }) + ``` + +## toHaveLength + +- **Type:** `(received: number) => Awaitable` + + `toHaveLength` asserts if an object has a `.length` property and it is set to a certain numeric value. + + ```ts + import { expect, test } from 'vitest' + + test('toHaveLength', () => { + expect('abc').toHaveLength(3) + expect([1, 2, 3]).toHaveLength(3) + + expect('').not.toHaveLength(3) // doesn't have .length of 3 + expect({ length: 3 }).toHaveLength(3) + }) + ``` + +## toHaveProperty + +- **Type:** `(key: any, received?: any) => Awaitable` + + `toHaveProperty` asserts if a property at provided reference `key` exists for an object. + + You can provide an optional value argument also known as deep equality, like the `toEqual` matcher to compare the received property value. + + ```ts + import { expect, test } from 'vitest' + + const invoice = { + 'isActive': true, + 'P.O': '12345', + 'customer': { + first_name: 'John', + last_name: 'Doe', + location: 'China', + }, + 'total_amount': 5000, + 'items': [ + { + type: 'apples', + quantity: 10, + }, + { + type: 'oranges', + quantity: 5, + }, + ], + } + + test('John Doe Invoice', () => { + expect(invoice).toHaveProperty('isActive') // assert that the key exists + expect(invoice).toHaveProperty('total_amount', 5000) // assert that the key exists and the value is equal + + expect(invoice).not.toHaveProperty('account') // assert that this key does not exist + + // Deep referencing using dot notation + expect(invoice).toHaveProperty('customer.first_name') + expect(invoice).toHaveProperty('customer.last_name', 'Doe') + expect(invoice).not.toHaveProperty('customer.location', 'India') + + // Deep referencing using an array containing the key + expect(invoice).toHaveProperty('items[0].type', 'apples') + expect(invoice).toHaveProperty('items.0.type', 'apples') // dot notation also works + + // Wrap your key in an array to avoid the key from being parsed as a deep reference + expect(invoice).toHaveProperty(['P.O'], '12345') + }) + ``` + +## toMatch + +- **Type:** `(received: string | regexp) => Awaitable` + + `toMatch` asserts if a string matches a regular expression or a string. + + ```ts + import { expect, test } from 'vitest' + + test('top fruits', () => { + expect('top fruits include apple, orange and grape').toMatch(/apple/) + expect('applefruits').toMatch('fruit') // toMatch also accepts a string + }) + ``` + +## toMatchObject + +- **Type:** `(received: object | array) => Awaitable` + + `toMatchObject` asserts if an object matches a subset of the properties of an object. + + You can also pass an array of objects. This is useful if you want to check that two arrays match in their number of elements, as opposed to `arrayContaining`, which allows for extra elements in the received array. + + ```ts + import { expect, test } from 'vitest' + + const johnInvoice = { + isActive: true, + customer: { + first_name: 'John', + last_name: 'Doe', + location: 'China', + }, + total_amount: 5000, + items: [ + { + type: 'apples', + quantity: 10, + }, + { + type: 'oranges', + quantity: 5, + }, + ], + } + + const johnDetails = { + customer: { + first_name: 'John', + last_name: 'Doe', + location: 'China', + }, + } + + test('invoice has john personal details', () => { + expect(johnInvoice).toMatchObject(johnDetails) + }) + + test('the number of elements must match exactly', () => { + // Assert that an array of object matches + expect([{ foo: 'bar' }, { baz: 1 }]).toMatchObject([ + { foo: 'bar' }, + { baz: 1 }, + ]) + }) + ``` + +## toThrowError + +- **Type:** `(received: any) => Awaitable` + +- **Alias:** `toThrow` + + `toThrowError` asserts if a function throws an error when it is called. + + You can provide an optional argument to test that a specific error is thrown: + + - regular expression: error message matches the pattern + - string: error message includes the substring + + :::tip + You must wrap the code in a function, otherwise the error will not be caught, and test will fail. + ::: + + For example, if we want to test that `getFruitStock('pineapples')` throws, we could write: + + ```ts + import { expect, test } from 'vitest' + + function getFruitStock(type) { + if (type === 'pineapples') + throw new DiabetesError('Pineapples is not good for people with diabetes') + + // Do some other stuff + } + + test('throws on pineapples', () => { + // Test that the error message says "diabetes" somewhere: these are equivalent + expect(() => getFruitStock('pineapples')).toThrowError(/diabetes/) + expect(() => getFruitStock('pineapples')).toThrowError('diabetes') + + // Test the exact error message + expect(() => getFruitStock('pineapples')).toThrowError( + /^Pineapples is not good for people with diabetes$/, + ) + }) + ``` + + :::tip + To test async functions, use in combination with [rejects](#rejects). + + ```js + const getAsyncFruitStock = () => Promise.reject(new Error('empty')) + + test('throws on pineapples', async () => { + await expect(() => getAsyncFruitStock()).rejects.toThrowError('empty') + }) + ``` + ::: + +## toMatchSnapshot + +- **Type:** `(shape?: Partial | string, message?: string) => void` + + This ensures that a value matches the most recent snapshot. + + You can provide an optional `hint` string argument that is appended to the test name. Although Vitest always appends a number at the end of a snapshot name, short descriptive hints might be more useful than numbers to differentiate multiple snapshots in a single it or test block. Vitest sorts snapshots by name in the corresponding `.snap` file. + + :::tip + When snapshot mismatch and causing the test failing, if the mismatch is expected, you can press `u` key to update the snapshot for once. Or you can pass `-u` or `--update` CLI options to make Vitest always update the tests. + ::: + + ```ts + import { expect, test } from 'vitest' + + test('matches snapshot', () => { + const data = { foo: new Set(['bar', 'snapshot']) } + expect(data).toMatchSnapshot() + }) + ``` + + You can also provide a shape of an object, if you are testing just a shape of an object, and don't need it to be 100% compatible: + + ```ts + import { expect, test } from 'vitest' + + test('matches snapshot', () => { + const data = { foo: new Set(['bar', 'snapshot']) } + expect(data).toMatchSnapshot({ foo: expect.any(Set) }) + }) + ``` + +## toMatchInlineSnapshot + +- **Type:** `(shape?: Partial | string, snapshot?: string, message?: string) => void` + + This ensures that a value matches the most recent snapshot. + + Vitest adds and updates the inlineSnapshot string argument to the matcher in the test file (instead of an external `.snap` file). + + ```ts + import { expect, test } from 'vitest' + + test('matches inline snapshot', () => { + const data = { foo: new Set(['bar', 'snapshot']) } + // Vitest will update following content when updating the snapshot + expect(data).toMatchInlineSnapshot(` + { + "foo": Set { + "bar", + "snapshot", + }, + } + `) + }) + ``` + + You can also provide a shape of an object, if you are testing just a shape of an object, and don't need it to be 100% compatible: + + ```ts + import { expect, test } from 'vitest' + + test('matches snapshot', () => { + const data = { foo: new Set(['bar', 'snapshot']) } + expect(data).toMatchInlineSnapshot( + { foo: expect.any(Set) }, + ` + { + "foo": Any, + } + ` + ) + }) + ``` + + +## toThrowErrorMatchingSnapshot + +- **Type:** `(message?: string) => void` + + The same as [`toMatchSnapshot`](#tomatchsnapshot), but expects the same value as [`toThrowError`](#tothrowerror). + + If the function throws an `Error`, the snapshot will be the error message. Otherwise, snapshot will be the value thrown by the function. + +## toThrowErrorMatchingInlineSnapshot + +- **Type:** `(snapshot?: string, message?: string) => void` + + The same as [`toMatchInlineSnapshot`](#tomatchinlinesnapshot), but expects the same value as [`toThrowError`](#tothrowerror). + + If the function throws an `Error`, the snapshot will be the error message. Otherwise, snapshot will be the value thrown by the function. + +## toHaveBeenCalled + +- **Type:** `() => Awaitable` + + This assertion is useful for testing that a function has been called. Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + const market = { + buy(subject: string, amount: number) { + // ... + }, + } + + test('spy function', () => { + const buySpy = vi.spyOn(market, 'buy') + + expect(buySpy).not.toHaveBeenCalled() + + market.buy('apples', 10) + + expect(buySpy).toHaveBeenCalled() + }) + ``` + +## toHaveBeenCalledTimes + + - **Type**: `(amount: number) => Awaitable` + + This assertion checks if a function was called a certain amount of times. Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + const market = { + buy(subject: string, amount: number) { + // ... + }, + } + + test('spy function called two times', () => { + const buySpy = vi.spyOn(market, 'buy') + + market.buy('apples', 10) + market.buy('apples', 20) + + expect(buySpy).toHaveBeenCalledTimes(2) + }) + ``` + +## toHaveBeenCalledWith + + - **Type**: `(...args: any[]) => Awaitable` + + This assertion checks if a function was called at least once with certain parameters. Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + const market = { + buy(subject: string, amount: number) { + // ... + }, + } + + test('spy function', () => { + const buySpy = vi.spyOn(market, 'buy') + + market.buy('apples', 10) + market.buy('apples', 20) + + expect(buySpy).toHaveBeenCalledWith('apples', 10) + expect(buySpy).toHaveBeenCalledWith('apples', 20) + }) + ``` + +## toHaveBeenLastCalledWith + + - **Type**: `(...args: any[]) => Awaitable` + + This assertion checks if a function was called with certain parameters at it's last invocation. Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + const market = { + buy(subject: string, amount: number) { + // ... + }, + } + + test('spy function', () => { + const buySpy = vi.spyOn(market, 'buy') + + market.buy('apples', 10) + market.buy('apples', 20) + + expect(buySpy).not.toHaveBeenLastCalledWith('apples', 10) + expect(buySpy).toHaveBeenLastCalledWith('apples', 20) + }) + ``` + +## toHaveBeenNthCalledWith + + - **Type**: `(time: number, ...args: any[]) => Awaitable` + + This assertion checks if a function was called with certain parameters at the certain time. The count starts at 1. So, to check the second entry, you would write `.toHaveBeenNthCalledWith(2, ...)`. + + Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + const market = { + buy(subject: string, amount: number) { + // ... + }, + } + + test('first call of spy function called with right params', () => { + const buySpy = vi.spyOn(market, 'buy') + + market.buy('apples', 10) + market.buy('apples', 20) + + expect(buySpy).toHaveBeenNthCalledWith(1, 'apples', 10) + }) + ``` + +## toHaveReturned + + - **Type**: `() => Awaitable` + + This assertion checks if a function has successfully returned a value at least once (i.e., did not throw an error). Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + const getApplesPrice = (amount: number) => { + const PRICE = 10 + return amount * PRICE + } + + test('spy function returned a value', () => { + const getPriceSpy = vi.fn(getApplesPrice) + + const price = getPriceSpy(10) + + expect(price).toBe(100) + expect(getPriceSpy).toHaveReturned() + }) + ``` + +## toHaveReturnedTimes + + - **Type**: `(amount: number) => Awaitable` + + This assertion checks if a function has successfully returned a value exact amount of times (i.e., did not throw an error). Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + test('spy function returns a value two times', () => { + const sell = vi.fn((product: string) => ({ product })) + + sell('apples') + sell('bananas') + + expect(sell).toHaveReturnedTimes(2) + }) + ``` + +## toHaveReturnedWith + + - **Type**: `(returnValue: any) => Awaitable` + + You can call this assertion to check if a function has successfully returned a value with certain parameters at least once. Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + test('spy function returns a product', () => { + const sell = vi.fn((product: string) => ({ product })) + + sell('apples') + + expect(sell).toHaveReturnedWith({ product: 'apples' }) + }) + ``` + +## toHaveLastReturnedWith + + - **Type**: `(returnValue: any) => Awaitable` + + You can call this assertion to check if a function has successfully returned a value with certain parameters on it's last invoking. Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + test('spy function returns bananas on a last call', () => { + const sell = vi.fn((product: string) => ({ product })) + + sell('apples') + sell('bananas') + + expect(sell).toHaveLastReturnedWith({ product: 'bananas' }) + }) + ``` + +## toHaveNthReturnedWith + + - **Type**: `(time: number, returnValue: any) => Awaitable` + + You can call this assertion to check if a function has successfully returned a value with certain parameters on a certain call. Requires a spy function to be passed to `expect`. + + ```ts + import { expect, test, vi } from 'vitest' + + test('spy function returns bananas on second call', () => { + const sell = vi.fn((product: string) => ({ product })) + + sell('apples') + sell('bananas') + + expect(sell).toHaveNthReturnedWith(2, { product: 'bananas' }) + }) + ``` + +## toSatisfy + + - **Type:** `(predicate: (value: any) => boolean) => Awaitable` + + This assertion checks if a value satisfies a certain predicate. + + ```ts + describe('toSatisfy()', () => { + const isOdd = (value: number) => value % 2 !== 0 + + it('pass with 0', () => { + expect(1).toSatisfy(isOdd) + }) + + it('pass with negotiation', () => { + expect(2).not.toSatisfy(isOdd) + }) + }) + ``` + +## resolves + +- **Type:** `Promisify` + + `resolves` is intended to remove boilerplate when asserting asynchronous code. Use it to unwrap value from the pending promise and assert its value with usual assertions. If the promise rejects, the assertion will fail. + + It returns the same `Assertions` object, but all matchers now return `Promise`, so you would need to `await` it. Also works with `chai` assertions. + + For example, if you have a function, that makes an API call and returns some data, you may use this code to assert its return value: + + ```ts + import { expect, test } from 'vitest' + + async function buyApples() { + return fetch('/buy/apples').then(r => r.json()) + } + + test('buyApples returns new stock id', async () => { + // toEqual returns a promise now, so you HAVE to await it + await expect(buyApples()).resolves.toEqual({ id: 1 }) // jest API + await expect(buyApples()).resolves.to.equal({ id: 1 }) // chai API + }) + ``` + + :::warning + If the assertion is not awaited, then you will have a false-positive test that will pass every time. To make sure that assertions are actually called, you may use [`expect.assertions(number)`](#expect-assertions). + ::: + +## rejects + +- **Type:** `Promisify` + + `rejects` is intended to remove boilerplate when asserting asynchronous code. Use it to unwrap reason why the promise was rejected, and assert its value with usual assertions. If the promise successfully resolves, the assertion will fail. + + It returns the same `Assertions` object, but all matchers now return `Promise`, so you would need to `await` it. Also works with `chai` assertions. + + For example, if you have a function that fails when you call it, you may use this code to assert the reason: + + ```ts + import { expect, test } from 'vitest' + + async function buyApples(id) { + if (!id) + throw new Error('no id') + } + + test('buyApples throws an error when no id provided', async () => { + // toThrow returns a promise now, so you HAVE to await it + await expect(buyApples()).rejects.toThrow('no id') + }) + ``` + + :::warning + If the assertion is not awaited, then you will have a false-positive test that will pass every time. To make sure that assertions were actually called, you can use [`expect.assertions(number)`](#expect-assertions). + ::: + +## expect.assertions + +- **Type:** `(count: number) => void` + + After the test has passed or failed verify that a certain number of assertions was called during a test. A useful case would be to check if an asynchronous code was called. + + For example, if we have a function that asynchronously calls two matchers, we can assert that they were actually called. + + ```ts + import { expect, test } from 'vitest' + + async function doAsync(...cbs) { + await Promise.all( + cbs.map((cb, index) => cb({ index })), + ) + } + + test('all assertions are called', async () => { + expect.assertions(2) + function callback1(data) { + expect(data).toBeTruthy() + } + function callback2(data) { + expect(data).toBeTruthy() + } + + await doAsync(callback1, callback2) + }) + ``` + +## expect.hasAssertions + +- **Type:** `() => void` + + After the test has passed or failed verify that at least one assertion was called during a test. A useful case would be to check if an asynchronous code was called. + + For example, if you have a code that calls a callback, we can make an assertion inside a callback, but the test will always pass if we don't check if an assertion was called. + + ```ts + import { expect, test } from 'vitest' + import { db } from './db' + + const cbs = [] + + function onSelect(cb) { + cbs.push(cb) + } + + // after selecting from db, we call all callbacks + function select(id) { + return db.select({ id }).then((data) => { + return Promise.all( + cbs.map(cb => cb(data)), + ) + }) + } + + test('callback was called', async () => { + expect.hasAssertions() + onSelect((data) => { + // should be called on select + expect(data).toBeTruthy() + }) + // if not awaited, test will fail + // if you don't have expect.hasAssertions(), test will pass + await select(3) + }) + ``` + + + +## expect.anything + +- **Type:** `() => any` + + This asymmetric matcher, when used with equality check, will always return `true`. Useful, if you just want to be sure that the property exist. + + ```ts + import { expect, test } from 'vitest' + + test('object has "apples" key', () => { + expect({ apples: 22 }).toEqual({ apples: expect.anything() }) + }) + ``` + +## expect.any + +- **Type:** `(constructor: unknown) => any` + + This asymmetric matcher, when used with an equality check, will return `true` only if the value is an instance of a specified constructor. Useful, if you have a value that is generated each time, and you only want to know that it exists with a proper type. + + ```ts + import { expect, test } from 'vitest' + import { generateId } from './generators' + + test('"id" is a number', () => { + expect({ id: generateId() }).toEqual({ id: expect.any(Number) }) + }) + ``` + +## expect.arrayContaining + +- **Type:** `(expected: T[]) => any` + + When used with an equality check, this asymmetric matcher will return `true` if the value is an array and contains specified items. + + ```ts + import { expect, test } from 'vitest' + + test('basket includes fuji', () => { + const basket = { + varieties: [ + 'Empire', + 'Fuji', + 'Gala', + ], + count: 3 + } + expect(basket).toEqual({ + count: 3, + varieties: expect.arrayContaining(['Fuji']) + }) + }) + ``` + + :::tip + You can use `expect.not` with this matcher to negate the expected value. + ::: + +## expect.objectContaining + +- **Type:** `(expected: any) => any` + + When used with an equality check, this asymmetric matcher will return `true` if the value has a similar shape. + + ```ts + import { expect, test } from 'vitest' + + test('basket has empire apples', () => { + const basket = { + varieties: [ + { + name: 'Empire', + count: 1, + } + ], + } + expect(basket).toEqual({ + varieties: [ + expect.objectContaining({ name: 'Empire' }), + ] + }) + }) + ``` + + :::tip + You can use `expect.not` with this matcher to negate the expected value. + ::: + +## expect.stringContaining + +- **Type:** `(expected: any) => any` + + When used with an equality check, this asymmetric matcher will return `true` if the value is a string and contains a specified substring. + + ```ts + import { expect, test } from 'vitest' + + test('variety has "Emp" in its name', () => { + const variety = { + name: 'Empire', + count: 1, + } + expect(basket).toEqual({ + name: expect.stringContaining('Emp'), + count: 1, + }) + }) + ``` + + :::tip + You can use `expect.not` with this matcher to negate the expected value. + ::: + +## expect.stringMatching + +- **Type:** `(expected: any) => any` + + When used with an equality check, this asymmetric matcher will return `true` if the value is a string and contains a specified substring or if the string matches a regular expression. + + ```ts + import { expect, test } from 'vitest' + + test('variety ends with "re"', () => { + const variety = { + name: 'Empire', + count: 1, + } + expect(basket).toEqual({ + name: expect.stringMatching(/re$/), + count: 1, + }) + }) + ``` + + :::tip + You can use `expect.not` with this matcher to negate the expected value. + ::: + +## expect.addSnapshotSerializer + +- **Type:** `(plugin: PrettyFormatPlugin) => void` + + This method adds custom serializers that are called when creating a snapshot. This is an advanced feature - if you want to know more, please read a [guide on custom serializers](/guide/snapshot#custom-serializer). + + If you are adding custom serializers, you should call this method inside [`setupFiles`](/config/#setupfiles). This will affect every snapshot. + + :::tip + If you previously used Vue CLI with Jest, you might want to install [jest-serializer-vue](https://www.npmjs.com/package/jest-serializer-vue). Otherwise, your snapshots will be wrapped in a string, which cases `"` to be escaped. + ::: + +## expect.extend + +- **Type:** `(matchers: MatchersObject) => void` + + You can extend default matchers with your own. This function is used to extend the matchers object with custom matchers. + + When you define matchers that way, you also create asymmetric matchers that can be used like `expect.stringContaining`. + + ```ts + import { expect, test } from 'vitest' + + test('custom matchers', () => { + expect.extend({ + toBeFoo: (received, expected) => { + if (received !== 'foo') { + return { + message: () => `expected ${received} to be foo`, + pass: false, + } + } + }, + }) + + expect('foo').toBeFoo() + expect({ foo: 'foo' }).toEqual({ foo: expect.toBeFoo() }) + }) + ``` + + ::: tip + If you want your matchers to appear in every test, you should call this method inside [`setupFiles`](/config/#setupFiles). + ::: + + This function is compatible with Jest's `expect.extend`, so any library that uses it to create custom matchers will work with Vitest. + + If you are using TypeScript, you can extend default Matchers interface with the code bellow: + + ```ts + interface CustomMatchers { + toBeFoo(): R + } + + declare global { + namespace Vi { + interface Assertion extends CustomMatchers {} + interface AsymmetricMatchersContaining extends CustomMatchers {} + } + + // Note: augmenting jest.Matchers interface will also work. + } + ``` + + :::tip + If you want to know more, checkout [guide on extending matchers](/guide/extending-matchers). + ::: \ No newline at end of file diff --git a/docs/api/index.md b/docs/api/index.md index a5f3af069f55..b593c87381ca 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -2,7 +2,7 @@ outline: deep --- -# API Reference +# Test API Reference The following types are used in the type signatures below @@ -653,2790 +653,115 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t You cannot use this syntax, when using Vitest as [type checker](/guide/testing-types). ::: -## expect - -- **Type:** `ExpectStatic & (actual: any) => Assertions` - - `expect` is used to create assertions. In this context `assertions` are functions that can be called to assert a statement. Vitest provides `chai` assertions by default and also `Jest` compatible assertions build on top of `chai`. - - For example, this code asserts that an `input` value is equal to `2`. If it's not, assertion will throw an error, and the test will fail. - - ```ts - import { expect } from 'vitest' - - const input = Math.sqrt(4) - - expect(input).to.equal(2) // chai API - expect(input).toBe(2) // jest API - ``` - - Technically this example doesn't use [`test`](#test) function, so in the console you will see Nodejs error instead of Vitest output. To learn more about `test`, please read [next chapter](#test). - - Also, `expect` can be used statically to access matchers functions, described later, and more. - -::: warning -`expect` has no effect on testing types, if expression doesn't have a type error. If you want to use Vitest as [type checker](/guide/testing-types), use [`expectTypeOf`](#expecttypeof) or [`assertType`](#asserttype). -::: - -### not - - Using `not` will negate the assertion. For example, this code asserts that an `input` value is not equal to `2`. If it's equal, assertion will throw an error, and the test will fail. - - ```ts - import { expect, test } from 'vitest' - - const input = Math.sqrt(16) - - expect(input).not.to.equal(2) // chai API - expect(input).not.toBe(2) // jest API - ``` - -### toBe - -- **Type:** `(value: any) => Awaitable` - - `toBe` can be used to assert if primitives are equal or that objects share the same reference. It is equivalent of calling `expect(Object.is(3, 3)).toBe(true)`. If the objects are not the same, but you want check if their structures are identical, you can use [`toEqual`](#toequal). - - For example, the code below checks if the trader has 13 apples. - - ```ts - import { expect, test } from 'vitest' - - const stock = { - type: 'apples', - count: 13, - } - - test('stock has 13 apples', () => { - expect(stock.type).toBe('apples') - expect(stock.count).toBe(13) - }) - - test('stocks are the same', () => { - const refStock = stock // same reference - - expect(stock).toBe(refStock) - }) - ``` - - Try not to use `toBe` with floating-point numbers. Since JavaScript rounds them, `0.1 + 0.2` is not strictly `0.3`. To reliably assert floating-point numbers, use [`toBeCloseTo`](#tobecloseto) assertion. - -### toBeCloseTo - -- **Type:** `(value: number, numDigits?: number) => Awaitable` - - Use `toBeCloseTo` to compare floating-point numbers. The optional `numDigits` argument limits the number of digits to check _after_ the decimal point. For example: - - ```ts - import { expect, test } from 'vitest' - - test.fails('decimals are not equal in javascript', () => { - expect(0.2 + 0.1).toBe(0.3) // 0.2 + 0.1 is 0.30000000000000004 - }) - - test('decimals are rounded to 5 after the point', () => { - // 0.2 + 0.1 is 0.30000 | "000000000004" removed - expect(0.2 + 0.1).toBeCloseTo(0.3, 5) - // nothing from 0.30000000000000004 is removed - expect(0.2 + 0.1).not.toBeCloseTo(0.3, 50) - }) - ``` - -### toBeDefined - -- **Type:** `() => Awaitable` - - `toBeDefined` asserts that the value is not equal to `undefined`. Useful use case would be to check if function _returned_ anything. - - ```ts - import { expect, test } from 'vitest' - - const getApples = () => 3 - - test('function returned something', () => { - expect(getApples()).toBeDefined() - }) - ``` - -### toBeUndefined - -- **Type:** `() => Awaitable` - - Opposite of `toBeDefined`, `toBeUndefined` asserts that the value _is_ equal to `undefined`. Useful use case would be to check if function hasn't _returned_ anything. - - ```ts - import { expect, test } from 'vitest' - - function getApplesFromStock(stock) { - if (stock === 'Bill') - return 13 - } - - test('mary doesn\'t have a stock', () => { - expect(getApplesFromStock('Mary')).toBeUndefined() - }) - ``` - -### toBeTruthy - -- **Type:** `() => Awaitable` - - `toBeTruthy` asserts that the value is true, when converted to boolean. Useful if you don't care for the value, but just want to know it can be converted to `true`. - - For example having this code you don't care for the return value of `stocks.getInfo` - it maybe complex object, a string or anything else. The code will still work. - - ```ts - import { Stocks } from './stocks' - const stocks = new Stocks() - stocks.sync('Bill') - if (stocks.getInfo('Bill')) - stocks.sell('apples', 'Bill') - ``` - - So if you want to test that `stocks.getInfo` will be truthy, you could write: - - ```ts - import { expect, test } from 'vitest' - import { Stocks } from './stocks' - const stocks = new Stocks() - - test('if we know Bill stock, sell apples to him', () => { - stocks.sync('Bill') - expect(stocks.getInfo('Bill')).toBeTruthy() - }) - ``` - - Everything in JavaScript is truthy, except `false`, `0`, `''`, `null`, `undefined`, and `NaN`. - -### toBeFalsy - -- **Type:** `() => Awaitable` - - `toBeFalsy` asserts that the value is false, when converted to boolean. Useful if you don't care for the value, but just want to know it can be converted to `false`. - - For example having this code you don't care for the return value of `stocks.stockFailed` - it may return any falsy value, but the code will still work. - - ```ts - import { Stocks } from './stocks' - const stocks = new Stocks() - stocks.sync('Bill') - if (!stocks.stockFailed('Bill')) - stocks.sell('apples', 'Bill') - ``` - - So if you want to test that `stocks.stockFailed` will be falsy, you could write: - - ```ts - import { expect, test } from 'vitest' - import { Stocks } from './stocks' - const stocks = new Stocks() - - test('if Bill stock hasn\'t failed, sell apples to him', () => { - stocks.syncStocks('Bill') - expect(stocks.stockFailed('Bill')).toBeFalsy() - }) - ``` - - Everything in JavaScript is truthy, except `false`, `0`, `''`, `null`, `undefined`, and `NaN`. - -### toBeNull - -- **Type:** `() => Awaitable` - - `toBeNull` simply asserts if something is `null`. Alias for `.toBe(null)`. - - ```ts - import { expect, test } from 'vitest' +## Setup and Teardown - function apples() { - return null - } +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. - test('we don\'t have apples', () => { - expect(apples()).toBeNull() - }) - ``` +### beforeEach -### toBeNaN +- **Type:** `beforeEach(fn: () => Awaitable, timeout?: number)` -- **Type:** `() => Awaitable` + Register a callback to be called before each of the tests in the current context runs. + If the function returns a promise, Vitest waits until the promise resolve before running the test. - `toBeNaN` simply asserts if something is `NaN`. Alias for `.toBe(NaN)`. + Optionally, you can pass a timeout (in milliseconds) defining how long to wait before terminating. The default is 5 seconds. ```ts - import { expect, test } from 'vitest' - - let i = 0 - - function getApplesCount() { - i++ - return i > 1 ? NaN : i - } + import { beforeEach } from 'vitest' - test('getApplesCount has some unusual side effects...', () => { - expect(getApplesCount()).not.toBeNaN() - expect(getApplesCount()).toBeNaN() + beforeEach(async () => { + // Clear mocks and add some testing data after before each test run + await stopMocking() + await addUser({ name: 'John' }) }) ``` -### toBeTypeOf - -- **Type:** `(c: 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined') => Awaitable` + Here, the `beforeEach` ensures that user is added for each test. - `toBeTypeOf` asserts if an actual value is of type of received type. + Since Vitest v0.10.0, `beforeEach` also accepts an optional cleanup function (equivalent to `afterEach`). ```ts - import { expect, test } from 'vitest' - const actual = 'stock' - - test('stock is type of string', () => { - expect(actual).toBeTypeOf('string') - }) - ``` - -### toBeInstanceOf - -- **Type:** `(c: any) => Awaitable` - - `toBeInstanceOf` asserts if an actual value is instance of received class. + import { beforeEach } from 'vitest' - ```ts - import { expect, test } from 'vitest' - import { Stocks } from './stocks' - const stocks = new Stocks() + beforeEach(async () => { + // called once before each test run + await prepareSomething() - test('stocks are instance of Stocks', () => { - expect(stocks).toBeInstanceOf(Stocks) + // clean up function, called once after each test run + return async () => { + await resetSomething() + } }) ``` -### toBeGreaterThan - -- **Type:** `(n: number | bigint) => Awaitable` - - `toBeGreaterThan` asserts if actual value is greater than received one. Equal values will fail the test. - - ```ts - import { expect, test } from 'vitest' - import { getApples } from './stock' - - test('have more then 10 apples', () => { - expect(getApples()).toBeGreaterThan(10) - }) - ``` +### afterEach -### toBeGreaterThanOrEqual +- **Type:** `afterEach(fn: () => Awaitable, timeout?: number)` -- **Type:** `(n: number | bigint) => Awaitable` + Register a callback to be called after each one of the tests in the current context completes. + If the function returns a promise, Vitest waits until the promise resolve before continuing. - `toBeGreaterThanOrEqual` asserts if actual value is greater than received one or equal to it. + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. ```ts - import { expect, test } from 'vitest' - import { getApples } from './stock' + import { afterEach } from 'vitest' - test('have 11 apples or more', () => { - expect(getApples()).toBeGreaterThanOrEqual(11) + afterEach(async () => { + await clearTestingData() // clear testing data after each test run }) ``` + Here, the `afterEach` ensures that testing data is cleared after each test runs. -### toBeLessThan - -- **Type:** `(n: number | bigint) => Awaitable` - - `toBeLessThan` asserts if actual value is less than received one. Equal values will fail the test. - - ```ts - import { expect, test } from 'vitest' - import { getApples } from './stock' - - test('have less then 20 apples', () => { - expect(getApples()).toBeLessThan(20) - }) - ``` +### beforeAll -### toBeLessThanOrEqual +- **Type:** `beforeAll(fn: () => Awaitable, timeout?: number)` -- **Type:** `(n: number | bigint) => Awaitable` + Register a callback to be called once before starting to run all tests in the current context. + If the function returns a promise, Vitest waits until the promise resolve before running tests. - `toBeLessThanOrEqual` asserts if actual value is less than received one or equal to it. + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. ```ts - import { expect, test } from 'vitest' - import { getApples } from './stock' + import { beforeAll } from 'vitest' - test('have 11 apples or less', () => { - expect(getApples()).toBeLessThanOrEqual(11) + beforeAll(async () => { + await startMocking() // called once before all tests run }) ``` -### toEqual - -- **Type:** `(received: any) => Awaitable` + Here the `beforeAll` ensures that the mock data is set up before tests run. - `toEqual` asserts if actual value is equal to received one or has the same structure, if it is an object (compares them recursively). You can see the difference between `toEqual` and [`toBe`](#tobe) in this example: + Since Vitest v0.10.0, `beforeAll` also accepts an optional cleanup function (equivalent to `afterAll`). ```ts - import { expect, test } from 'vitest' - - const stockBill = { - type: 'apples', - count: 13, - } - - const stockMary = { - type: 'apples', - count: 13, - } - - test('stocks have the same properties', () => { - expect(stockBill).toEqual(stockMary) - }) - - test('stocks are not the same', () => { - expect(stockBill).not.toBe(stockMary) - }) - ``` - - :::warning - A _deep equality_ will not be performed for `Error` objects. To test if something was thrown, use [`toThrowError`](#tothrowerror) assertion. - ::: - -### toStrictEqual - -- **Type:** `(received: any) => Awaitable` - - `toStrictEqual` asserts if actual value is equal to received one or has the same structure, if it is an object (compares them recursively), and of the same type. - - Differences from [`.toEqual`](#toequal): - - - Keys with `undefined` properties are checked. e.g. `{a: undefined, b: 2}` does not match `{b: 2}` when using `.toStrictEqual`. - - Array sparseness is checked. e.g. `[, 1]` does not match `[undefined, 1]` when using `.toStrictEqual`. - - Object types are checked to be equal. e.g. A class instance with fields `a` and` b` will not equal a literal object with fields `a` and `b`. + import { beforeAll } from 'vitest' - ```ts - import { expect, test } from 'vitest' + beforeAll(async () => { + // called once before all tests run + await startMocking() - class Stock { - constructor(type) { - this.type = type + // clean up function, called once after all tests run + return async () => { + await stopMocking() } - } - - test('structurally the same, but semantically different', () => { - expect(new Stock('apples')).toEqual({ type: 'apples' }) - expect(new Stock('apples')).not.toStrictEqual({ type: 'apples' }) - }) - ``` - -### toContain - -- **Type:** `(received: string) => Awaitable` - - `toContain` asserts if actual value is in an array. `toContain` can also check whether a string is a substring of another string. - - ```ts - import { expect, test } from 'vitest' - import { getAllFruits } from './stock' - - test('the fruit list contains orange', () => { - expect(getAllFruits()).toContain('orange') - }) - ``` - -### toContainEqual - -- **Type:** `(received: any) => Awaitable` - - `toContainEqual` asserts if an item with a specific structure and values is contained in an array. - It works like [`toEqual`](#toequal) inside for each element. - - ```ts - import { expect, test } from 'vitest' - import { getFruitStock } from './stock' - - test('apple available', () => { - expect(getFruitStock()).toContainEqual({ fruit: 'apple', count: 5 }) - }) - ``` - -### toHaveLength - -- **Type:** `(received: number) => Awaitable` - - `toHaveLength` asserts if an object has a `.length` property and it is set to a certain numeric value. - - ```ts - import { expect, test } from 'vitest' - - test('toHaveLength', () => { - expect('abc').toHaveLength(3) - expect([1, 2, 3]).toHaveLength(3) - - expect('').not.toHaveLength(3) // doesn't have .length of 3 - expect({ length: 3 }).toHaveLength(3) }) ``` -### toHaveProperty +### afterAll -- **Type:** `(key: any, received?: any) => Awaitable` +- **Type:** `afterAll(fn: () => Awaitable, timeout?: number)` - `toHaveProperty` asserts if a property at provided reference `key` exists for an object. + Register a callback to be called once after all tests have run in the current context. + If the function returns a promise, Vitest waits until the promise resolve before continuing. - You can provide an optional value argument also known as deep equality, like the `toEqual` matcher to compare the received property value. + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. ```ts - import { expect, test } from 'vitest' - - const invoice = { - 'isActive': true, - 'P.O': '12345', - 'customer': { - first_name: 'John', - last_name: 'Doe', - location: 'China', - }, - 'total_amount': 5000, - 'items': [ - { - type: 'apples', - quantity: 10, - }, - { - type: 'oranges', - quantity: 5, - }, - ], - } - - test('John Doe Invoice', () => { - expect(invoice).toHaveProperty('isActive') // assert that the key exists - expect(invoice).toHaveProperty('total_amount', 5000) // assert that the key exists and the value is equal - - expect(invoice).not.toHaveProperty('account') // assert that this key does not exist - - // Deep referencing using dot notation - expect(invoice).toHaveProperty('customer.first_name') - expect(invoice).toHaveProperty('customer.last_name', 'Doe') - expect(invoice).not.toHaveProperty('customer.location', 'India') - - // Deep referencing using an array containing the key - expect(invoice).toHaveProperty('items[0].type', 'apples') - expect(invoice).toHaveProperty('items.0.type', 'apples') // dot notation also works + import { afterAll } from 'vitest' - // Wrap your key in an array to avoid the key from being parsed as a deep reference - expect(invoice).toHaveProperty(['P.O'], '12345') + afterAll(async () => { + await stopMocking() // this method is called after all tests run }) ``` -### toMatch - -- **Type:** `(received: string | regexp) => Awaitable` - - `toMatch` asserts if a string matches a regular expression or a string. - - ```ts - import { expect, test } from 'vitest' - - test('top fruits', () => { - expect('top fruits include apple, orange and grape').toMatch(/apple/) - expect('applefruits').toMatch('fruit') // toMatch also accepts a string - }) - ``` - -### toMatchObject - -- **Type:** `(received: object | array) => Awaitable` - - `toMatchObject` asserts if an object matches a subset of the properties of an object. - - You can also pass an array of objects. This is useful if you want to check that two arrays match in their number of elements, as opposed to `arrayContaining`, which allows for extra elements in the received array. - - ```ts - import { expect, test } from 'vitest' - - const johnInvoice = { - isActive: true, - customer: { - first_name: 'John', - last_name: 'Doe', - location: 'China', - }, - total_amount: 5000, - items: [ - { - type: 'apples', - quantity: 10, - }, - { - type: 'oranges', - quantity: 5, - }, - ], - } - - const johnDetails = { - customer: { - first_name: 'John', - last_name: 'Doe', - location: 'China', - }, - } - - test('invoice has john personal details', () => { - expect(johnInvoice).toMatchObject(johnDetails) - }) - - test('the number of elements must match exactly', () => { - // Assert that an array of object matches - expect([{ foo: 'bar' }, { baz: 1 }]).toMatchObject([ - { foo: 'bar' }, - { baz: 1 }, - ]) - }) - ``` - -### toThrowError - -- **Type:** `(received: any) => Awaitable` - -- **Alias:** `toThrow` - - `toThrowError` asserts if a function throws an error when it is called. - - For example, if we want to test that `getFruitStock('pineapples')` throws, we could write: - - You can provide an optional argument to test that a specific error is thrown: - - - regular expression: error message matches the pattern - - string: error message includes the substring - - :::tip - You must wrap the code in a function, otherwise the error will not be caught, and the assertion will fail. - ::: - - ```ts - import { expect, test } from 'vitest' - - function getFruitStock(type) { - if (type === 'pineapples') - throw new DiabetesError('Pineapples is not good for people with diabetes') - - // Do some other stuff - } - - test('throws on pineapples', () => { - // Test that the error message says "diabetes" somewhere: these are equivalent - expect(() => getFruitStock('pineapples')).toThrowError(/diabetes/) - expect(() => getFruitStock('pineapples')).toThrowError('diabetes') - - // Test the exact error message - expect(() => getFruitStock('pineapples')).toThrowError( - /^Pineapples is not good for people with diabetes$/, - ) - }) - ``` - - :::tip - To test async functions, use in combination with [rejects](#rejects). - ::: - -### toMatchSnapshot - -- **Type:** `(shape?: Partial | string, message?: string) => void` - - This ensures that a value matches the most recent snapshot. - - You can provide an optional `hint` string argument that is appended to the test name. Although Vitest always appends a number at the end of a snapshot name, short descriptive hints might be more useful than numbers to differentiate multiple snapshots in a single it or test block. Vitest sorts snapshots by name in the corresponding `.snap` file. - - :::tip - When snapshot mismatch and causing the test failing, if the mismatch is expected, you can press `u` key to update the snapshot for once. Or you can pass `-u` or `--update` CLI options to make Vitest always update the tests. - ::: - - ```ts - import { expect, test } from 'vitest' - - test('matches snapshot', () => { - const data = { foo: new Set(['bar', 'snapshot']) } - expect(data).toMatchSnapshot() - }) - ``` - - You can also provide a shape of an object, if you are testing just a shape of an object, and don't need it to be 100% compatible: - - ```ts - import { expect, test } from 'vitest' - - test('matches snapshot', () => { - const data = { foo: new Set(['bar', 'snapshot']) } - expect(data).toMatchSnapshot({ foo: expect.any(Set) }) - }) - ``` - -### toMatchInlineSnapshot - -- **Type:** `(shape?: Partial | string, snapshot?: string, message?: string) => void` - - This ensures that a value matches the most recent snapshot. - - Vitest adds and updates the inlineSnapshot string argument to the matcher in the test file (instead of an external `.snap` file). - - ```ts - import { expect, test } from 'vitest' - - test('matches inline snapshot', () => { - const data = { foo: new Set(['bar', 'snapshot']) } - // Vitest will update following content when updating the snapshot - expect(data).toMatchInlineSnapshot(` - { - "foo": Set { - "bar", - "snapshot", - }, - } - `) - }) - ``` - - You can also provide a shape of an object, if you are testing just a shape of an object, and don't need it to be 100% compatible: - - ```ts - import { expect, test } from 'vitest' - - test('matches snapshot', () => { - const data = { foo: new Set(['bar', 'snapshot']) } - expect(data).toMatchInlineSnapshot( - { foo: expect.any(Set) }, - ` - { - "foo": Any, - } - ` - ) - }) - ``` - - -### toThrowErrorMatchingSnapshot - -- **Type:** `(message?: string) => void` - - The same as [`toMatchSnapshot`](#tomatchsnapshot), but expects the same value as [`toThrowError`](#tothrowerror). - - If the function throws an `Error`, the snapshot will be the error message. Otherwise, snapshot will be the value thrown by the function. - -### toThrowErrorMatchingInlineSnapshot - -- **Type:** `(snapshot?: string, message?: string) => void` - - The same as [`toMatchInlineSnapshot`](#tomatchinlinesnapshot), but expects the same value as [`toThrowError`](#tothrowerror). - - If the function throws an `Error`, the snapshot will be the error message. Otherwise, snapshot will be the value thrown by the function. - -### toHaveBeenCalled - -- **Type:** `() => Awaitable` - - This assertion is useful for testing that a function has been called. Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - const market = { - buy(subject: string, amount: number) { - // ... - }, - } - - test('spy function', () => { - const buySpy = vi.spyOn(market, 'buy') - - expect(buySpy).not.toHaveBeenCalled() - - market.buy('apples', 10) - - expect(buySpy).toHaveBeenCalled() - }) - ``` - -### toHaveBeenCalledTimes - - - **Type**: `(amount: number) => Awaitable` - - This assertion checks if a function was called a certain amount of times. Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - const market = { - buy(subject: string, amount: number) { - // ... - }, - } - - test('spy function called two times', () => { - const buySpy = vi.spyOn(market, 'buy') - - market.buy('apples', 10) - market.buy('apples', 20) - - expect(buySpy).toHaveBeenCalledTimes(2) - }) - ``` - -### toHaveBeenCalledWith - - - **Type**: `(...args: any[]) => Awaitable` - - This assertion checks if a function was called at least once with certain parameters. Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - const market = { - buy(subject: string, amount: number) { - // ... - }, - } - - test('spy function', () => { - const buySpy = vi.spyOn(market, 'buy') - - market.buy('apples', 10) - market.buy('apples', 20) - - expect(buySpy).toHaveBeenCalledWith('apples', 10) - expect(buySpy).toHaveBeenCalledWith('apples', 20) - }) - ``` - -### toHaveBeenLastCalledWith - - - **Type**: `(...args: any[]) => Awaitable` - - This assertion checks if a function was called with certain parameters at it's last invocation. Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - const market = { - buy(subject: string, amount: number) { - // ... - }, - } - - test('spy function', () => { - const buySpy = vi.spyOn(market, 'buy') - - market.buy('apples', 10) - market.buy('apples', 20) - - expect(buySpy).not.toHaveBeenLastCalledWith('apples', 10) - expect(buySpy).toHaveBeenLastCalledWith('apples', 20) - }) - ``` - -### toHaveBeenNthCalledWith - - - **Type**: `(time: number, ...args: any[]) => Awaitable` - - This assertion checks if a function was called with certain parameters at the certain time. The count starts at 1. So, to check the second entry, you would write `.toHaveBeenNthCalledWith(2, ...)`. - - Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - const market = { - buy(subject: string, amount: number) { - // ... - }, - } - - test('first call of spy function called with right params', () => { - const buySpy = vi.spyOn(market, 'buy') - - market.buy('apples', 10) - market.buy('apples', 20) - - expect(buySpy).toHaveBeenNthCalledWith(1, 'apples', 10) - }) - ``` - -### toHaveReturned - - - **Type**: `() => Awaitable` - - This assertion checks if a function has successfully returned a value at least once (i.e., did not throw an error). Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - const getApplesPrice = (amount: number) => { - const PRICE = 10 - return amount * PRICE - } - - test('spy function returned a value', () => { - const getPriceSpy = vi.fn(getApplesPrice) - - const price = getPriceSpy(10) - - expect(price).toBe(100) - expect(getPriceSpy).toHaveReturned() - }) - ``` - -### toHaveReturnedTimes - - - **Type**: `(amount: number) => Awaitable` - - This assertion checks if a function has successfully returned a value exact amount of times (i.e., did not throw an error). Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - test('spy function returns a value two times', () => { - const sell = vi.fn((product: string) => ({ product })) - - sell('apples') - sell('bananas') - - expect(sell).toHaveReturnedTimes(2) - }) - ``` - -### toHaveReturnedWith - - - **Type**: `(returnValue: any) => Awaitable` - - You can call this assertion to check if a function has successfully returned a value with certain parameters at least once. Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - test('spy function returns a product', () => { - const sell = vi.fn((product: string) => ({ product })) - - sell('apples') - - expect(sell).toHaveReturnedWith({ product: 'apples' }) - }) - ``` - -### toHaveLastReturnedWith - - - **Type**: `(returnValue: any) => Awaitable` - - You can call this assertion to check if a function has successfully returned a value with certain parameters on it's last invoking. Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - test('spy function returns bananas on a last call', () => { - const sell = vi.fn((product: string) => ({ product })) - - sell('apples') - sell('bananas') - - expect(sell).toHaveLastReturnedWith({ product: 'bananas' }) - }) - ``` - -### toHaveNthReturnedWith - - - **Type**: `(time: number, returnValue: any) => Awaitable` - - You can call this assertion to check if a function has successfully returned a value with certain parameters on a certain call. Requires a spy function to be passed to `expect`. - - ```ts - import { expect, test, vi } from 'vitest' - - test('spy function returns bananas on second call', () => { - const sell = vi.fn((product: string) => ({ product })) - - sell('apples') - sell('bananas') - - expect(sell).toHaveNthReturnedWith(2, { product: 'bananas' }) - }) - ``` - -### toSatisfy - - - **Type:** `(predicate: (value: any) => boolean) => Awaitable` - - This assertion checks if a value satisfies a certain predicate. - - ```ts - describe('toSatisfy()', () => { - const isOdd = (value: number) => value % 2 !== 0 - - it('pass with 0', () => { - expect(1).toSatisfy(isOdd) - }) - - it('pass with negotiation', () => { - expect(2).not.toSatisfy(isOdd) - }) - }) - ``` - -### resolves - -- **Type:** `Promisify` - - `resolves` is intended to remove boilerplate when asserting asynchronous code. Use it to unwrap value from the pending promise and assert its value with usual assertions. If the promise rejects, the assertion will fail. - - It returns the same `Assertions` object, but all matchers now return `Promise`, so you would need to `await` it. Also works with `chai` assertions. - - For example, if you have a function, that makes an API call and returns some data, you may use this code to assert its return value: - - ```ts - import { expect, test } from 'vitest' - - async function buyApples() { - return fetch('/buy/apples').then(r => r.json()) - } - - test('buyApples returns new stock id', async () => { - // toEqual returns a promise now, so you HAVE to await it - await expect(buyApples()).resolves.toEqual({ id: 1 }) // jest API - await expect(buyApples()).resolves.to.equal({ id: 1 }) // chai API - }) - ``` - - :::warning - If the assertion is not awaited, then you will have a false-positive test that will pass every time. To make sure that assertions are actually called, you may use [`expect.assertions(number)`](#expect-assertions). - ::: - -### rejects - -- **Type:** `Promisify` - - `rejects` is intended to remove boilerplate when asserting asynchronous code. Use it to unwrap reason why promise was rejected, and assert its value with usual assertions. If promise successfully resolves, the assertion will fail. - - It returns the same `Assertions` object, but all matchers are now return `Promise`, so you would need to `await` it. Also works with `chai` assertions. - - For example, if you have a function that fails when you call it, you may use this code to assert the reason: - - ```ts - import { expect, test } from 'vitest' - - async function buyApples(id) { - if (!id) - throw new Error('no id') - } - - test('buyApples throws an error when no id provided', async () => { - // toThrow returns a promise now, so you HAVE to await it - await expect(buyApples()).rejects.toThrow('no id') - }) - ``` - - :::warning - If the assertion is not awaited, then you will have a false-positive test that will pass every time. To make sure that assertions are actually happened, you may use [`expect.assertions(number)`](#expect-assertions). - ::: - -### expect.assertions - -- **Type:** `(count: number) => void` - - After the test has passed or failed verifies that certain number of assertions was called during a test. Useful case would be to check if an asynchronous code was called. - - For example, if we have a function that asynchronously calls two matchers, we can assert that they were actually called. - - ```ts - import { expect, test } from 'vitest' - - async function doAsync(...cbs) { - await Promise.all( - cbs.map((cb, index) => cb({ index })), - ) - } - - test('all assertions are called', async () => { - expect.assertions(2) - function callback1(data) { - expect(data).toBeTruthy() - } - function callback2(data) { - expect(data).toBeTruthy() - } - - await doAsync(callback1, callback2) - }) - ``` - -### expect.hasAssertions - -- **Type:** `() => void` - - After the test has passed or failed verifies that at least one assertion was called during a test. Useful case would be to check if an asynchronous code was called. - - For example, if you have a code that calls a callback, we can make an assertion inside a callback, but the test will always pass, if we don't check if an assertion was called. - - ```ts - import { expect, test } from 'vitest' - import { db } from './db' - - const cbs = [] - - function onSelect(cb) { - cbs.push(cb) - } - - // after selecting from db, we call all callbacks - function select(id) { - return db.select({ id }).then((data) => { - return Promise.all( - cbs.map(cb => cb(data)), - ) - }) - } - - test('callback was called', async () => { - expect.hasAssertions() - onSelect((data) => { - // should be called on select - expect(data).toBeTruthy() - }) - // if not awaited, test will fail - // if you don't have expect.hasAssertions(), test will pass - await select(3) - }) - ``` - - - -### expect.anything - -- **Type:** `() => any` - - This asymmetric matcher, when used with equality check, will always return `true`. Useful, if you just want to be sure that the property exist. - - ```ts - import { expect, test } from 'vitest' - - test('object has "apples" key', () => { - expect({ apples: 22 }).toEqual({ apples: expect.anything() }) - }) - ``` - -### expect.any - -- **Type:** `(constructor: unknown) => any` - - This asymmetric matcher, when used with equality check, will return `true` only if value is an instance of specified constructor. Useful, if you have a value that is generated each time, and you only want to know that it exist with a proper type. - - ```ts - import { expect, test } from 'vitest' - import { generateId } from './generators' - - test('"id" is a number', () => { - expect({ id: generateId() }).toEqual({ id: expect.any(Number) }) - }) - ``` - -### expect.arrayContaining - -- **Type:** `(expected: T[]) => any` - - When used with equality check, this asymmetric matcher will return `true` if value is an array and contains specified items. - - ```ts - import { expect, test } from 'vitest' - - test('basket includes fuji', () => { - const basket = { - varieties: [ - 'Empire', - 'Fuji', - 'Gala', - ], - count: 3 - } - expect(basket).toEqual({ - count: 3, - varieties: expect.arrayContaining(['Fuji']) - }) - }) - ``` - - :::tip - You can use `expect.not` with this matcher to negate the expected value. - ::: - -### expect.objectContaining - -- **Type:** `(expected: any) => any` - - When used with equality check, this asymmetric matcher will return `true` if value has a similar shape. - - ```ts - import { expect, test } from 'vitest' - - test('basket has empire apples', () => { - const basket = { - varieties: [ - { - name: 'Empire', - count: 1, - } - ], - } - expect(basket).toEqual({ - varieties: [ - expect.objectContaining({ name: 'Empire' }), - ] - }) - }) - ``` - - :::tip - You can use `expect.not` with this matcher to negate the expected value. - ::: - -### expect.stringContaining - -- **Type:** `(expected: any) => any` - - When used with equality check, this asymmetric matcher will return `true` if value is a string and contains specified substring. - - ```ts - import { expect, test } from 'vitest' - - test('variety has "Emp" in its name', () => { - const variety = { - name: 'Empire', - count: 1, - } - expect(basket).toEqual({ - name: expect.stringContaining('Emp'), - count: 1, - }) - }) - ``` - - :::tip - You can use `expect.not` with this matcher to negate the expected value. - ::: - -### expect.stringMatching - -- **Type:** `(expected: any) => any` - - When used with equality check, this asymmetric matcher will return `true` if value is a string and contains specified substring or the string matches regular expression. - - ```ts - import { expect, test } from 'vitest' - - test('variety ends with "re"', () => { - const variety = { - name: 'Empire', - count: 1, - } - expect(basket).toEqual({ - name: expect.stringMatching(/re$/), - count: 1, - }) - }) - ``` - - :::tip - You can use `expect.not` with this matcher to negate the expected value. - ::: - -### expect.addSnapshotSerializer - -- **Type:** `(plugin: PrettyFormatPlugin) => void` - - This method adds custom serializers that are called when creating a snapshot. This is advanced feature - if you want to know more, please read a [guide on custom serializers](/guide/snapshot#custom-serializer). - - If you are adding custom serializers, you should call this method inside [`setupFiles`](/config/#setupfiles). This will affect every snapshot. - - :::tip - If you previously used Vue CLI with Jest, you might want to install [jest-serializer-vue](https://www.npmjs.com/package/jest-serializer-vue). Otherwise, your snapshots will be wrapped in a string, which cases `"` to be escaped. - ::: - -### expect.extend - -- **Type:** `(matchers: MatchersObject) => void` - - You can extend default matchers with your own. This function is used to extend the matchers object with custom matchers. - - When you define matchers that way, you also create asymmetric matchers that can be used like `expect.stringContaining`. - - ```ts - import { expect, test } from 'vitest' - - test('custom matchers', () => { - expect.extend({ - toBeFoo: (received, expected) => { - if (received !== 'foo') { - return { - message: () => `expected ${received} to be foo`, - pass: false, - } - } - }, - }) - - expect('foo').toBeFoo() - expect({ foo: 'foo' }).toEqual({ foo: expect.toBeFoo() }) - }) - ``` - - > If you want your matchers to appear in every test, you should call this method inside [`setupFiles`](/config/#setupFiles). - - This function is compatible with Jest's `expect.extend`, so any library that uses it to create custom matchers will work with Vitest. - - If you are using TypeScript, you can extend default Matchers interface with the code bellow: - - ```ts - interface CustomMatchers { - toBeFoo(): R - } - - declare global { - namespace Vi { - interface Assertion extends CustomMatchers {} - interface AsymmetricMatchersContaining extends CustomMatchers {} - } - } - ``` - - > Note: augmenting jest.Matchers interface will also work. - - :::tip - If you want to know more, checkout [guide on extending matchers](/guide/extending-matchers). - ::: - -## expectTypeOf - -- **Type:** `(a: unknown) => ExpectTypeOf` - -### not - - - **Type:** `ExpectTypeOf` - - You can negate all assertions, using `.not` property. - -### toEqualTypeOf - - - **Type:** `(expected: T) => void` - - This matcher will check, if types are fully equal to each other. This matcher will not fail, if two objects have different values, but the same type, but will fail, if object is missing a property. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: number }>() - expectTypeOf({ a: 1 }).toEqualTypeOf({ a: 1 }) - expectTypeOf({ a: 1 }).toEqualTypeOf({ a: 2 }) - expectTypeOf({ a: 1, b: 1 }).not.toEqualTypeOf<{ a: number }>() - ``` - -### toMatchTypeOf - - - **Type:** `(expected: T) => void` - - This matcher checks if expect type extends provided type. It is different from `toEqual` and is more similar to expect's `toMatch`. With this matcher you can check, if an object "matches" a type. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf({ a: 1, b: 1 }).toMatchTypeOf({ a: 1 }) - expectTypeOf().toMatchTypeOf() - expectTypeOf().not.toMatchTypeOf() - ``` - -### extract - - - **Type:** `ExpectTypeOf` - - You can use `.extract` to narrow down types for further testing. - - ```ts - import { expectTypeOf } from 'vitest' - - type ResponsiveProp = T | T[] | { xs?: T; sm?: T; md?: T } - const getResponsiveProp = (_props: T): ResponsiveProp => ({}) - interface CSSProperties { margin?: string; padding?: string } - - const cssProperties: CSSProperties = { margin: '1px', padding: '2px' } - - expectTypeOf(getResponsiveProp(cssProperties)) - .extract<{ xs?: any }>() // extracts the last type from a union - .toEqualTypeOf<{ xs?: CSSProperties; sm?: CSSProperties; md?: CSSProperties }>() - - expectTypeOf(getResponsiveProp(cssProperties)) - .extract() // extracts an array from a union - .toEqualTypeOf() - ``` - - ::: warning - If no type is found in the union, `.extract` will return `never`. - ::: - -### exclude - - - **Type:** `ExpectTypeOf` - - You can use `.exclude` to remove types from a union for further testing. - - ```ts - import { expectTypeOf } from 'vitest' - - type ResponsiveProp = T | T[] | { xs?: T; sm?: T; md?: T } - const getResponsiveProp = (_props: T): ResponsiveProp => ({}) - interface CSSProperties { margin?: string; padding?: string } - - const cssProperties: CSSProperties = { margin: '1px', padding: '2px' } - - expectTypeOf(getResponsiveProp(cssProperties)) - .exclude() - .exclude<{ xs?: unknown }>() // or just .exclude() - .toEqualTypeOf() - ``` - - ::: warning - If no type is found in the union, `.exclude` will return `never`. - ::: - -### returns - - - **Type:** `ExpectTypeOf` - - You can use `.returns` to extract return value of a function type. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(() => {}).returns.toBeVoid() - expectTypeOf((a: number) => [a, a]).returns.toEqualTypeOf([1, 2]) - ``` - - ::: warning - If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. - ::: - -### parameters - - - **Type:** `ExpectTypeOf` - - You can extract function arguments with `.parameters` to perform assertions on its value. Parameters are returned as an array. - - ```ts - import { expectTypeOf } from 'vitest' - - type NoParam = () => void - type HasParam = (s: string) => void - - expectTypeOf().parameters.toEqualTypeOf<[]>() - expectTypeOf().parameters.toEqualTypeOf<[string]>() - ``` - - ::: warning - If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. - ::: - - ::: tip - You can also use [`.toBeCallableWith`](#tobecallablewith) matcher as a more expressive assertion. - ::: - -### parameter - - - **Type:** `(nth: number) => ExpectTypeOf` - - You can extract a certain function argument with `.parameter(number)` call to perform other assertions on it. - - ```ts - import { expectTypeOf } from 'vitest' - - const foo = (a: number, b: string) => [a, b] - - expectTypeOf(foo).parameter(0).toBeNumber() - expectTypeOf(foo).parameter(1).toBeString() - ``` - - ::: warning - If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. - ::: - -### constructorParameters - - - **Type:** `ExpectTypeOf` - - You can extract constructor parameters as an array of values and perform assertions on them with this method. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(Date).constructorParameters.toEqualTypeOf<[] | [string | number | Date]>() - ``` - - ::: warning - If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. - ::: - - ::: tip - You can also use [`.toBeConstructibleWith`](#tobeconstructiblewith) matcher as a more expressive assertion. - ::: - -### instance - - - **Type:** `ExpectTypeOf` - - This property gives access to matchers that can be performed on an instance of the provided class. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(Date).instance.toHaveProperty('toISOString') - ``` - - ::: warning - If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. - ::: - -### items - - - **Type:** `ExpectTypeOf` - - You can get array item type with `.items` to perform further assertions. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf([1, 2, 3]).items.toEqualTypeOf() - expectTypeOf([1, 2, 3]).items.not.toEqualTypeOf() - ``` - -### resolves - - - **Type:** `ExpectTypeOf` - - This matcher extracts resolved value of a `Promise`, so you can perform other assertions on it. - - ```ts - import { expectTypeOf } from 'vitest' - - const asyncFunc = async () => 123 - - expectTypeOf(asyncFunc).returns.resolves.toBeNumber() - expectTypeOf(Promise.resolve('string')).resolves.toBeString() - ``` - - ::: warning - If used on a non-promise type, it will return `never`, so you won't be able to chain it with other matchers. - ::: - -### guards - - - **Type:** `ExpectTypeOf` - - This matcher extracts guard value (e.g., `v is number`), so you can perform assertions on it. - - ```ts - import { expectTypeOf } from 'vitest' - - const isString = (v: any): v is string => typeof v === 'string' - expectTypeOf(isString).guards.toBeString() - ``` - - ::: warning - Returns `never`, if the value is not a guard function, so you won't be able to chain it with other matchers. - ::: - -### asserts - - - **Type:** `ExpectTypeOf` - - This matcher extracts assert value (e.g., `assert v is number`), so you can perform assertions on it. - - ```ts - import { expectTypeOf } from 'vitest' - - const assertNumber = (v: any): asserts v is number => { - if (typeof v !== 'number') - throw new TypeError('Nope !') - } - - expectTypeOf(assertNumber).asserts.toBeNumber() - ``` - - ::: warning - Returns `never`, if the value is not an assert function, so you won't be able to chain it with other matchers. - ::: - -### toBeAny - - - **Type:** `() => void` - - With this matcher you can check, if provided type is `any` type. If the type is too specific, the test will fail. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf().toBeAny() - expectTypeOf({} as any).toBeAny() - expectTypeOf('string').not.toBeAny() - ``` - -### toBeUnknown - - - **Type:** `() => void` - - This matcher checks, if provided type is `unknown` type. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf().toBeUnknown() - expectTypeOf({} as unknown).toBeUnknown() - expectTypeOf('string').not.toBeUnknown() - ``` - -### toBeNever - - - **Type:** `() => void` - - This matcher checks, if provided type is a `never` type. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf().toBeNever() - expectTypeOf((): never => {}).returns.toBeNever() - ``` - -### toBeFunction - - - **Type:** `() => void` - - This matcher checks, if provided type is a `functon`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(42).not.toBeFunction() - expectTypeOf((): never => {}).toBeFunction() - ``` - -### toBeObject - - - **Type:** `() => void` - - This matcher checks, if provided type is an `object`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(42).not.toBeObject() - expectTypeOf({}).toBeObject() - ``` - -### toBeArray - - - **Type:** `() => void` - - This matcher checks, if provided type is `Array`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(42).not.toBeArray() - expectTypeOf([]).toBeArray() - expectTypeOf([1, 2]).toBeArray() - expectTypeOf([{}, 42]).toBeArray() - ``` - -### toBeString - - - **Type:** `() => void` - - This matcher checks, if provided type is a `string`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(42).not.toBeString() - expectTypeOf('').toBeString() - expectTypeOf('a').toBeString() - ``` - -### toBeBoolean - - - **Type:** `() => void` - - This matcher checks, if provided type is `boolean`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(42).not.toBeBoolean() - expectTypeOf(true).toBeBoolean() - expectTypeOf().toBeBoolean() - ``` - -### toBeVoid - - - **Type:** `() => void` - - This matcher checks, if provided type is `void`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(() => {}).returns.toBeVoid() - expectTypeOf().toBeVoid() - ``` - -### toBeSymbol - - - **Type:** `() => void` - - This matcher checks, if provided type is a `symbol`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(Symbol(1)).toBeSymbol() - expectTypeOf().toBeSymbol() - ``` - -### toBeNull - - - **Type:** `() => void` - - This matcher checks, if provided type is `null`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(null).toBeNull() - expectTypeOf().toBeNull() - expectTypeOf(undefined).not.toBeNull() - ``` - -### toBeUndefined - - - **Type:** `() => void` - - This matcher checks, if provided type is `undefined`. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(undefined).toBeUndefined() - expectTypeOf().toBeUndefined() - expectTypeOf(null).not.toBeUndefined() - ``` - -### toBeNullable - - - **Type:** `() => void` - - This matcher checks, if you can use `null` or `undefined` with provided type. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf<1 | undefined>().toBeNullable() - expectTypeOf<1 | null>().toBeNullable() - expectTypeOf<1 | undefined | null>().toBeNullable() - ``` - -### toBeCallableWith - - - **Type:** `() => void` - - This matcher ensures you can call provided function with a set of parameters. - - ```ts - import { expectTypeOf } from 'vitest' - - type NoParam = () => void - type HasParam = (s: string) => void - - expectTypeOf().toBeCallableWith() - expectTypeOf().toBeCallableWith('some string') - ``` - - ::: warning - If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. - ::: - -### toBeConstructibleWith - - - **Type:** `() => void` - - This matcher ensures you can create a new instance with a set of constructor parameters. - - ```ts - import { expectTypeOf } from 'vitest' - - expectTypeOf(Date).toBeConstructibleWith(new Date()) - expectTypeOf(Date).toBeConstructibleWith('01-01-2000') - ``` - - ::: warning - If used on a non-function type, it will return `never`, so you won't be able to chain it with other matchers. - ::: - -### toHaveProperty - - - **Type:** `(property: K) => ExpectTypeOf` - - This matcher checks if a property exists on provided object. If it exists, it also returns the same set of matchers for the type of this property, so you can chain assertions one after another. - - ```ts - import { expectTypeOf } from 'vitest' - - const obj = { a: 1, b: '' } - - expectTypeOf(obj).toHaveProperty('a') - expectTypeOf(obj).not.toHaveProperty('c') - - expectTypeOf(obj).toHaveProperty('a').toBeNumber() - expectTypeOf(obj).toHaveProperty('b').toBeString() - expectTypeOf(obj).toHaveProperty('a').not.toBeString() - ``` - -## assertType - - - **Type:** `(value: T): void` - - You can use this function as an alternative for `expectTypeOf` to easily assert that argument type is equal to provided generic. - - ```ts - import { assertType } from 'vitest' - - function concat(a: string, b: string): string - function concat(a: number, b: number): number - function concat(a: string | number, b: string | number): string | number - - assertType(concat('a', 'b')) - assertType(concat(1, 2)) - // @ts-expect-error wrong types - assertType(concat('a', 2)) - ``` - -## 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. - -### beforeEach - -- **Type:** `beforeEach(fn: () => Awaitable, timeout?: number)` - - Register a callback to be called before each of the tests in the current context runs. - If the function returns a promise, Vitest waits until the promise resolve before running the test. - - Optionally, you can pass a timeout (in milliseconds) defining how long to wait before terminating. The default is 5 seconds. - - ```ts - import { beforeEach } from 'vitest' - - beforeEach(async () => { - // Clear mocks and add some testing data after before each test run - await stopMocking() - await addUser({ name: 'John' }) - }) - ``` - - Here, the `beforeEach` ensures that user is added for each test. - - Since Vitest v0.10.0, `beforeEach` also accepts an optional cleanup function (equivalent to `afterEach`). - - ```ts - import { beforeEach } from 'vitest' - - beforeEach(async () => { - // called once before each test run - await prepareSomething() - - // clean up function, called once after each test run - return async () => { - await resetSomething() - } - }) - ``` - -### afterEach - -- **Type:** `afterEach(fn: () => Awaitable, timeout?: number)` - - Register a callback to be called after each one of the tests in the current context completes. - If the function returns a promise, Vitest waits until the promise resolve before continuing. - - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. - - ```ts - import { afterEach } from 'vitest' - - afterEach(async () => { - await clearTestingData() // clear testing data after each test run - }) - ``` - Here, the `afterEach` ensures that testing data is cleared after each test runs. - -### beforeAll - -- **Type:** `beforeAll(fn: () => Awaitable, timeout?: number)` - - Register a callback to be called once before starting to run all tests in the current context. - If the function returns a promise, Vitest waits until the promise resolve before running tests. - - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. - - ```ts - import { beforeAll } from 'vitest' - - beforeAll(async () => { - await startMocking() // called once before all tests run - }) - ``` - - Here the `beforeAll` ensures that the mock data is set up before tests run. - - Since Vitest v0.10.0, `beforeAll` also accepts an optional cleanup function (equivalent to `afterAll`). - - ```ts - import { beforeAll } from 'vitest' - - beforeAll(async () => { - // called once before all tests run - await startMocking() - - // clean up function, called once after all tests run - return async () => { - await stopMocking() - } - }) - ``` - -### afterAll - -- **Type:** `afterAll(fn: () => Awaitable, timeout?: number)` - - Register a callback to be called once after all tests have run in the current context. - If the function returns a promise, Vitest waits until the promise resolve before continuing. - - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. - - ```ts - import { afterAll } from 'vitest' - - afterAll(async () => { - await stopMocking() // this method is called after all tests run - }) - ``` - - Here the `afterAll` ensures that `stopMocking` method is called after all tests run. - -## Vi -Vitest provides utility functions to help you out through it's **vi** helper. You can `import { vi } from 'vitest'` or access it **globally** (when [globals configuration](/config/#globals) is **enabled**). - -### vi.advanceTimersByTime - -- **Type:** `(ms: number) => Vitest` - - Works just like `runAllTimers`, but will end after passed milliseconds. For example this will log `1, 2, 3` and will not throw: - - ```ts - let i = 0 - setInterval(() => console.log(++i), 50) - - vi.advanceTimersByTime(150) - ``` - -### vi.advanceTimersByTimeAsync - -- **Type:** `(ms: number) => Promise` - - Works just like `runAllTimersAsync`, but will end after passed milliseconds. This will include asynchronously set timers. For example this will log `1, 2, 3` and will not throw: - - ```ts - let i = 0 - setInterval(() => Promise.resolve().then(() => console.log(++i)), 50) - - await vi.advanceTimersByTimeAsync(150) - ``` - -### vi.advanceTimersToNextTimer - -- **Type:** `() => Vitest` - - Will call next available timer. Useful to make assertions between each timer call. You can chain call it to manage timers by yourself. - - ```ts - let i = 0 - setInterval(() => console.log(++i), 50) - - vi.advanceTimersToNextTimer() // log 1 - .advanceTimersToNextTimer() // log 2 - .advanceTimersToNextTimer() // log 3 - ``` - -### vi.advanceTimersToNextTimerAsync - -- **Type:** `() => Promise` - - Will call next available timer even if it was set asynchronously. Useful to make assertions between each timer call. You can chain call it to manage timers by yourself. - - ```ts - let i = 0 - setInterval(() => Promise.resolve().then(() => console.log(++i)), 50) - - vi.advanceTimersToNextTimerAsync() // log 1 - .advanceTimersToNextTimerAsync() // log 2 - .advanceTimersToNextTimerAsync() // log 3 - ``` - -### vi.getTimerCount - -- **Type:** `() => number` - - Get the number of waiting timers. - -### vi.clearAllMocks - - Will call [`.mockClear()`](/api/#mockclear) on all spies. This will clear mock history, but not reset its implementation to the default one. - -### vi.clearAllTimers - - Removes all timers that are scheduled to run. These timers will never run in the future. - -### vi.dynamicImportSettled - - Wait for all imports to load. Useful, if you have a synchronous call that starts importing a module, that you cannot wait otherwise. - -### vi.fn - -- **Type:** `(fn?: Function) => CallableMockInstance` - - Creates a spy on a function, though can be initiated without one. Every time a function is invoked, it stores its call arguments, returns, and instances. Also, you can manipulate its behavior with [methods](#mockinstance-methods). - If no function is given, mock will return `undefined`, when invoked. - - ```ts - const getApples = vi.fn(() => 0) - - getApples() - - expect(getApples).toHaveBeenCalled() - expect(getApples).toHaveReturnedWith(0) - - getApples.mockReturnValueOnce(5) - - const res = getApples() - expect(res).toBe(5) - expect(getApples).toHaveNthReturnedWith(2, 5) - ``` - -### vi.getMockedSystemTime - -- **Type**: `() => Date | null` - - Returns mocked current date that was set using `setSystemTime`. If date is not mocked, will return `null`. - -### vi.getRealSystemTime - -- **Type**: `() => number` - - When using `vi.useFakeTimers`, `Date.now` calls are mocked. If you need to get real time in milliseconds, you can call this function. - -### vi.mock - -- **Type**: `(path: string, factory?: () => unknown) => void` - - Substitutes all imported modules from provided `path` with another module. You can use configured Vite aliases inside a path. The call to `vi.mock` is hoisted, so it doesn't matter where you call it. It will always be executed before all imports. - - ::: warning - `vi.mock` works only for modules that were imported with the `import` keyword. It doesn't work with `require`. - - Vitest statically analyzes your files to hoist `vi.mock`. It means that you cannot use `vi` that was not imported directly from `vitest` package (for example, from some utility file). To fix this, always use `vi.mock` with `vi` imported from `vitest`, or enable [`globals`](/config/#globals) config option. - ::: - - If `factory` is defined, all imports will return its result. Vitest calls factory only once and caches result for all subsequent imports until [`vi.unmock`](#vi-unmock) or [`vi.doUnmock`](#vi-dounmock) is called. - - Unlike in `jest`, the factory can be asynchronous, so you can use [`vi.importActual`](#vi-importactual) or a helper, received as the first argument, inside to get the original module. - - ```ts - vi.mock('./path/to/module.js', async (importOriginal) => { - const mod = await importOriginal() - return { - ...mod, - // replace some exports - namedExport: vi.fn(), - } - }) - ``` - - ::: warning - `vi.mock` is hoisted (in other words, _moved_) to **top of the file**. It means that whenever you write it (be it inside `beforeEach` or `test`), it will actually be called before that. - - This also means that you cannot use any variables inside the factory that are defined outside the factory. - - If you need to use variables inside the factory, try [`vi.doMock`](#vi-domock). It works the same way but isn't hoisted. Beware that it only mocks subsequent imports. - ::: - - ::: warning - If you are mocking a module with default export, you will need to provide a `default` key within the returned factory function object. This is an ES modules-specific caveat, therefore `jest` documentation may differ as `jest` uses CommonJS modules. For example, - - ```ts - vi.mock('./path/to/module.js', () => { - return { - default: { myDefaultKey: vi.fn() }, - namedExport: vi.fn(), - // etc... - } - }) - ``` - ::: - - If there is a `__mocks__` folder alongside a file that you are mocking, and the factory is not provided, Vitest will try to find a file with the same name in the `__mocks__` subfolder and use it as an actual module. If you are mocking a dependency, Vitest will try to find a `__mocks__` folder in the [root](/config/#root) of the project (default is `process.cwd()`). - - For example, you have this file structure: - - ``` - - __mocks__ - - axios.js - - src - __mocks__ - - increment.js - - increment.js - - tests - - increment.test.js - ``` - - If you call `vi.mock` in a test file without a factory provided, it will find a file in the `__mocks__` folder to use as a module: - - ```ts - // increment.test.js - import { vi } from 'vitest' - // axios is a default export from `__mocks__/axios.js` - import axios from 'axios' - // increment is a named export from `src/__mocks__/increment.js` - import { increment } from '../increment.js' - - vi.mock('axios') - vi.mock('../increment.js') - - axios.get(`/apples/${increment(1)}`) - ``` - - ::: warning - Beware that if you don't call `vi.mock`, modules **are not** mocked automatically. - ::: - - If there is no `__mocks__` folder or a factory provided, Vitest will import the original module and auto-mock all its exports. For the rules applied, see [algorithm](/guide/mocking#automocking-algorithm). - -### vi.doMock - -- **Type**: `(path: string, factory?: () => unknown) => void` - - The same as [`vi.mock`](#vi-mock), but it's not hoisted at the top of the file, so you can reference variables in the global file scope. The next import of the module will be mocked. This will not mock modules that were imported before this was called. - -```ts -// ./increment.js -export const increment = number => number + 1 -``` - -```ts -import { beforeEach, test } from 'vitest' -import { increment } from './increment.js' - -// the module is not mocked, because vi.doMock is not called yet -increment(1) === 2 - -let mockedIncrement = 100 - -beforeEach(() => { - // simple doMock doesn't clear the previous cache, so we need to clear it manually here - vi.doUnmock('./increment.js') - // you can access variables inside a factory - vi.doMock('./increment.js', () => ({ increment: () => mockedIncrement++ })) -}) - -test('importing the next module imports mocked one', () => { - // original import WAS NOT MOCKED, because vi.doMock is evaluated AFTER imports - expect(increment(1)).toBe(2) - const { increment: mockedIncrement } = await import('./increment.js') - // new import returns mocked module - expect(mockedIncrement(1)).toBe(101) - expect(mockedIncrement(1)).toBe(102) - expect(mockedIncrement(1)).toBe(103) -}) -``` - -### vi.mocked - -- **Type**: `(obj: T, deep?: boolean) => MaybeMockedDeep` -- **Type**: `(obj: T, options?: { partial?: boolean; deep?: boolean }) => MaybePartiallyMockedDeep` - - Type helper for TypeScript. In reality just returns the object that was passed. - - When `partial` is `true` it will expect a `Partial` as a return value. - ```ts - import example from './example' - vi.mock('./example') - - test('1+1 equals 2', async () => { - vi.mocked(example.calc).mockRestore() - - const res = example.calc(1, '+', 1) - - expect(res).toBe(2) - }) - ``` - -### vi.importActual - -- **Type**: `(path: string) => Promise` - - Imports module, bypassing all checks if it should be mocked. Can be useful if you want to mock module partially. - - ```ts - vi.mock('./example', async () => { - const axios = await vi.importActual('./example') - - return { ...axios, get: vi.fn() } - }) - ``` - -### vi.importMock - -- **Type**: `(path: string) => Promise>` - - Imports a module with all of its properties (including nested properties) mocked. Follows the same rules that [`vi.mock`](#vi-mock) follows. For the rules applied, see [algorithm](/guide/mocking#automocking-algorithm). - -### vi.resetAllMocks - - Will call [`.mockReset()`](/api/#mockreset) on all spies. This will clear mock history and reset its implementation to an empty function (will return `undefined`). - -### vi.resetConfig - -- **Type**: `RuntimeConfig` - - If [`vi.setConfig`](/api/#vi-setconfig) was called before, this will reset config to the original state. - -### vi.resetModules - -- **Type**: `() => Vitest` - - Resets modules registry by clearing cache of all modules. Might be useful to isolate modules where local state conflicts between tests. - - ```ts - import { vi } from 'vitest' - - beforeAll(() => { - vi.resetModules() - }) - - test('change state', async () => { - const mod = await import('./some/path') - mod.changeLocalState('new value') - expect(mod.getlocalState()).toBe('new value') - }) - - test('module has old state', async () => { - const mod = await import('./some/path') - expect(mod.getlocalState()).toBe('old value') - }) - ``` - -::: warning -Does not reset mocks registry. To clear mocks registry, use [`vi.unmock`](#vi-unmock) or [`vi.doUnmock`](#vi-dounmock). -::: - -### vi.restoreAllMocks - - Will call [`.mockRestore()`](/api/#mockrestore) on all spies. This will clear mock history and reset its implementation to the original one. - -### vi.restoreCurrentDate - -- **Type:** `() => void` - - Restores `Date` back to its native implementation. - -### vi.stubEnv - -- **Type:** `(name: string, value: string) => Vitest` -- **Version:** Since Vitest 0.26.0 - - Changes the value of environmental variable on `process.env` and `import.meta.env`. You can restore its value by calling `vi.unstubAllEnvs`. - -```ts -import { vi } from 'vitest' - -// `process.env.NODE_ENV` and `import.meta.env.NODE_ENV` -// are "development" before calling "vi.stubEnv" - -vi.stubEnv('NODE_ENV', 'production') - -process.env.NODE_ENV === 'production' -import.meta.env.NODE_ENV === 'production' -// doesn't change other envs -import.meta.env.MODE === 'development' -``` - -:::tip -You can also change the value by simply assigning it, but you won't be able to use `vi.unstubAllEnvs` to restore previous value: - -```ts -import.meta.env.MODE = 'test' -``` -::: - -:::warning -Vitest transforms all `import.meta.env` calls into `process.env`, so they can be easily changed at runtime. Node.js only supports string values as env parameters, while Vite supports several built-in envs as boolean (namely, `SSR`, `DEV`, `PROD`). To mimic Vite, set "truthy" values as env: `''` instead of `false`, and `'1'` instead of `true`. - -But beware that you cannot rely on `import.meta.env.DEV === false` in this case. Use `!import.meta.env.DEV`. This also affects simple assigning, not just `vi.stubEnv` method. -::: - -### vi.unstubAllEnvs - -- **Type:** `() => Vitest` -- **Version:** Since Vitest 0.26.0 - - Restores all `import.meta.env` and `process.env` values that were changed with `vi.stubEnv`. When it's called for the first time, Vitest remembers the original value and will store it, until `unstubAllEnvs` is called again. - -```ts -import { vi } from 'vitest' - -// `process.env.NODE_ENV` and `import.meta.env.NODE_ENV` -// are "development" before calling stubEnv - -vi.stubEnv('NODE_ENV', 'production') - -process.env.NODE_ENV === 'production' -import.meta.env.NODE_ENV === 'production' - -vi.stubEnv('NODE_ENV', 'staging') - -process.env.NODE_ENV === 'staging' -import.meta.env.NODE_ENV === 'staging' - -vi.unstubAllEnvs() - -// restores to the value that were stored before the first "stubEnv" call -process.env.NODE_ENV === 'development' -import.meta.env.NODE_ENV === 'development' -``` - -### vi.stubGlobal - -- **Type:** `(name: string | number | symbol, value: uknown) => Vitest` - - Changes the value of global variable. You can restore its original value by calling `vi.unstubAllGlobals`. - -```ts -import { vi } from 'vitest' - -// `innerWidth` is "0" before callling stubGlobal - -vi.stubGlobal('innerWidth', 100) - -innerWidth === 100 -globalThis.innerWidth === 100 -// if you are using jsdom or happy-dom -window.innerWidth === 100 -``` - -:::tip -You can also change the value by simply assigning it to `globalThis` or `window` (if you are using `jsdom` or `happy-dom` environment), but you won't be able to use `vi.unstubAllGlobals` to restore original value: - -```ts -globalThis.innerWidth = 100 -// if you are using jsdom or happy-dom -window.innerWidth = 100 -``` -::: - -### vi.unstubAllGlobals - -- **Type:** `() => Vitest` -- **Version:** Since Vitest 0.26.0 - - Restores all global values on `globalThis`/`global` (and `window`/`top`/`self`/`parent`, if you are using `jsdom` or `happy-dom` environment) that were changed with `vi.stubGlobal`. When it's called for the first time, Vitest remembers the original value and will store it, until `unstubAllGlobals` is called again. - -```ts -import { vi } from 'vitest' - -const Mock = vi.fn() - -// IntersectionObserver is "undefined" before calling "stubGlobal" - -vi.stubGlobal('IntersectionObserver', Mock) - -IntersectionObserver === Mock -global.IntersectionObserver === Mock -globalThis.IntersectionObserver === Mock -// if you are using jsdom or happy-dom -window.IntersectionObserver === Mock - -vi.unstubAllGlobals() - -globalThis.IntersectionObserver === undefined -'IntersectionObserver' in globalThis === false -// throws ReferenceError, because it's not defined -IntersectionObserver === undefined -``` - -### vi.runAllTicks - -- **Type:** `() => Vitest` - - Calls every microtask that was queued by `proccess.nextTick`. This will also run all microtasks scheduled by themselves. - -### vi.runAllTimers - -- **Type:** `() => Vitest` - - This method will invoke every initiated timer until the timers queue is empty. It means that every timer called during `runAllTimers` will be fired. If you have an infinite interval, - it will throw after 10 000 tries. For example this will log `1, 2, 3`: - - ```ts - let i = 0 - setTimeout(() => console.log(++i)) - const interval = setInterval(() => { - console.log(++i) - if (i === 3) - clearInterval(interval) - - }, 50) - - vi.runAllTimers() - ``` - -### vi.runAllTimersAsync - -- **Type:** `() => Promise` - - This method will asynchronously invoke every initiated timer until the timers queue is empty. It means that every timer called during `runAllTimersAsync` will be fired even asynchronous timers. If you have an infinite interval, - it will throw after 10 000 tries. For example this will log `result`: - - ```ts - setTimeout(async () => { - console.log(await Promise.resolve('result')) - }, 100) - - await vi.runAllTimersAsync() - ``` - -### vi.runOnlyPendingTimers - -- **Type:** `() => Vitest` - - This method will call every timer that was initiated after `vi.useFakeTimers()` call. It will not fire any timer that was initiated during its call. For example this will only log `1`: - - ```ts - let i = 0 - setInterval(() => console.log(++i), 50) - - vi.runOnlyPendingTimers() - ``` - -### vi.runOnlyPendingTimersAsync - -- **Type:** `() => Promise` - - This method will asynchronously call every timer that was initiated after `vi.useFakeTimers()` call, even asynchronous ones. It will not fire any timer that was initiated during its call. For example this will log `2, 3, 3, 1`: - - ```ts - setTimeout(() => { - console.log(1) - }, 100) - setTimeout(() => { - Promise.resolve().then(() => { - console.log(2) - setInterval(() => { - console.log(3) - }, 40) - }) - }, 10) - - await vi.runOnlyPendingTimersAsync() - ``` - -### vi.setSystemTime - -- **Type**: `(date: string | number | Date) => void` - - Sets current date to the one that was passed. All `Date` calls will return this date. - - Useful if you need to test anything that depends on the current date - for example [luxon](https://github.com/moment/luxon/) calls inside your code. - - ```ts - const date = new Date(1998, 11, 19) - - vi.useFakeTimers() - vi.setSystemTime(date) - - expect(Date.now()).toBe(date.valueOf()) - - vi.useRealTimers() - ``` - -### vi.setConfig - -- **Type**: `RuntimeConfig` - - Updates config for the current test file. You can only affect values that are used, when executing tests. - -### vi.spyOn - -- **Type:** `(object: T, method: K, accessType?: 'get' | 'set') => MockInstance` - - Creates a spy on a method or getter/setter of an object. - - ```ts - let apples = 0 - const obj = { - getApples: () => 13, - } - - const spy = vi.spyOn(obj, 'getApples').mockImplementation(() => apples) - apples = 1 - - expect(obj.getApples()).toBe(1) - - expect(spy).toHaveBeenCalled() - expect(spy).toHaveReturnedWith(1) - ``` - -### vi.stubGlobal - -- **Type**: `(key: keyof globalThis & Window, value: any) => Vitest` - - Puts a value on global variable. If you are using `jsdom` or `happy-dom`, also puts the value on `window` object. - - Read more in ["Mocking Globals" section](/guide/mocking.html#globals). - -### vi.unmock - -- **Type**: `(path: string) => void` - - Removes module from the mocked registry. All calls to import will return the original module even if it was mocked before. This call is hoisted (moved) to the top of the file, so it will only unmock modules that were defined in `setupFiles`, for example. - -### vi.doUnmock - -- **Type**: `(path: string) => void` - - The same as [`vi.unmock`](#vi-unmock), but is not hoisted to the top of the file. The next import of the module will import the original module instead of the mock. This will not unmock previously imported modules. - -```ts -// ./increment.js -export const increment = number => number + 1 -``` - -```ts -import { increment } from './increment.js' - -// increment is already mocked, because vi.mock is hoisted -increment(1) === 100 - -// this is hoisted, and factory is called before the import on line 1 -vi.mock('./increment.js', () => ({ increment: () => 100 })) - -// all calls are mocked, and `increment` always returns 100 -increment(1) === 100 -increment(30) === 100 - -// this is not hoisted, so other import will return unmocked module -vi.doUnmock('./increment.js') - -// this STILL returns 100, because `vi.doUnmock` doesn't reevaluate a module -increment(1) === 100 -increment(30) === 100 - -// the next import is unmocked, now `increment` is the original function that returns count + 1 -const { increment: unmockedIncrement } = await import('./increment.js') - -unmockedIncrement(1) === 2 -unmockedIncrement(30) === 31 -``` - -### vi.useFakeTimers - -- **Type:** `() => Vitest` - - To enable mocking timers, you need to call this method. It will wrap all further calls to timers (such as `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `nextTick`, `setImmediate`, `clearImmediate`, and `Date`), until [`vi.useRealTimers()`](#vi-userealtimers) is called. - - The implementation is based internally on [`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers). - -### vi.useRealTimers - -- **Type:** `() => Vitest` - - When timers are run out, you may call this method to return mocked timers to its original implementations. All timers that were run before will not be restored. - -## MockInstance Methods - -### getMockName - -- **Type:** `() => string` - - Use it to return the name given to mock with method `.mockName(name)`. - -### mockClear - -- **Type:** `() => MockInstance` - - Clears all information about every call. After calling it, [`spy.mock.calls`](#mock-calls), [`spy.mock.results`](#mock-results) will return empty arrays. It is useful if you need to clean up spy between different assertions. - - If you want this method to be called before each test automatically, you can enable [`clearMocks`](/config/#clearmocks) setting in config. - - -### mockName - -- **Type:** `(name: string) => MockInstance` - - Sets internal mock name. Useful to see what mock has failed the assertion. - -### mockImplementation - -- **Type:** `(fn: Function) => MockInstance` - - Accepts a function that will be used as an implementation of the mock. - - For example: - - ```ts - const mockFn = vi.fn().mockImplementation(apples => apples + 1) - // or: vi.fn(apples => apples + 1); - - const NelliesBucket = mockFn(0) - const BobsBucket = mockFn(1) - - NelliesBucket === 1 // true - BobsBucket === 2 // true - - mockFn.mock.calls[0][0] === 0 // true - mockFn.mock.calls[1][0] === 1 // true - ``` - -### mockImplementationOnce - -- **Type:** `(fn: Function) => MockInstance` - - Accepts a function that will be used as an implementation of the mock for one call to the mocked function. Can be chained so that multiple function calls produce different results. - - ```ts - const myMockFn = vi - .fn() - .mockImplementationOnce(() => true) - .mockImplementationOnce(() => false) - - myMockFn() // true - myMockFn() // false - ``` - - When the mocked function runs out of implementations, it will invoke the default implementation that was set with `vi.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called: - - ```ts - const myMockFn = vi - .fn(() => 'default') - .mockImplementationOnce(() => 'first call') - .mockImplementationOnce(() => 'second call') - - // 'first call', 'second call', 'default', 'default' - console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()) - ``` - -### mockRejectedValue - -- **Type:** `(value: any) => MockInstance` - - Accepts an error that will be rejected, when async function will be called. - - ```ts - test('async test', async () => { - const asyncMock = vi.fn().mockRejectedValue(new Error('Async error')) - - await asyncMock() // throws "Async error" - }) - ``` - -### mockRejectedValueOnce - -- **Type:** `(value: any) => MockInstance` - - Accepts a value that will be rejected for one call to the mock function. If chained, every consecutive call will reject passed value. - - ```ts - test('async test', async () => { - const asyncMock = vi - .fn() - .mockResolvedValueOnce('first call') - .mockRejectedValueOnce(new Error('Async error')) - - await asyncMock() // first call - await asyncMock() // throws "Async error" - }) - ``` - -### mockReset - -- **Type:** `() => MockInstance` - - Does what `mockClear` does and makes inner implementation as an empty function (returning `undefined`, when invoked). This is useful when you want to completely reset a mock back to its initial state. - - If you want this method to be called before each test automatically, you can enable [`mockReset`](/config/#mockreset) setting in config. - -### mockRestore - -- **Type:** `() => MockInstance` - - Does what `mockReset` does and restores inner implementation to the original function. - - Note that restoring mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. Restoring a `vi.fn(impl)` will restore implementation to `impl`. - - If you want this method to be called before each test automatically, you can enable [`restoreMocks`](/config/#restoreMocks) setting in config. - -### mockResolvedValue - -- **Type:** `(value: any) => MockInstance` - - Accepts a value that will be resolved, when async function will be called. - - ```ts - test('async test', async () => { - const asyncMock = vi.fn().mockResolvedValue(43) - - await asyncMock() // 43 - }) - ``` - -### mockResolvedValueOnce - -- **Type:** `(value: any) => MockInstance` - - Accepts a value that will be resolved for one call to the mock function. If chained, every consecutive call will resolve passed value. - - ```ts - test('async test', async () => { - const asyncMock = vi - .fn() - .mockResolvedValue('default') - .mockResolvedValueOnce('first call') - .mockResolvedValueOnce('second call') - - await asyncMock() // first call - await asyncMock() // second call - await asyncMock() // default - await asyncMock() // default - }) - ``` - -### mockReturnThis - -- **Type:** `() => MockInstance` - - Sets inner implementation to return `this` context. - -### mockReturnValue - -- **Type:** `(value: any) => MockInstance` - - Accepts a value that will be returned whenever the mock function is called. - - ```ts - const mock = vi.fn() - mock.mockReturnValue(42) - mock() // 42 - mock.mockReturnValue(43) - mock() // 43 - ``` - -### mockReturnValueOnce - -- **Type:** `(value: any) => MockInstance` - - Accepts a value that will be returned for one call to the mock function. If chained, every consecutive call will return passed value. When there are no more `mockReturnValueOnce` values to use, calls a function specified by `mockImplementation` or other `mockReturn*` methods. - - ```ts - const myMockFn = vi - .fn() - .mockReturnValue('default') - .mockReturnValueOnce('first call') - .mockReturnValueOnce('second call') - - // 'first call', 'second call', 'default', 'default' - console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()) - ``` - -## MockInstance Properties - -### mock.calls - -This is an array containing all arguments for each call. One item of the array is arguments of that call. - -If a function was invoked twice with the following arguments `fn(arg1, arg2)`, `fn(arg3, arg4)` in that order, then `mock.calls` will be: - -```js -[ - ['arg1', 'arg2'], - ['arg3', 'arg4'], -] -``` - -### mock.lastCall - -This contains the arguments of the last call. If spy wasn't called, will return `undefined`. - -### mock.results - -This is an array containing all values, that were `returned` from function. One item of the array is an object with properties `type` and `value`. Available types are: - -- `'return'` - function returned without throwing. -- `'throw'` - function threw a value. - -The `value` property contains returned value or thrown error. - -If function returned `result`, then threw an error, then `mock.results` will be: - -```js -[ - { - type: 'return', - value: 'result', - }, - { - type: 'throw', - value: Error, - }, -] -``` - -### mock.instances - -This is an array containing all instances that were instantiated when mock was called with a `new` keyword. Note, this is an actual context (`this`) of the function, not a return value. - -For example, if mock was instantiated with `new MyClass()`, then `mock.instances` will be an array of one value: - -```js -import { expect, vi } from 'vitest' - -const MyClass = vi.fn() - -const a = new MyClass() - -expect(MyClass.mock.instances[0]).toBe(a) -``` - -If you return a value from constructor, it will not be in `instances` array, but instead on `results`: - -```js -import { expect, vi } from 'vitest' - -const Spy = vi.fn(() => ({ method: vi.fn() })) - -const a = new Spy() - -expect(Spy.mock.instances[0]).not.toBe(a) -expect(Spy.mock.results[0]).toBe(a) -``` + Here the `afterAll` ensures that `stopMocking` method is called after all tests run. diff --git a/docs/api/mock.md b/docs/api/mock.md new file mode 100644 index 000000000000..cbdf294fcb47 --- /dev/null +++ b/docs/api/mock.md @@ -0,0 +1,272 @@ +# Mock Functions + +You can create a spy function (mock) to track its execution with `vi.fn` method. If you want to track a method on an already created object, you can use `vi.spyOn` method: + +```js +import { vi } from 'vitest' + +const fn = vi.fn() +fn('hello world') +fn.mock.calls[0] === ['hello world'] + +const market = { + getApples: () => 100 +} + +const getApplesSpy = vi.spyOn(market, 'getApples') +market.getApples() +getApplesSpy.mock.calls.length === 1 +``` + +You should use spy assertions (e.g., [`toHaveBeenCalled`](/api/expect#tohavebeencalled)) on [`expect`](/api/expect) to assert spy result. This API reference describes available properties and methods to manipulate spy behavior. + +## getMockName + +- **Type:** `() => string` + + Use it to return the name given to mock with method `.mockName(name)`. + +## mockClear + +- **Type:** `() => MockInstance` + + Clears all information about every call. After calling it, [`spy.mock.calls`](#mock-calls), [`spy.mock.results`](#mock-results) will return empty arrays. It is useful if you need to clean up spy between different assertions. + + If you want this method to be called before each test automatically, you can enable [`clearMocks`](/config/#clearmocks) setting in config. + + +## mockName + +- **Type:** `(name: string) => MockInstance` + + Sets internal mock name. Useful to see what mock has failed the assertion. + +## mockImplementation + +- **Type:** `(fn: Function) => MockInstance` + + Accepts a function that will be used as an implementation of the mock. + + For example: + + ```ts + const mockFn = vi.fn().mockImplementation(apples => apples + 1) + // or: vi.fn(apples => apples + 1); + + const NelliesBucket = mockFn(0) + const BobsBucket = mockFn(1) + + NelliesBucket === 1 // true + BobsBucket === 2 // true + + mockFn.mock.calls[0][0] === 0 // true + mockFn.mock.calls[1][0] === 1 // true + ``` + +## mockImplementationOnce + +- **Type:** `(fn: Function) => MockInstance` + + Accepts a function that will be used as an implementation of the mock for one call to the mocked function. Can be chained so that multiple function calls produce different results. + + ```ts + const myMockFn = vi + .fn() + .mockImplementationOnce(() => true) + .mockImplementationOnce(() => false) + + myMockFn() // true + myMockFn() // false + ``` + + When the mocked function runs out of implementations, it will invoke the default implementation that was set with `vi.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called: + + ```ts + const myMockFn = vi + .fn(() => 'default') + .mockImplementationOnce(() => 'first call') + .mockImplementationOnce(() => 'second call') + + // 'first call', 'second call', 'default', 'default' + console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()) + ``` + +## mockRejectedValue + +- **Type:** `(value: any) => MockInstance` + + Accepts an error that will be rejected, when async function will be called. + + ```ts + const asyncMock = vi.fn().mockRejectedValue(new Error('Async error')) + + await asyncMock() // throws "Async error" + ``` + +## mockRejectedValueOnce + +- **Type:** `(value: any) => MockInstance` + + Accepts a value that will be rejected for one call to the mock function. If chained, every consecutive call will reject passed value. + + ```ts + const asyncMock = vi + .fn() + .mockResolvedValueOnce('first call') + .mockRejectedValueOnce(new Error('Async error')) + + await asyncMock() // first call + await asyncMock() // throws "Async error" + ``` + +## mockReset + +- **Type:** `() => MockInstance` + + Does what `mockClear` does and makes inner implementation an empty function (returning `undefined`, when invoked). This is useful when you want to completely reset a mock back to its initial state. + + If you want this method to be called before each test automatically, you can enable [`mockReset`](/config/#mockreset) setting in config. + +## mockRestore + +- **Type:** `() => MockInstance` + + Does what `mockReset` does and restores inner implementation to the original function. + + Note that restoring mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. Restoring a `vi.fn(impl)` will restore implementation to `impl`. + + If you want this method to be called before each test automatically, you can enable [`restoreMocks`](/config/#restoreMocks) setting in config. + +## mockResolvedValue + +- **Type:** `(value: any) => MockInstance` + + Accepts a value that will be resolved, when async function will be called. + + ```ts + const asyncMock = vi.fn().mockResolvedValue(43) + + await asyncMock() // 43 + ``` + +## mockResolvedValueOnce + +- **Type:** `(value: any) => MockInstance` + + Accepts a value that will be resolved for one call to the mock function. If chained, every consecutive call will resolve passed value. + + ```ts + const asyncMock = vi + .fn() + .mockResolvedValue('default') + .mockResolvedValueOnce('first call') + .mockResolvedValueOnce('second call') + + await asyncMock() // first call + await asyncMock() // second call + await asyncMock() // default + await asyncMock() // default + ``` + +## mockReturnThis + +- **Type:** `() => MockInstance` + + Sets inner implementation to return `this` context. + +## mockReturnValue + +- **Type:** `(value: any) => MockInstance` + + Accepts a value that will be returned whenever the mock function is called. + + ```ts + const mock = vi.fn() + mock.mockReturnValue(42) + mock() // 42 + mock.mockReturnValue(43) + mock() // 43 + ``` + +## mockReturnValueOnce + +- **Type:** `(value: any) => MockInstance` + + Accepts a value that will be returned for one call to the mock function. If chained, every consecutive call will return the passed value. When there are no more `mockReturnValueOnce` values to use, call a function specified by `mockImplementation` or other `mockReturn*` methods. + + ```ts + const myMockFn = vi + .fn() + .mockReturnValue('default') + .mockReturnValueOnce('first call') + .mockReturnValueOnce('second call') + + // 'first call', 'second call', 'default', 'default' + console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()) + ``` + +## mock.calls + +This is an array containing all arguments for each call. One item of the array is the arguments of that call. + +If a function was invoked twice with the following arguments `fn(arg1, arg2)`, `fn(arg3, arg4)` in that order, then `mock.calls` will be: + +```js +[ + ['arg1', 'arg2'], + ['arg3', 'arg4'], +] +``` + +## mock.lastCall + +This contains the arguments of the last call. If spy wasn't called, will return `undefined`. + +## mock.results + +This is an array containing all values, that were `returned` from the function. One item of the array is an object with properties `type` and `value`. Available types are: + +- `'return'` - function returned without throwing. +- `'throw'` - function threw a value. + +The `value` property contains returned value or thrown error. + +If function returned `result`, then threw an error, then `mock.results` will be: + +```js +[ + { + type: 'return', + value: 'result', + }, + { + type: 'throw', + value: Error, + }, +] +``` + +## mock.instances + +This is an array containing all instances that were instantiated when mock was called with a `new` keyword. Note, this is an actual context (`this`) of the function, not a return value. + +::: warning +If mock was instantiated with `new MyClass()`, then `mock.instances` will be an array with one value: + +```js +const MyClass = vi.fn() +const a = new MyClass() + +MyClass.mock.instances[0] === a +``` + +If you return a value from constructor, it will not be in `instances` array, but instead inside `results`: + +```js +const Spy = vi.fn(() => ({ method: vi.fn() })) +const a = new Spy() + +Spy.mock.instances[0] !== a +Spy.mock.results[0] === a +``` +::: \ No newline at end of file diff --git a/docs/api/vi.md b/docs/api/vi.md new file mode 100644 index 000000000000..f308dad5f9cf --- /dev/null +++ b/docs/api/vi.md @@ -0,0 +1,641 @@ +# Vi + +Vitest provides utility functions to help you out through its `vi` helper. You can access it globally (when [globals configuration](/config/#globals) is **enabled**), or import from `vitest`: + +```js +import { vi } from 'vitest' +``` + +## vi.advanceTimersByTime + +- **Type:** `(ms: number) => Vitest` + + Works just like `runAllTimers`, but will end after passed milliseconds. For example this will log `1, 2, 3` and will not throw: + + ```ts + let i = 0 + setInterval(() => console.log(++i), 50) + + vi.advanceTimersByTime(150) + ``` + +### vi.advanceTimersByTimeAsync + +- **Type:** `(ms: number) => Promise` + + Works just like `runAllTimersAsync`, but will end after passed milliseconds. This will include asynchronously set timers. For example this will log `1, 2, 3` and will not throw: + + ```ts + let i = 0 + setInterval(() => Promise.resolve().then(() => console.log(++i)), 50) + + await vi.advanceTimersByTimeAsync(150) + ``` + +## vi.advanceTimersToNextTimer + +- **Type:** `() => Vitest` + + Will call next available timer. Useful to make assertions between each timer call. You can chain call it to manage timers by yourself. + + ```ts + let i = 0 + setInterval(() => console.log(++i), 50) + + vi.advanceTimersToNextTimer() // log 1 + .advanceTimersToNextTimer() // log 2 + .advanceTimersToNextTimer() // log 3 + ``` + +### vi.advanceTimersToNextTimerAsync + +- **Type:** `() => Promise` + + Will call next available timer even if it was set asynchronously. Useful to make assertions between each timer call. You can chain call it to manage timers by yourself. + + ```ts + let i = 0 + setInterval(() => Promise.resolve().then(() => console.log(++i)), 50) + + vi.advanceTimersToNextTimerAsync() // log 1 + .advanceTimersToNextTimerAsync() // log 2 + .advanceTimersToNextTimerAsync() // log 3 + ``` + +## vi.getTimerCount + +- **Type:** `() => number` + + Get the number of waiting timers. + +## vi.clearAllMocks + + Will call [`.mockClear()`](/api/#mockclear) on all spies. This will clear mock history, but not reset its implementation to the default one. + +## vi.clearAllTimers + + Removes all timers that are scheduled to run. These timers will never run in the future. + +## vi.dynamicImportSettled + + Wait for all imports to load. Useful, if you have a synchronous call that starts importing a module, that you cannot wait otherwise. + +## vi.fn + +- **Type:** `(fn?: Function) => CallableMockInstance` + + Creates a spy on a function, though can be initiated without one. Every time a function is invoked, it stores its call arguments, returns, and instances. Also, you can manipulate its behavior with [methods](#mockinstance-methods). + If no function is given, mock will return `undefined`, when invoked. + + ```ts + const getApples = vi.fn(() => 0) + + getApples() + + expect(getApples).toHaveBeenCalled() + expect(getApples).toHaveReturnedWith(0) + + getApples.mockReturnValueOnce(5) + + const res = getApples() + expect(res).toBe(5) + expect(getApples).toHaveNthReturnedWith(2, 5) + ``` + +## vi.getMockedSystemTime + +- **Type**: `() => Date | null` + + Returns mocked current date that was set using `setSystemTime`. If date is not mocked, will return `null`. + +## vi.getRealSystemTime + +- **Type**: `() => number` + + When using `vi.useFakeTimers`, `Date.now` calls are mocked. If you need to get real time in milliseconds, you can call this function. + +## vi.mock + +- **Type**: `(path: string, factory?: () => unknown) => void` + + Substitutes all imported modules from provided `path` with another module. You can use configured Vite aliases inside a path. The call to `vi.mock` is hoisted, so it doesn't matter where you call it. It will always be executed before all imports. + + ::: warning + `vi.mock` works only for modules that were imported with the `import` keyword. It doesn't work with `require`. + + Vitest statically analyzes your files to hoist `vi.mock`. It means that you cannot use `vi` that was not imported directly from `vitest` package (for example, from some utility file). To fix this, always use `vi.mock` with `vi` imported from `vitest`, or enable [`globals`](/config/#globals) config option. + ::: + + If `factory` is defined, all imports will return its result. Vitest calls factory only once and caches result for all subsequent imports until [`vi.unmock`](#vi-unmock) or [`vi.doUnmock`](#vi-dounmock) is called. + + Unlike in `jest`, the factory can be asynchronous, so you can use [`vi.importActual`](#vi-importactual) or a helper, received as the first argument, inside to get the original module. + + ```ts + vi.mock('./path/to/module.js', async (importOriginal) => { + const mod = await importOriginal() + return { + ...mod, + // replace some exports + namedExport: vi.fn(), + } + }) + ``` + + ::: warning + `vi.mock` is hoisted (in other words, _moved_) to **top of the file**. It means that whenever you write it (be it inside `beforeEach` or `test`), it will actually be called before that. + + This also means that you cannot use any variables inside the factory that are defined outside the factory. + + If you need to use variables inside the factory, try [`vi.doMock`](#vi-domock). It works the same way but isn't hoisted. Beware that it only mocks subsequent imports. + ::: + + ::: warning + If you are mocking a module with default export, you will need to provide a `default` key within the returned factory function object. This is an ES modules-specific caveat, therefore `jest` documentation may differ as `jest` uses CommonJS modules. For example, + + ```ts + vi.mock('./path/to/module.js', () => { + return { + default: { myDefaultKey: vi.fn() }, + namedExport: vi.fn(), + // etc... + } + }) + ``` + ::: + + If there is a `__mocks__` folder alongside a file that you are mocking, and the factory is not provided, Vitest will try to find a file with the same name in the `__mocks__` subfolder and use it as an actual module. If you are mocking a dependency, Vitest will try to find a `__mocks__` folder in the [root](/config/#root) of the project (default is `process.cwd()`). + + For example, you have this file structure: + + ``` + - __mocks__ + - axios.js + - src + __mocks__ + - increment.js + - increment.js + - tests + - increment.test.js + ``` + + If you call `vi.mock` in a test file without a factory provided, it will find a file in the `__mocks__` folder to use as a module: + + ```ts + // increment.test.js + import { vi } from 'vitest' + // axios is a default export from `__mocks__/axios.js` + import axios from 'axios' + // increment is a named export from `src/__mocks__/increment.js` + import { increment } from '../increment.js' + + vi.mock('axios') + vi.mock('../increment.js') + + axios.get(`/apples/${increment(1)}`) + ``` + + ::: warning + Beware that if you don't call `vi.mock`, modules **are not** mocked automatically. + ::: + + If there is no `__mocks__` folder or a factory provided, Vitest will import the original module and auto-mock all its exports. For the rules applied, see [algorithm](/guide/mocking#automocking-algorithm). + +## vi.doMock + +- **Type**: `(path: string, factory?: () => unknown) => void` + + The same as [`vi.mock`](#vi-mock), but it's not hoisted at the top of the file, so you can reference variables in the global file scope. The next import of the module will be mocked. This will not mock modules that were imported before this was called. + +```ts +// ./increment.js +export const increment = number => number + 1 +``` + +```ts +import { beforeEach, test } from 'vitest' +import { increment } from './increment.js' + +// the module is not mocked, because vi.doMock is not called yet +increment(1) === 2 + +let mockedIncrement = 100 + +beforeEach(() => { + // simple doMock doesn't clear the previous cache, so we need to clear it manually here + vi.doUnmock('./increment.js') + // you can access variables inside a factory + vi.doMock('./increment.js', () => ({ increment: () => mockedIncrement++ })) +}) + +test('importing the next module imports mocked one', () => { + // original import WAS NOT MOCKED, because vi.doMock is evaluated AFTER imports + expect(increment(1)).toBe(2) + const { increment: mockedIncrement } = await import('./increment.js') + // new import returns mocked module + expect(mockedIncrement(1)).toBe(101) + expect(mockedIncrement(1)).toBe(102) + expect(mockedIncrement(1)).toBe(103) +}) +``` + +## vi.mocked + +- **Type**: `(obj: T, deep?: boolean) => MaybeMockedDeep` +- **Type**: `(obj: T, options?: { partial?: boolean; deep?: boolean }) => MaybePartiallyMockedDeep` + + Type helper for TypeScript. In reality just returns the object that was passed. + + When `partial` is `true` it will expect a `Partial` as a return value. + ```ts + import example from './example' + vi.mock('./example') + + test('1+1 equals 2', async () => { + vi.mocked(example.calc).mockRestore() + + const res = example.calc(1, '+', 1) + + expect(res).toBe(2) + }) + ``` + +## vi.importActual + +- **Type**: `(path: string) => Promise` + + Imports module, bypassing all checks if it should be mocked. Can be useful if you want to mock module partially. + + ```ts + vi.mock('./example', async () => { + const axios = await vi.importActual('./example') + + return { ...axios, get: vi.fn() } + }) + ``` + +## vi.importMock + +- **Type**: `(path: string) => Promise>` + + Imports a module with all of its properties (including nested properties) mocked. Follows the same rules that [`vi.mock`](#vi-mock) follows. For the rules applied, see [algorithm](/guide/mocking#automocking-algorithm). + +## vi.resetAllMocks + + Will call [`.mockReset()`](/api/#mockreset) on all spies. This will clear mock history and reset its implementation to an empty function (will return `undefined`). + +## vi.resetConfig + +- **Type**: `RuntimeConfig` + + If [`vi.setConfig`](/api/#vi-setconfig) was called before, this will reset config to the original state. + +## vi.resetModules + +- **Type**: `() => Vitest` + + Resets modules registry by clearing cache of all modules. Might be useful to isolate modules where local state conflicts between tests. + + ```ts + import { vi } from 'vitest' + + beforeAll(() => { + vi.resetModules() + }) + + test('change state', async () => { + const mod = await import('./some/path') + mod.changeLocalState('new value') + expect(mod.getlocalState()).toBe('new value') + }) + + test('module has old state', async () => { + const mod = await import('./some/path') + expect(mod.getlocalState()).toBe('old value') + }) + ``` + +::: warning +Does not reset mocks registry. To clear mocks registry, use [`vi.unmock`](#vi-unmock) or [`vi.doUnmock`](#vi-dounmock). +::: + +## vi.restoreAllMocks + + Will call [`.mockRestore()`](/api/#mockrestore) on all spies. This will clear mock history and reset its implementation to the original one. + +## vi.restoreCurrentDate + +- **Type:** `() => void` + + Restores `Date` back to its native implementation. + +## vi.stubEnv + +- **Type:** `(name: string, value: string) => Vitest` +- **Version:** Since Vitest 0.26.0 + + Changes the value of environmental variable on `process.env` and `import.meta.env`. You can restore its value by calling `vi.unstubAllEnvs`. + +```ts +import { vi } from 'vitest' + +// `process.env.NODE_ENV` and `import.meta.env.NODE_ENV` +// are "development" before calling "vi.stubEnv" + +vi.stubEnv('NODE_ENV', 'production') + +process.env.NODE_ENV === 'production' +import.meta.env.NODE_ENV === 'production' +// doesn't change other envs +import.meta.env.MODE === 'development' +``` + +:::tip +You can also change the value by simply assigning it, but you won't be able to use `vi.unstubAllEnvs` to restore previous value: + +```ts +import.meta.env.MODE = 'test' +``` +::: + +:::warning +Vitest transforms all `import.meta.env` calls into `process.env`, so they can be easily changed at runtime. Node.js only supports string values as env parameters, while Vite supports several built-in envs as boolean (namely, `SSR`, `DEV`, `PROD`). To mimic Vite, set "truthy" values as env: `''` instead of `false`, and `'1'` instead of `true`. + +But beware that you cannot rely on `import.meta.env.DEV === false` in this case. Use `!import.meta.env.DEV`. This also affects simple assigning, not just `vi.stubEnv` method. +::: + +## vi.unstubAllEnvs + +- **Type:** `() => Vitest` +- **Version:** Since Vitest 0.26.0 + + Restores all `import.meta.env` and `process.env` values that were changed with `vi.stubEnv`. When it's called for the first time, Vitest remembers the original value and will store it, until `unstubAllEnvs` is called again. + +```ts +import { vi } from 'vitest' + +// `process.env.NODE_ENV` and `import.meta.env.NODE_ENV` +// are "development" before calling stubEnv + +vi.stubEnv('NODE_ENV', 'production') + +process.env.NODE_ENV === 'production' +import.meta.env.NODE_ENV === 'production' + +vi.stubEnv('NODE_ENV', 'staging') + +process.env.NODE_ENV === 'staging' +import.meta.env.NODE_ENV === 'staging' + +vi.unstubAllEnvs() + +// restores to the value that were stored before the first "stubEnv" call +process.env.NODE_ENV === 'development' +import.meta.env.NODE_ENV === 'development' +``` + +## vi.stubGlobal + +- **Type:** `(name: stirng | number | symbol, value: uknown) => Vitest` + + Changes the value of global variable. You can restore its original value by calling `vi.unstubAllGlobals`. + +```ts +import { vi } from 'vitest' + +// `innerWidth` is "0" before callling stubGlobal + +vi.stubGlobal('innerWidth', 100) + +innerWidth === 100 +globalThis.innerWidth === 100 +// if you are using jsdom or happy-dom +window.innerWidth === 100 +``` + +:::tip +You can also change the value by simply assigning it to `globalThis` or `window` (if you are using `jsdom` or `happy-dom` environment), but you won't be able to use `vi.unstubAllGlobals` to restore original value: + +```ts +globalThis.innerWidth = 100 +// if you are using jsdom or happy-dom +window.innerWidth = 100 +``` +::: + +## vi.unstubAllGlobals + +- **Type:** `() => Vitest` +- **Version:** Since Vitest 0.26.0 + + Restores all global values on `globalThis`/`global` (and `window`/`top`/`self`/`parent`, if you are using `jsdom` or `happy-dom` environment) that were changed with `vi.stubGlobal`. When it's called for the first time, Vitest remembers the original value and will store it, until `unstubAllGlobals` is called again. + +```ts +import { vi } from 'vitest' + +const Mock = vi.fn() + +// IntersectionObserver is "undefined" before calling "stubGlobal" + +vi.stubGlobal('IntersectionObserver', Mock) + +IntersectionObserver === Mock +global.IntersectionObserver === Mock +globalThis.IntersectionObserver === Mock +// if you are using jsdom or happy-dom +window.IntersectionObserver === Mock + +vi.unstubAllGlobals() + +globalThis.IntersectionObserver === undefined +'IntersectionObserver' in globalThis === false +// throws ReferenceError, because it's not defined +IntersectionObserver === undefined +``` + +## vi.runAllTicks + +- **Type:** `() => Vitest` + + Calls every microtask that was queued by `proccess.nextTick`. This will also run all microtasks scheduled by themselves. + +## vi.runAllTimers + +- **Type:** `() => Vitest` + + This method will invoke every initiated timer until the timers queue is empty. It means that every timer called during `runAllTimers` will be fired. If you have an infinite interval, + it will throw after 10 000 tries. For example this will log `1, 2, 3`: + + ```ts + let i = 0 + setTimeout(() => console.log(++i)) + const interval = setInterval(() => { + console.log(++i) + if (i === 3) + clearInterval(interval) + + }, 50) + + vi.runAllTimers() + ``` + +### vi.runAllTimersAsync + +- **Type:** `() => Promise` + + This method will asynchronously invoke every initiated timer until the timers queue is empty. It means that every timer called during `runAllTimersAsync` will be fired even asynchronous timers. If you have an infinite interval, + it will throw after 10 000 tries. For example this will log `result`: + + ```ts + setTimeout(async () => { + console.log(await Promise.resolve('result')) + }, 100) + + await vi.runAllTimersAsync() + ``` + +## vi.runOnlyPendingTimers + +- **Type:** `() => Vitest` + + This method will call every timer that was initiated after `vi.useFakeTimers()` call. It will not fire any timer that was initiated during its call. For example this will only log `1`: + + ```ts + let i = 0 + setInterval(() => console.log(++i), 50) + + vi.runOnlyPendingTimers() + ``` + +### vi.runOnlyPendingTimersAsync + +- **Type:** `() => Promise` + + This method will asynchronously call every timer that was initiated after `vi.useFakeTimers()` call, even asynchronous ones. It will not fire any timer that was initiated during its call. For example this will log `2, 3, 3, 1`: + + ```ts + setTimeout(() => { + console.log(1) + }, 100) + setTimeout(() => { + Promise.resolve().then(() => { + console.log(2) + setInterval(() => { + console.log(3) + }, 40) + }) + }, 10) + + await vi.runOnlyPendingTimersAsync() + ``` + +## vi.setSystemTime + +- **Type**: `(date: string | number | Date) => void` + + Sets current date to the one that was passed. All `Date` calls will return this date. + + Useful if you need to test anything that depends on the current date - for example [luxon](https://github.com/moment/luxon/) calls inside your code. + + ```ts + const date = new Date(1998, 11, 19) + + vi.useFakeTimers() + vi.setSystemTime(date) + + expect(Date.now()).toBe(date.valueOf()) + + vi.useRealTimers() + ``` + +## vi.setConfig + +- **Type**: `RuntimeConfig` + + Updates config for the current test file. You can only affect values that are used, when executing tests. + +## vi.spyOn + +- **Type:** `(object: T, method: K, accessType?: 'get' | 'set') => MockInstance` + + Creates a spy on a method or getter/setter of an object. + + ```ts + let apples = 0 + const obj = { + getApples: () => 13, + } + + const spy = vi.spyOn(obj, 'getApples').mockImplementation(() => apples) + apples = 1 + + expect(obj.getApples()).toBe(1) + + expect(spy).toHaveBeenCalled() + expect(spy).toHaveReturnedWith(1) + ``` + +## vi.stubGlobal + +- **Type**: `(key: keyof globalThis & Window, value: any) => Vitest` + + Puts a value on global variable. If you are using `jsdom` or `happy-dom`, also puts the value on `window` object. + + Read more in ["Mocking Globals" section](/guide/mocking.html#globals). + +## vi.unmock + +- **Type**: `(path: string) => void` + + Removes module from the mocked registry. All calls to import will return the original module even if it was mocked before. This call is hoisted (moved) to the top of the file, so it will only unmock modules that were defined in `setupFiles`, for example. + +## vi.doUnmock + +- **Type**: `(path: string) => void` + + The same as [`vi.unmock`](#vi-unmock), but is not hoisted to the top of the file. The next import of the module will import the original module instead of the mock. This will not unmock previously imported modules. + +```ts +// ./increment.js +export const increment = number => number + 1 +``` + +```ts +import { increment } from './increment.js' + +// increment is already mocked, because vi.mock is hoisted +increment(1) === 100 + +// this is hoisted, and factory is called before the import on line 1 +vi.mock('./increment.js', () => ({ increment: () => 100 })) + +// all calls are mocked, and `increment` always returns 100 +increment(1) === 100 +increment(30) === 100 + +// this is not hoisted, so other import will return unmocked module +vi.doUnmock('./increment.js') + +// this STILL returns 100, because `vi.doUnmock` doesn't reevaluate a module +increment(1) === 100 +increment(30) === 100 + +// the next import is unmocked, now `increment` is the original function that returns count + 1 +const { increment: unmockedIncrement } = await import('./increment.js') + +unmockedIncrement(1) === 2 +unmockedIncrement(30) === 31 +``` + +## vi.useFakeTimers + +- **Type:** `() => Vitest` + + To enable mocking timers, you need to call this method. It will wrap all further calls to timers (such as `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `nextTick`, `setImmediate`, `clearImmediate`, and `Date`), until [`vi.useRealTimers()`](#vi-userealtimers) is called. + + The implementation is based internally on [`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers). + +## vi.useRealTimers + +- **Type:** `() => Vitest` + + When timers are run out, you may call this method to return mocked timers to its original implementations. All timers that were run before will not be restored. diff --git a/docs/guide/extending-matchers.md b/docs/guide/extending-matchers.md index 9f5e4b7c6421..022329eb70f7 100644 --- a/docs/guide/extending-matchers.md +++ b/docs/guide/extending-matchers.md @@ -23,6 +23,23 @@ expect.extend({ }) ``` +If you are using TypeScript, you can extend default Matchers interface with the code bellow: + +```ts +interface CustomMatchers { + toBeFoo(): R +} + +declare global { + namespace Vi { + interface Assertion extends CustomMatchers {} + interface AsymmetricMatchersContaining extends CustomMatchers {} + } + + // Note: augmenting jest.Matchers interface will also work. +} +``` + The return value of a matcher should be compatible with the following interface: ```ts