From eda66e8639b577c433f0f58585b5a6edf5b830f8 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 31 Oct 2023 14:39:43 +0100 Subject: [PATCH 1/9] docs: prefer .js extension in the documentation (#4411) --- docs/api/vi.md | 7 +++---- docs/guide/cli.md | 2 +- packages/snapshot/README.md | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/api/vi.md b/docs/api/vi.md index d63cc86d6aed..382b9133cbbe 100644 --- a/docs/api/vi.md +++ b/docs/api/vi.md @@ -650,7 +650,6 @@ const interval = setInterval(() => { console.log(++i) if (i === 3) clearInterval(interval) - }, 50) vi.runAllTimers() @@ -868,15 +867,15 @@ To bypass this limitation, you can rewrite static imports into dynamic ones like ```diff callFunctionWithSideEffect() -- import { value } from './some/module.ts' -+ const { value } = await import('./some/module.ts') +- import { value } from './some/module.js' ++ const { value } = await import('./some/module.js') ``` When running `vitest`, you can do this automatically by using `vi.hoisted` method. ```diff - callFunctionWithSideEffect() -import { value } from './some/module.ts' +import { value } from './some/module.js' + vi.hoisted(() => callFunctionWithSideEffect()) ``` diff --git a/docs/guide/cli.md b/docs/guide/cli.md index c4569ec8a106..8a7a47e6e639 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -32,7 +32,7 @@ Alias to `vitest watch`. ### `vitest related` -Run only tests that cover a list of source files. Works with static imports (e.g., `import('./index.ts')` or `import index from './index.ts`), but not the dynamic ones (e.g., `import(filepath)`). All files should be relative to root folder. +Run only tests that cover a list of source files. Works with static imports (e.g., `import('./index.js')` or `import index from './index.js`), but not the dynamic ones (e.g., `import(filepath)`). All files should be relative to root folder. Useful to run with [`lint-staged`](https://github.com/okonet/lint-staged) or with your CI setup. diff --git a/packages/snapshot/README.md b/packages/snapshot/README.md index 82643f412c5b..a89d9fe59680 100644 --- a/packages/snapshot/README.md +++ b/packages/snapshot/README.md @@ -22,7 +22,7 @@ const environment = new NodeSnapshotEnvironment() // you need to implement this yourselves, // this depends on your runner function getCurrentFilepath() { - return '/file.spec.ts' + return '/file.spec.js' } function getCurrentTestName() { return 'test1' From 0db386dc1fe708791c0548e652592d4fd988d071 Mon Sep 17 00:00:00 2001 From: Marcelo Botega Fontana Date: Tue, 31 Oct 2023 14:45:40 +0100 Subject: [PATCH 2/9] fix: coverage.100 crash when using as an cli argument (#4346) --- packages/vitest/src/node/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vitest/src/node/cli.ts b/packages/vitest/src/node/cli.ts index 511e9d87d5b6..124558ea936f 100644 --- a/packages/vitest/src/node/cli.ts +++ b/packages/vitest/src/node/cli.ts @@ -25,7 +25,7 @@ cli .option('--hideSkippedTests', 'Hide logs for skipped tests') .option('--reporter ', 'Specify reporters') .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('--coverage', 'Enable coverage report', { default: { 100: false } }) .option('--run', 'Disable watch mode') .option('--mode ', 'Override Vite mode (default: test)') .option('--globals', 'Inject apis globally') From 9021e8b84bec120e5c7546d6e0aed88db3b60f3c Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Tue, 31 Oct 2023 16:30:44 +0100 Subject: [PATCH 3/9] feat(vitest): expose execArgv to the different pools (#4383) --- docs/config/index.md | 33 +++++++++ packages/vitest/src/node/pool.ts | 2 +- packages/vitest/src/node/pools/child.ts | 5 +- packages/vitest/src/node/pools/threads.ts | 5 +- packages/vitest/src/node/pools/vm-threads.ts | 1 + packages/vitest/src/types/pool-options.ts | 26 +++++++ pnpm-lock.yaml | 3 + .../allowed-exec-argv.test.ts | 16 +++++ .../vitest.config.ts | 3 + test/run/exec-args-fixtures/forks.test.ts | 13 ++++ test/run/exec-args-fixtures/threads.test.ts | 11 +++ test/run/exec-args-fixtures/vmThreads.test.ts | 15 ++++ .../no-exec-argv.test.ts | 12 ++++ .../no-exec-args-fixtures/vitest.config.ts | 3 + test/run/package.json | 1 + test/run/test/exec-args.test.ts | 69 +++++++++++++++++++ 16 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 test/run/allowed-exec-args-fixtures/allowed-exec-argv.test.ts create mode 100644 test/run/allowed-exec-args-fixtures/vitest.config.ts create mode 100644 test/run/exec-args-fixtures/forks.test.ts create mode 100644 test/run/exec-args-fixtures/threads.test.ts create mode 100644 test/run/exec-args-fixtures/vmThreads.test.ts create mode 100644 test/run/no-exec-args-fixtures/no-exec-argv.test.ts create mode 100644 test/run/no-exec-args-fixtures/vitest.config.ts create mode 100644 test/run/test/exec-args.test.ts diff --git a/docs/config/index.md b/docs/config/index.md index 0a391b5628dc..273a519b3017 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -723,6 +723,17 @@ This can improve performance in some cases, but might cause segfault in older No Isolate environment for each test file. +##### poolOptions.threads.execArgv + +- **Type:** `string[]` +- **Default:** `[]` + +Pass additional arguments to `node` in the threads. See [Command-line API | Node.js](https://nodejs.org/docs/latest/api/cli.html) for more information. + +:::warning +Be careful when using, it as some options may crash worker, e.g. --prof, --title. See https://github.com/nodejs/node/issues/41103. +::: + #### poolOptions.forks Options for `forks` pool. @@ -776,6 +787,17 @@ Even though this option will force tests to run one after another, this option i This might cause all sorts of issues, if you are relying on global state (frontend frameworks usually do) or your code relies on environment to be defined separately for each test. But can be a speed boost for your tests (up to 3 times faster), that don't necessarily rely on global state or can easily bypass that. ::: +##### poolOptions.forks.execArgv + +- **Type:** `string[]` +- **Default:** `[]` + +Pass additional arguments to `node` process in the child processes. See [Command-line API | Node.js](https://nodejs.org/docs/latest/api/cli.html) for more information. + +:::warning +Be careful when using, it as some options may crash worker, e.g. --prof, --title. See https://github.com/nodejs/node/issues/41103. +::: + #### poolOptions.vmThreads Options for `vmThreads` pool. @@ -846,6 +868,17 @@ Use Atomics to synchronize threads. This can improve performance in some cases, but might cause segfault in older Node versions. +##### poolOptions.vmThreads.execArgv + +- **Type:** `string[]` +- **Default:** `[]` + +Pass additional arguments to `node` process in the VM context. See [Command-line API | Node.js](https://nodejs.org/docs/latest/api/cli.html) for more information. + +:::warning +Be careful when using, it as some options may crash worker, e.g. --prof, --title. See https://github.com/nodejs/node/issues/41103. +::: + ### testTimeout - **Type:** `number` diff --git a/packages/vitest/src/node/pool.ts b/packages/vitest/src/node/pool.ts index 3f13a2c6ebbc..f44c250709e3 100644 --- a/packages/vitest/src/node/pool.ts +++ b/packages/vitest/src/node/pool.ts @@ -63,7 +63,7 @@ export function createPool(ctx: Vitest): ProcessPool { // Instead of passing whole process.execArgv to the workers, pick allowed options. // Some options may crash worker, e.g. --prof, --title. nodejs/node#41103 const execArgv = process.execArgv.filter(execArg => - execArg.startsWith('--cpu-prof') || execArg.startsWith('--heap-prof'), + execArg.startsWith('--cpu-prof') || execArg.startsWith('--heap-prof') || execArg.startsWith('--diagnostic-dir'), ) const options: PoolProcessOptions = { diff --git a/packages/vitest/src/node/pools/child.ts b/packages/vitest/src/node/pools/child.ts index 6f6cc7351c2b..f1eddcd6dde5 100644 --- a/packages/vitest/src/node/pools/child.ts +++ b/packages/vitest/src/node/pools/child.ts @@ -69,7 +69,10 @@ export function createChildProcessPool(ctx: Vitest, { execArgv, env, forksPath } minThreads, env, - execArgv, + execArgv: [ + ...ctx.config.poolOptions?.forks?.execArgv ?? [], + ...execArgv, + ], terminateTimeout: ctx.config.teardownTimeout, } diff --git a/packages/vitest/src/node/pools/threads.ts b/packages/vitest/src/node/pools/threads.ts index 9e8023c8656a..7b1b8040f54a 100644 --- a/packages/vitest/src/node/pools/threads.ts +++ b/packages/vitest/src/node/pools/threads.ts @@ -56,7 +56,10 @@ export function createThreadsPool(ctx: Vitest, { execArgv, env, workerPath }: Po minThreads, env, - execArgv, + execArgv: [ + ...ctx.config.poolOptions?.threads?.execArgv ?? [], + ...execArgv, + ], terminateTimeout: ctx.config.teardownTimeout, } diff --git a/packages/vitest/src/node/pools/vm-threads.ts b/packages/vitest/src/node/pools/vm-threads.ts index 35a1c6ae5f7d..5c8a298d4931 100644 --- a/packages/vitest/src/node/pools/vm-threads.ts +++ b/packages/vitest/src/node/pools/vm-threads.ts @@ -66,6 +66,7 @@ export function createVmThreadsPool(ctx: Vitest, { execArgv, env, vmPath }: Pool '--experimental-vm-modules', '--require', suppressWarningsPath, + ...ctx.config.poolOptions?.vmThreads?.execArgv ?? [], ...execArgv, ], diff --git a/packages/vitest/src/types/pool-options.ts b/packages/vitest/src/types/pool-options.ts index eb874614e599..30c6f0115f3e 100644 --- a/packages/vitest/src/types/pool-options.ts +++ b/packages/vitest/src/types/pool-options.ts @@ -81,6 +81,19 @@ interface WorkerContextOptions { * @default true */ isolate?: boolean + + /** + * Pass additional arguments to `node` process when spawning `worker_threads` or `child_process`. + * + * See [Command-line API | Node.js](https://nodejs.org/docs/latest/api/cli.html) for more information. + * + * Set to `process.execArgv` to pass all arguments of the current process. + * + * Be careful when using, it as some options may crash worker, e.g. --prof, --title. See https://github.com/nodejs/node/issues/41103 + * + * @default [] // no execution arguments are passed + */ + execArgv?: string[] } interface VmOptions { @@ -92,4 +105,17 @@ interface VmOptions { /** Isolation is always enabled */ isolate?: true + + /** + * Pass additional arguments to `node` process when spawning `worker_threads` or `child_process`. + * + * See [Command-line API | Node.js](https://nodejs.org/docs/latest/api/cli.html) for more information. + * + * Set to `process.execArgv` to pass all arguments of the current process. + * + * Be careful when using, it as some options may crash worker, e.g. --prof, --title. See https://github.com/nodejs/node/issues/41103 + * + * @default [] // no execution arguments are passed + */ + execArgv?: string[] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f34f99c9fe3..ed7acbcae594 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1886,6 +1886,9 @@ importers: test/run: devDependencies: + execa: + specifier: ^7.1.1 + version: 7.1.1 vite: specifier: ^4.5.0 version: 4.5.0(@types/node@18.16.19)(less@4.1.3) diff --git a/test/run/allowed-exec-args-fixtures/allowed-exec-argv.test.ts b/test/run/allowed-exec-args-fixtures/allowed-exec-argv.test.ts new file mode 100644 index 000000000000..7835748e07eb --- /dev/null +++ b/test/run/allowed-exec-args-fixtures/allowed-exec-argv.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' + +describe('exec-args', async () => { + it('should have the correct flags', () => { + // flags that should go through + expect(process.execArgv).toContain('--cpu-prof') + expect(process.execArgv).toContain('--cpu-prof-name=cpu.prof') + expect(process.execArgv).toContain('--heap-prof') + expect(process.execArgv).toContain('--heap-prof-name=heap.prof') + expect(process.execArgv).toContain('--diagnostic-dir=/tmp/vitest-diagnostics') + + // added via vitest + expect(process.execArgv).toContain('--conditions') + expect(process.execArgv).toContain('node') + }) +}) diff --git a/test/run/allowed-exec-args-fixtures/vitest.config.ts b/test/run/allowed-exec-args-fixtures/vitest.config.ts new file mode 100644 index 000000000000..abed6b2116e1 --- /dev/null +++ b/test/run/allowed-exec-args-fixtures/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({}) diff --git a/test/run/exec-args-fixtures/forks.test.ts b/test/run/exec-args-fixtures/forks.test.ts new file mode 100644 index 000000000000..758837b1992d --- /dev/null +++ b/test/run/exec-args-fixtures/forks.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest' + +describe('exec-args', async () => { + it('should have the correct flags', () => { + expect(process.execArgv).toContain('--hash-seed=1') + expect(process.execArgv).toContain('--random-seed=1') + expect(process.execArgv).toContain('--no-opt') + + // added via vitest + expect(process.execArgv).toContain('--conditions') + expect(process.execArgv).toContain('node') + }) +}) diff --git a/test/run/exec-args-fixtures/threads.test.ts b/test/run/exec-args-fixtures/threads.test.ts new file mode 100644 index 000000000000..779e4f106e38 --- /dev/null +++ b/test/run/exec-args-fixtures/threads.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from 'vitest' + +describe('exec-args', async () => { + it('should have the correct flags', () => { + expect(process.execArgv).toContain('--inspect-brk') + + // added via vitest + expect(process.execArgv).toContain('--conditions') + expect(process.execArgv).toContain('node') + }) +}) diff --git a/test/run/exec-args-fixtures/vmThreads.test.ts b/test/run/exec-args-fixtures/vmThreads.test.ts new file mode 100644 index 000000000000..04ee85f6e2fe --- /dev/null +++ b/test/run/exec-args-fixtures/vmThreads.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest' + +describe('exec-args', async () => { + it('should have the correct flags', () => { + expect(process.execArgv).toContain('--inspect-brk') + + // added via vitest + expect(process.execArgv).toContain('--experimental-import-meta-resolve') + expect(process.execArgv).toContain('--experimental-vm-modules') + expect(process.execArgv).toContain('--require') + expect(process.execArgv).toContainEqual(expect.stringContaining('/packages/vitest/suppress-warnings.cjs')) + expect(process.execArgv).toContain('--conditions') + expect(process.execArgv).toContain('node') + }) +}) diff --git a/test/run/no-exec-args-fixtures/no-exec-argv.test.ts b/test/run/no-exec-args-fixtures/no-exec-argv.test.ts new file mode 100644 index 000000000000..04162d6205f0 --- /dev/null +++ b/test/run/no-exec-args-fixtures/no-exec-argv.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest' + +describe('exec-args', async () => { + it('should have the correct flags', () => { + // flags should not be passed + expect(process.execArgv).not.toContain('--title') + + // added via vitest + expect(process.execArgv).toContain('--conditions') + expect(process.execArgv).toContain('node') + }) +}) diff --git a/test/run/no-exec-args-fixtures/vitest.config.ts b/test/run/no-exec-args-fixtures/vitest.config.ts new file mode 100644 index 000000000000..abed6b2116e1 --- /dev/null +++ b/test/run/no-exec-args-fixtures/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({}) diff --git a/test/run/package.json b/test/run/package.json index 5bc48ffdaa9d..1ffa3012ba94 100644 --- a/test/run/package.json +++ b/test/run/package.json @@ -5,6 +5,7 @@ "test": "vitest" }, "devDependencies": { + "execa": "^7.1.1", "vite": "latest", "vitest": "workspace:*" } diff --git a/test/run/test/exec-args.test.ts b/test/run/test/exec-args.test.ts new file mode 100644 index 000000000000..6d37cda4732a --- /dev/null +++ b/test/run/test/exec-args.test.ts @@ -0,0 +1,69 @@ +import { afterAll, beforeAll, expect, test } from 'vitest' +import { execa } from 'execa' +import { runVitest } from '../../test-utils' + +// VITEST_SEGFAULT_RETRY messes with the node flags, as can be seen in packages/vitest/src/node/cli-wrapper.ts +// so here we remove it to make sure the tests are not affected by it +const ORIGIN_VITEST_SEGFAULT_RETRY = process.env.VITEST_SEGFAULT_RETRY +beforeAll(() => { + delete process.env.VITEST_SEGFAULT_RETRY +}) +afterAll(() => { + process.env.VITEST_SEGFAULT_RETRY = ORIGIN_VITEST_SEGFAULT_RETRY +}) + +test.each([ + { pool: 'forks', execArgv: ['--hash-seed=1', '--random-seed=1', '--no-opt'] }, + { pool: 'threads', execArgv: ['--inspect-brk'] }, + { pool: 'vmThreads', execArgv: ['--inspect-brk'] }, +] as const)('should pass execArgv to { pool: $pool } ', async ({ pool, execArgv }) => { + const fileToTest = `exec-args-fixtures/${pool}.test.ts` + + const vitest = await runVitest({ + include: [fileToTest], + pool, + poolOptions: { + [pool]: { + execArgv, + }, + }, + }) + + expect(vitest.stdout).toContain(`✓ ${fileToTest}`) +}) + +test('should not pass execArgv to workers when not specified in the config', async () => { + const { stdout, stderr } = await execa('node', [ + '--title', 'this-works-only-on-main-thread', + '../node_modules/vitest/vitest.mjs', '--run', + ], { + cwd: `${process.cwd()}/no-exec-args-fixtures`, + reject: false, + env: { + VITE_NODE_DEPS_MODULE_DIRECTORIES: '/node_modules/,/packages/', + NO_COLOR: '1', + }, + }) + + expect(stderr).not.toContain('Error: Initiated Worker with invalid execArgv flags: --title') + expect(stderr).not.toContain('ERR_WORKER_INVALID_EXEC_ARGV') + expect(stdout).toContain('✓ no-exec-argv.test.ts') +}) + +test('should let allowed args pass to workers', async () => { + const { stdout, stderr } = await execa('node', [ + '--cpu-prof', '--heap-prof', '--diagnostic-dir=/tmp/vitest-diagnostics', + '--cpu-prof-name=cpu.prof', '--heap-prof-name=heap.prof', + '../node_modules/vitest/vitest.mjs', '--run', + ], { + cwd: `${process.cwd()}/allowed-exec-args-fixtures`, + reject: false, + env: { + VITE_NODE_DEPS_MODULE_DIRECTORIES: '/node_modules/,/packages/', + NO_COLOR: '1', + }, + }) + + expect(stderr).toBe('') + expect(stdout).toContain('✓ allowed-exec-argv.test.ts') +}) From 1ecbe74d453f5f856e4bfc7dd9b3097e939610ba Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 1 Nov 2023 14:01:14 +0100 Subject: [PATCH 4/9] fix: support typechecking with Yarn PnP (#4412) --- packages/vitest/src/typecheck/parse.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vitest/src/typecheck/parse.ts b/packages/vitest/src/typecheck/parse.ts index d6337197f446..7c5b5bb70adc 100644 --- a/packages/vitest/src/typecheck/parse.ts +++ b/packages/vitest/src/typecheck/parse.ts @@ -1,4 +1,5 @@ import url from 'node:url' +import os from 'node:os' import { writeFile } from 'node:fs/promises' import { basename, dirname, join, resolve } from 'pathe' import { getTsconfig as getTsconfigContent } from 'get-tsconfig' @@ -77,7 +78,7 @@ export async function getTsconfig(root: string, config: TypecheckConfig) { tmpTsConfig.compilerOptions.emitDeclarationOnly = false tmpTsConfig.compilerOptions.incremental = true tmpTsConfig.compilerOptions.tsBuildInfoFile = join( - __dirname, + process.versions.pnp ? join(os.tmpdir(), 'vitest') : __dirname, 'tsconfig.tmp.tsbuildinfo', ) From 3397fdc45cc0f57df4f7afe08e472a9c5ff360b9 Mon Sep 17 00:00:00 2001 From: dsyddall Date: Thu, 2 Nov 2023 11:56:13 +0000 Subject: [PATCH 5/9] fix: support accessing task from test context without accessing fixtures (#4419) --- packages/runner/src/fixture.ts | 4 ++++ test/core/test/fixture-initialization.test.ts | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index becd8dc0f318..2a710dcda4c7 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -74,6 +74,10 @@ export function withFixtures(fn: Function, testContext?: TestContext) { const usedFixtures = fixtures.filter(({ prop }) => usedProps.includes(prop)) const pendingFixtures = resolveDeps(usedFixtures) + + if (!pendingFixtures.length) + return fn(context) + let cursor = 0 return new Promise((resolve, reject) => { diff --git a/test/core/test/fixture-initialization.test.ts b/test/core/test/fixture-initialization.test.ts index 820ba6f454f7..42f7d1098e22 100644 --- a/test/core/test/fixture-initialization.test.ts +++ b/test/core/test/fixture-initialization.test.ts @@ -1,5 +1,5 @@ import type { Use } from '@vitest/runner' -import { describe, expect, expectTypeOf, test, vi } from 'vitest' +import { beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest' interface Fixtures { a: number @@ -179,4 +179,16 @@ describe('fixture initialization', () => { expect(archive.length).toBe(1) }) }) + + describe('accessing non-fixture context', () => { + const myTest = test.extend({ a: 1 }) + + beforeEach(async ({ task }) => { + expect(task).toBeTruthy() + }) + + myTest('non-fixture context can be accessed without accessing fixtures', ({ task }) => { + expect(task).toBeTruthy() + }) + }) }) From 067ce7e0634e0919f6888eefba83ff65325f5351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?= Date: Thu, 2 Nov 2023 14:58:52 +0100 Subject: [PATCH 6/9] docs: fix VP rc24 styles (#4416) --- docs/.vitepress/config.ts | 4 +- docs/.vitepress/scripts/pwa.ts | 7 +++- docs/.vitepress/style/main.css | 76 ++++++++++++++++++++++++++++++++++ docs/.vitepress/style/vars.css | 63 ++++++++++++++-------------- docs/index.md | 4 ++ docs/package.json | 1 + netlify.toml | 5 --- pnpm-lock.yaml | 3 ++ 8 files changed, 122 insertions(+), 41 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 6367cd20bcba..e57a1424d0f0 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -54,8 +54,8 @@ export default withPwa(defineConfig({ lastUpdated: true, markdown: { theme: { - light: 'vitesse-light', - dark: 'vitesse-dark', + light: 'github-light', + dark: 'github-dark', }, }, themeConfig: { diff --git a/docs/.vitepress/scripts/pwa.ts b/docs/.vitepress/scripts/pwa.ts index afd49f99f239..fdca7e50de00 100644 --- a/docs/.vitepress/scripts/pwa.ts +++ b/docs/.vitepress/scripts/pwa.ts @@ -65,7 +65,9 @@ export const pwa: PwaOptions = { }, workbox: { navigateFallbackDenylist: [/^\/new$/], - globPatterns: ['**/*.{css,js,html,png,svg,ico,txt,woff2}'], + // warning: sponsors/antfu.svg is 2.51 MB, and won't be precached + maximumFileSizeToCacheInBytes: 3 * 1024 * 1024, // <== 3MB + globPatterns: ['**/*.{css,js,html,png,svg,ico,txt,woff2,json}'], runtimeCaching: [ { urlPattern: pwaFontsRegex, @@ -111,4 +113,7 @@ export const pwa: PwaOptions = { }, ], }, + experimental: { + includeAllowlist: true, + }, } diff --git a/docs/.vitepress/style/main.css b/docs/.vitepress/style/main.css index 1c603ea7e5d5..7b57691c6ea0 100644 --- a/docs/.vitepress/style/main.css +++ b/docs/.vitepress/style/main.css @@ -8,10 +8,86 @@ html:not(.dark) [img-dark] { /* Overrides */ +.sp .sp-link.link:hover, +.sp .sp-link.link:focus { + background-color: var(--vitest-c-sponsor-hover) !important; +} + +/* outline styles for buttons and VPButton anchors */ +button:focus, +button:focus-visible, +.DocSearch-Button:focus, +.DocSearch-Button:focus-visible, +a:focus-visible, +a:focus { + border-radius: 2px; + outline: 2px solid var(--vp-c-text-1); +} + +a:focus:not(:focus-visible), +button:focus:not(:focus-visible) { + outline: none !important; +} + +/* custom block styles */ +html:not(.dark) .custom-block.tip code { + color: var(--vitest-custom-block-tip-code-text) !important; +} +html:not(.dark) .custom-block.info code { + color: var(--vitest-custom-block-info-code-text) !important; +} +.custom-block.tip a:hover, +.vp-doc .custom-block.tip a:hover > code { + color: var(--vp-c-brand-1) !important; + opacity: 1; +} +.custom-block.info a:hover, +.vp-doc .custom-block.info a:hover > code { + color: var(--vp-c-brand-1) !important; + opacity: 1; +} +html:not(.dark) .custom-block.info a:hover, +html:not(.dark) .vp-doc .custom-block.info a:hover > code { + color: var(--vitest-custom-block-info-code-text) !important; + opacity: 1; +} +.custom-block.warning a:hover, +.vp-doc .custom-block.warning a:hover > code { + color: var(--vp-c-warning-1) !important; + opacity: 1; +} +.custom-block.danger a:hover, +.vp-doc .custom-block.danger a:hover > code { + color: var(--vp-c-danger-1) !important; + opacity: 1; +} + +/* search # styles */ +:not(.dark) .title-icon { + opacity: 1 !important; +} +.dark .title-icon { + opacity: 0.67 !important; +} + .VPSocialLink { transform: scale(0.9); } +.vp-doc a { + text-decoration-style: dotted; +} +.custom-block a:focus, +.custom-block a:active, +.custom-block a:hover, +.custom-block a:active, +.vp-doc a:focus, +.vp-doc a:active, +.vp-doc a:hover, +.vp-doc a:active { + text-decoration: underline; +} + .vp-doc th, .vp-doc td { padding: 6px 10px; border: 1px solid #8882; diff --git a/docs/.vitepress/style/vars.css b/docs/.vitepress/style/vars.css index eca8f6b73fcd..5814c2ca3ad8 100644 --- a/docs/.vitepress/style/vars.css +++ b/docs/.vitepress/style/vars.css @@ -3,47 +3,44 @@ * -------------------------------------------------------------------------- */ :root { - --vp-c-accent: #dab40b; - --vp-c-brand: #6da13f; - --vp-c-brand-1: var(--vp-c-brand-dark); - --vp-c-brand-2: var(--vp-c-brand-darker); - --vp-c-brand-light: #7ec242; - --vp-c-brand-lighter: #93d31c; - --vp-c-brand-dark: #668d11; - --vp-c-brand-darker: #52730d; - --vp-c-text-code: #5d6f5d; - --vp-code-block-bg: rgba(125,125,125,0.04); - --vp-c-text-light-2: rgba(56 56 56 / 70%); - /* fix contrast: lang name on gray code block */ - --vp-c-text-dark-3: rgba(180, 180, 180, 0.7); - --vp-code-copy-code-bg: rgba(125,125,125,0.1); - --vp-code-copy-code-hover-bg: rgba(125,125,125,0.2); - --vp-c-disabled-bg: rgba(125,125,125,0.2); + --vp-c-brand-1: #52730d; + --vp-c-brand-2: #57791b; + --vp-c-brand-3: #506e10; + --vp-c-sponsor: #ca2971; + --vitest-c-sponsor-hover: #c13071; } .dark { - --vp-code-block-bg: rgba(0,0,0,0.2); - --vp-c-text-code: #c0cec0; - /* fix contrast on gray cards: check the same above (this is the default) */ - --vp-c-text-dark-2: rgba(235, 235, 235, 0.60); - /* fix lang name: check the same above (this is the default) */ - --vp-c-text-dark-3: rgba(235, 235, 235, 0.38); + --vp-c-brand-1: #add467; + --vp-c-brand-2: #a7cc66; + --vp-c-brand-3: #acd268; + --vp-c-sponsor: #ee4e95; + --vitest-c-sponsor-hover: #e51370; } + /** - * Component: Button + * Component: Custom Block * -------------------------------------------------------------------------- */ - :root { - --vp-button-brand-border: var(--vp-c-brand-light); - --vp-button-brand-text: var(--vp-c-text-dark-1); - --vp-button-brand-bg: var(--vp-c-brand); - --vp-button-brand-hover-border: var(--vp-c-brand-light); - --vp-button-brand-hover-text: var(--vp-c-text-dark-1); - --vp-button-brand-hover-bg: var(--vp-c-brand-light); - --vp-button-brand-active-border: var(--vp-c-brand-light); - --vp-button-brand-active-text: var(--vp-c-text-dark-1); - --vp-button-brand-active-bg: var(--vp-button-brand-bg); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); + --vp-custom-block-danger-code-bg: #a79fa029; + --vitest-custom-block-tip-code-text: #4a680c; + --vitest-custom-block-info-code-text: #394f0c +} + +.dark { + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); + --vp-custom-block-danger-code-bg: #6c6a6a2b; +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ +.dark { + --vp-button-brand-text: #243600; + --vp-button-brand-active-text: #243600; + --vp-button-brand-hover-text: #243600; } /** diff --git a/docs/index.md b/docs/index.md index 8bfbc3825bd9..14bed543a26f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,11 +28,15 @@ hero: features: - title: Vite Powered + icon: details: Reuse Vite's config and plugins - consistent across your app and tests. But it's not required to use Vitest! - title: Jest Compatible + icon: details: Expect, snapshot, coverage, and more - migrating from Jest is straightforward. - title: Smart & instant watch mode + icon: ⚡ details: Only rerun the related changes, just like HMR for tests! - title: ESM, TypeScript, JSX + icon: details: Out-of-box ESM, TypeScript and JSX support powered by esbuild. --- diff --git a/docs/package.json b/docs/package.json index 906a85b1b0a9..5278f075dad1 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@iconify-json/carbon": "^1.1.21", + "@iconify-json/logos": "^1.1.37", "@unocss/reset": "^0.53.4", "@vite-pwa/assets-generator": "^0.0.10", "@vite-pwa/vitepress": "^0.2.3", diff --git a/netlify.toml b/netlify.toml index d4a8d288985d..4e1a606e9416 100755 --- a/netlify.toml +++ b/netlify.toml @@ -13,11 +13,6 @@ to = "https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/basic?initialPath=__vitest__/" status = 302 -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 - [[headers]] for = "/manifest.webmanifest" [headers.values] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed7acbcae594..c9d7d39e846d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,9 @@ importers: '@iconify-json/carbon': specifier: ^1.1.21 version: 1.1.21 + '@iconify-json/logos': + specifier: ^1.1.37 + version: 1.1.37 '@unocss/reset': specifier: ^0.53.4 version: 0.53.4 From 970038b2d06e68a1ad4003746cb296bf2022e119 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 2 Nov 2023 15:08:26 +0100 Subject: [PATCH 7/9] docs: remove extra spaces before paragraphs in documentation (#4426) --- docs/api/expect.md | 1721 +++++++++++++++++++-------------------- docs/api/index.md | 994 +++++++++++----------- docs/guide/migration.md | 2 +- 3 files changed, 1357 insertions(+), 1360 deletions(-) diff --git a/docs/api/expect.md b/docs/api/expect.md index 437fe2ddf792..0fee5b78f5d6 100644 --- a/docs/api/expect.md +++ b/docs/api/expect.md @@ -6,22 +6,22 @@ The following types are used in the type signatures below 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`. +`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. +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' +```ts +import { expect } from 'vitest' - const input = Math.sqrt(4) +const input = Math.sqrt(4) - expect(input).to.equal(2) // chai API - expect(input).toBe(2) // jest API - ``` +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/). +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. +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). @@ -33,27 +33,27 @@ type Awaitable = T | PromiseLike `expect.soft` functions similarly to `expect`, but instead of terminating the test execution upon a failed assertion, it continues running and marks the failure as a test failure. All errors encountered during the test will be displayed until the test is completed. - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - test('expect.soft test', () => { - expect.soft(1 + 1).toBe(3) // mark the test as fail and continue - expect.soft(1 + 2).toBe(4) // mark the test as fail and continue - }) - // At the end of the test, the above errors will be output. - ``` +test('expect.soft test', () => { + expect.soft(1 + 1).toBe(3) // mark the test as fail and continue + expect.soft(1 + 2).toBe(4) // mark the test as fail and continue +}) +// At the end of the test, the above errors will be output. +``` - It can also be used with `expect`. if `expect` assertion fails, the test will be terminated and all errors will be displayed. +It can also be used with `expect`. if `expect` assertion fails, the test will be terminated and all errors will be displayed. - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - test('expect.soft test', () => { - expect.soft(1 + 1).toBe(3) // mark the test as fail and continue - expect(1 + 2).toBe(3) // failed and terminate the test, all previous errors will be output - expect.soft(1 + 2).toBe(4) // do not run - }) - ``` +test('expect.soft test', () => { + expect.soft(1 + 1).toBe(3) // mark the test as fail and continue + expect(1 + 2).toBe(3) // failed and terminate the test, all previous errors will be output + expect.soft(1 + 2).toBe(4) // do not run +}) +``` ::: warning `expect.soft` can only be used inside the [`test`](/api/#test) function. @@ -61,482 +61,482 @@ type Awaitable = T | PromiseLike ## 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. +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' +```ts +import { expect, test } from 'vitest' - const input = Math.sqrt(16) +const input = Math.sqrt(16) - expect(input).not.to.equal(2) // chai API - expect(input).not.toBe(2) // jest API - ``` +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). +`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. +For example, the code below checks if the trader has 13 apples. - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - const stock = { - type: 'apples', - count: 13, - } +const stock = { + type: 'apples', + count: 13, +} - test('stock has 13 apples', () => { - expect(stock.type).toBe('apples') - expect(stock.count).toBe(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 +test('stocks are the same', () => { + const refStock = stock // same reference - expect(stock).toBe(refStock) - }) - ``` + 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. +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: +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' +```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.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) - }) - ``` +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. +`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' +```ts +import { expect, test } from 'vitest' - function getApples() { - return 3 - } +function getApples() { + return 3 +} - test('function returned something', () => { - expect(getApples()).toBeDefined() - }) - ``` +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. +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' +```ts +import { expect, test } from 'vitest' - function getApplesFromStock(stock) { - if (stock === 'Bill') - return 13 - } +function getApplesFromStock(stock) { + if (stock === 'Bill') + return 13 +} - test('mary doesn\'t have a stock', () => { - expect(getApplesFromStock('Mary')).toBeUndefined() - }) - ``` +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`. +`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. +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.js' +```ts +import { Stocks } from './stocks.js' - const stocks = new Stocks() - stocks.sync('Bill') - if (stocks.getInfo('Bill')) - stocks.sell('apples', 'Bill') - ``` +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: +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.js' +```ts +import { expect, test } from 'vitest' +import { Stocks } from './stocks.js' - const stocks = new Stocks() +const stocks = new Stocks() - test('if we know Bill stock, sell apples to him', () => { - stocks.sync('Bill') - expect(stocks.getInfo('Bill')).toBeTruthy() - }) - ``` +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`, `null`, `undefined`, `NaN`, `0`, `-0`, `0n`, `""` and `document.all`. +Everything in JavaScript is truthy, except `false`, `null`, `undefined`, `NaN`, `0`, `-0`, `0n`, `""` and `document.all`. ## 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`. +`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. +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.js' +```ts +import { Stocks } from './stocks.js' - const stocks = new Stocks() - stocks.sync('Bill') - if (!stocks.stockFailed('Bill')) - stocks.sell('apples', 'Bill') - ``` +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: +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.js' +```ts +import { expect, test } from 'vitest' +import { Stocks } from './stocks.js' - const stocks = new Stocks() +const stocks = new Stocks() - test('if Bill stock hasn\'t failed, sell apples to him', () => { - stocks.syncStocks('Bill') - expect(stocks.stockFailed('Bill')).toBeFalsy() - }) - ``` +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`, `null`, `undefined`, `NaN`, `0`, `-0`, `0n`, `""` and `document.all`. +Everything in JavaScript is truthy, except `false`, `null`, `undefined`, `NaN`, `0`, `-0`, `0n`, `""` and `document.all`. ## toBeNull - **Type:** `() => Awaitable` - `toBeNull` simply asserts if something is `null`. Alias for `.toBe(null)`. +`toBeNull` simply asserts if something is `null`. Alias for `.toBe(null)`. - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - function apples() { - return null - } +function apples() { + return null +} - test('we don\'t have apples', () => { - expect(apples()).toBeNull() - }) - ``` +test('we don\'t have apples', () => { + expect(apples()).toBeNull() +}) +``` ## toBeNaN - **Type:** `() => Awaitable` - `toBeNaN` simply asserts if something is `NaN`. Alias for `.toBe(NaN)`. +`toBeNaN` simply asserts if something is `NaN`. Alias for `.toBe(NaN)`. - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - let i = 0 +let i = 0 - function getApplesCount() { - i++ - return i > 1 ? Number.NaN : i - } +function getApplesCount() { + i++ + return i > 1 ? Number.NaN : i +} - test('getApplesCount has some unusual side effects...', () => { - expect(getApplesCount()).not.toBeNaN() - expect(getApplesCount()).toBeNaN() - }) - ``` +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. +`toBeTypeOf` asserts if an actual value is of type of received type. - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - const actual = 'stock' +const actual = 'stock' - test('stock is type of string', () => { - expect(actual).toBeTypeOf('string') - }) - ``` +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. +`toBeInstanceOf` asserts if an actual value is instance of received class. - ```ts - import { expect, test } from 'vitest' - import { Stocks } from './stocks.js' +```ts +import { expect, test } from 'vitest' +import { Stocks } from './stocks.js' - const stocks = new Stocks() +const stocks = new Stocks() - test('stocks are instance of Stocks', () => { - expect(stocks).toBeInstanceOf(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. +`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 './stocks.js' +```ts +import { expect, test } from 'vitest' +import { getApples } from './stocks.js' - test('have more then 10 apples', () => { - expect(getApples()).toBeGreaterThan(10) - }) - ``` +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. +`toBeGreaterThanOrEqual` asserts if actual value is greater than received one or equal to it. - ```ts - import { expect, test } from 'vitest' - import { getApples } from './stocks.js' +```ts +import { expect, test } from 'vitest' +import { getApples } from './stocks.js' - test('have 11 apples or more', () => { - expect(getApples()).toBeGreaterThanOrEqual(11) - }) - ``` +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. +`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 './stocks.js' +```ts +import { expect, test } from 'vitest' +import { getApples } from './stocks.js' - test('have less then 20 apples', () => { - expect(getApples()).toBeLessThan(20) - }) - ``` +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. +`toBeLessThanOrEqual` asserts if actual value is less than received one or equal to it. - ```ts - import { expect, test } from 'vitest' - import { getApples } from './stocks.js' +```ts +import { expect, test } from 'vitest' +import { getApples } from './stocks.js' - test('have 11 apples or less', () => { - expect(getApples()).toBeLessThanOrEqual(11) - }) - ``` +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: +`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' +```ts +import { expect, test } from 'vitest' - const stockBill = { - type: 'apples', - count: 13, - } +const stockBill = { + type: 'apples', + count: 13, +} - const stockMary = { - type: 'apples', - count: 13, - } +const stockMary = { + type: 'apples', + count: 13, +} - test('stocks have the same properties', () => { - expect(stockBill).toEqual(stockMary) - }) +test('stocks have the same properties', () => { + expect(stockBill).toEqual(stockMary) +}) - test('stocks are not the same', () => { - expect(stockBill).not.toBe(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. - ::: +:::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. +`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): +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`. +- 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' +```ts +import { expect, test } from 'vitest' - class Stock { - constructor(type) { - this.type = type - } +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' }) - }) - ``` +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. +`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 './stocks.js' +```ts +import { expect, test } from 'vitest' +import { getAllFruits } from './stocks.js' - test('the fruit list contains orange', () => { - expect(getAllFruits()).toContain('orange') - }) - ``` +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. +`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 './stocks.js' +```ts +import { expect, test } from 'vitest' +import { getFruitStock } from './stocks.js' - test('apple available', () => { - expect(getFruitStock()).toContainEqual({ fruit: 'apple', count: 5 }) - }) - ``` +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. +`toHaveLength` asserts if an object has a `.length` property and it is set to a certain numeric value. - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - test('toHaveLength', () => { - expect('abc').toHaveLength(3) - expect([1, 2, 3]).toHaveLength(3) +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) - }) - ``` + 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. +`toHaveProperty` asserts if a property at provided reference `key` exists for an object. - ```ts - import { expect, test } from 'vitest' +You can provide an optional value argument also known as deep equality, like the `toEqual` matcher to compare the received property value. - const invoice = { - 'isActive': true, - 'P.O': '12345', - 'customer': { - first_name: 'John', - last_name: 'Doe', - location: 'China', +```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, }, - 'total_amount': 5000, - 'items': [ - { - type: 'apples', - quantity: 10, - }, - { - type: 'oranges', - quantity: 5, - }, - ], - } + { + 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 +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 + 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 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 + // 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 - // Deep referencing using an array containing the keyPath - expect(invoice).toHaveProperty(['items', 0, 'type'], 'apples') - expect(invoice).toHaveProperty(['items', '0', 'type'], 'apples') // string notation also works + // Deep referencing using an array containing the keyPath + expect(invoice).toHaveProperty(['items', 0, 'type'], 'apples') + expect(invoice).toHaveProperty(['items', '0', 'type'], 'apples') // string 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') - }) - ``` + // 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. +`toMatch` asserts if a string matches a regular expression or a string. - ```ts - import { expect, test } from 'vitest' +```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 - }) - ``` +test('top fruits', () => { + expect('top fruits include apple, orange and grape').toMatch(/apple/) + expect('applefruits').toMatch('fruit') // toMatch also accepts a string +}) +``` ::: tip If the value in the error message is too truncated, you can increase [chaiConfig.truncateThreshold](/config/#chaiconfig-truncatethreshold) in your config file. @@ -546,53 +546,53 @@ If the value in the error message is too truncated, you can increase [chaiConfig - **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. +`toMatchObject` asserts if an object matches a subset of the properties of an object. - ```ts - import { expect, test } from 'vitest' +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. - const johnInvoice = { - isActive: true, - customer: { - first_name: 'John', - last_name: 'Doe', - location: 'China', +```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, }, - total_amount: 5000, - items: [ - { - type: 'apples', - quantity: 10, - }, - { - type: 'oranges', - quantity: 5, - }, - ], - } - - const johnDetails = { - customer: { - first_name: 'John', - last_name: 'Doe', - location: 'China', + { + type: 'oranges', + quantity: 5, }, - } - - test('invoice has john personal details', () => { - expect(johnInvoice).toMatchObject(johnDetails) - }) + ], +} + +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 }, - ]) - }) - ``` +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 @@ -600,615 +600,612 @@ If the value in the error message is too truncated, you can increase [chaiConfig - **Alias:** `toThrow` - `toThrowError` asserts if a function throws an error when it is called. +`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: +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 +- 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. - ::: +:::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: +For example, if we want to test that `getFruitStock('pineapples')` throws, we could write: - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - function getFruitStock(type) { - if (type === 'pineapples') - throw new DiabetesError('Pineapples are not good for people with diabetes') +function getFruitStock(type) { + if (type === 'pineapples') + throw new DiabetesError('Pineapples are not good for people with diabetes') - // Do some other stuff - } + // 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('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 are not good for people with diabetes$/, - ) - }) - ``` + // Test the exact error message + expect(() => getFruitStock('pineapples')).toThrowError( + /^Pineapples are not good for people with diabetes$/, + ) +}) +``` - :::tip - To test async functions, use in combination with [rejects](#rejects). +:::tip +To test async functions, use in combination with [rejects](#rejects). - ```js - function getAsyncFruitStock() { - return Promise.reject(new Error('empty')) - } +```js +function getAsyncFruitStock() { + return Promise.reject(new Error('empty')) +} - test('throws on pineapples', async () => { - await expect(() => getAsyncFruitStock()).rejects.toThrowError('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. +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. +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. - ::: +:::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' +```ts +import { expect, test } from 'vitest' - test('matches snapshot', () => { - const data = { foo: new Set(['bar', 'snapshot']) } - expect(data).toMatchSnapshot() - }) - ``` +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: +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' +```ts +import { expect, test } from 'vitest' - test('matches snapshot', () => { - const data = { foo: new Set(['bar', 'snapshot']) } - expect(data).toMatchSnapshot({ foo: expect.any(Set) }) - }) - ``` +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). +This ensures that a value matches the most recent snapshot. - ```ts - import { expect, test } from 'vitest' +Vitest adds and updates the inlineSnapshot string argument to the matcher in the test file (instead of an external `.snap` file). - 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", - }, - } - `) - }) - ``` +```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: +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' +```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, - } +test('matches snapshot', () => { + const data = { foo: new Set(['bar', 'snapshot']) } + expect(data).toMatchInlineSnapshot( + { foo: expect.any(Set) }, ` - ) - }) - ``` + { + "foo": Any, + } + ` + ) +}) +``` ## toMatchFileSnapshot - **Type:** `(filepath: string, message?: string) => Promise` - **Version:** Since Vitest 0.30.0 - Compare or update the snapshot with the content of a file explicitly specified (instead of the `.snap` file). +Compare or update the snapshot with the content of a file explicitly specified (instead of the `.snap` file). - ```ts - import { expect, it } from 'vitest' +```ts +import { expect, it } from 'vitest' - it('render basic', async () => { - const result = renderHTML(h('div', { class: 'foo' })) - await expect(result).toMatchFileSnapshot('./test/basic.output.html') - }) - ``` +it('render basic', async () => { + const result = renderHTML(h('div', { class: 'foo' })) + await expect(result).toMatchFileSnapshot('./test/basic.output.html') +}) +``` - Note that since file system operation is async, you need to use `await` with `toMatchFileSnapshot()`. +Note that since file system operation is async, you need to use `await` with `toMatchFileSnapshot()`. ## toThrowErrorMatchingSnapshot - **Type:** `(message?: string) => void` - The same as [`toMatchSnapshot`](#tomatchsnapshot), but expects the same value as [`toThrowError`](#tothrowerror). +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. +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). +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. +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`. +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' +```ts +import { expect, test, vi } from 'vitest' - const market = { - buy(subject: string, amount: number) { - // ... - }, - } +const market = { + buy(subject: string, amount: number) { + // ... + }, +} - test('spy function', () => { - const buySpy = vi.spyOn(market, 'buy') +test('spy function', () => { + const buySpy = vi.spyOn(market, 'buy') - expect(buySpy).not.toHaveBeenCalled() + expect(buySpy).not.toHaveBeenCalled() - market.buy('apples', 10) + market.buy('apples', 10) - expect(buySpy).toHaveBeenCalled() - }) - ``` + expect(buySpy).toHaveBeenCalled() +}) +``` ## toHaveBeenCalledTimes - - **Type**: `(amount: number) => Awaitable` +- **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`. +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' +```ts +import { expect, test, vi } from 'vitest' - const market = { - buy(subject: string, amount: number) { - // ... - }, - } +const market = { + buy(subject: string, amount: number) { + // ... + }, +} - test('spy function called two times', () => { - const buySpy = vi.spyOn(market, 'buy') +test('spy function called two times', () => { + const buySpy = vi.spyOn(market, 'buy') - market.buy('apples', 10) - market.buy('apples', 20) + market.buy('apples', 10) + market.buy('apples', 20) - expect(buySpy).toHaveBeenCalledTimes(2) - }) - ``` + expect(buySpy).toHaveBeenCalledTimes(2) +}) +``` ## toHaveBeenCalledWith - - **Type**: `(...args: any[]) => Awaitable` +- **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`. +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' +```ts +import { expect, test, vi } from 'vitest' - const market = { - buy(subject: string, amount: number) { - // ... - }, - } +const market = { + buy(subject: string, amount: number) { + // ... + }, +} - test('spy function', () => { - const buySpy = vi.spyOn(market, 'buy') +test('spy function', () => { + const buySpy = vi.spyOn(market, 'buy') - market.buy('apples', 10) - market.buy('apples', 20) + market.buy('apples', 10) + market.buy('apples', 20) - expect(buySpy).toHaveBeenCalledWith('apples', 10) - expect(buySpy).toHaveBeenCalledWith('apples', 20) - }) - ``` + expect(buySpy).toHaveBeenCalledWith('apples', 10) + expect(buySpy).toHaveBeenCalledWith('apples', 20) +}) +``` ## toHaveBeenLastCalledWith - - **Type**: `(...args: any[]) => Awaitable` +- **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`. +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' +```ts +import { expect, test, vi } from 'vitest' - const market = { - buy(subject: string, amount: number) { - // ... - }, - } +const market = { + buy(subject: string, amount: number) { + // ... + }, +} - test('spy function', () => { - const buySpy = vi.spyOn(market, 'buy') +test('spy function', () => { + const buySpy = vi.spyOn(market, 'buy') - market.buy('apples', 10) - market.buy('apples', 20) + market.buy('apples', 10) + market.buy('apples', 20) - expect(buySpy).not.toHaveBeenLastCalledWith('apples', 10) - expect(buySpy).toHaveBeenLastCalledWith('apples', 20) - }) - ``` + expect(buySpy).not.toHaveBeenLastCalledWith('apples', 10) + expect(buySpy).toHaveBeenLastCalledWith('apples', 20) +}) +``` ## toHaveBeenNthCalledWith - - **Type**: `(time: number, ...args: any[]) => Awaitable` +- **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, ...)`. +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`. +Requires a spy function to be passed to `expect`. - ```ts - import { expect, test, vi } from 'vitest' +```ts +import { expect, test, vi } from 'vitest' - const market = { - buy(subject: string, amount: number) { - // ... - }, - } +const market = { + buy(subject: string, amount: number) { + // ... + }, +} - test('first call of spy function called with right params', () => { - const buySpy = vi.spyOn(market, 'buy') +test('first call of spy function called with right params', () => { + const buySpy = vi.spyOn(market, 'buy') - market.buy('apples', 10) - market.buy('apples', 20) + market.buy('apples', 10) + market.buy('apples', 20) - expect(buySpy).toHaveBeenNthCalledWith(1, 'apples', 10) - }) - ``` + expect(buySpy).toHaveBeenNthCalledWith(1, 'apples', 10) +}) +``` ## toHaveReturned - - **Type**: `() => Awaitable` +- **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`. +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' +```ts +import { expect, test, vi } from 'vitest' - function getApplesPrice(amount: number) { - const PRICE = 10 - return amount * PRICE - } +function getApplesPrice(amount: number) { + const PRICE = 10 + return amount * PRICE +} - test('spy function returned a value', () => { - const getPriceSpy = vi.fn(getApplesPrice) +test('spy function returned a value', () => { + const getPriceSpy = vi.fn(getApplesPrice) - const price = getPriceSpy(10) + const price = getPriceSpy(10) - expect(price).toBe(100) - expect(getPriceSpy).toHaveReturned() - }) - ``` + expect(price).toBe(100) + expect(getPriceSpy).toHaveReturned() +}) +``` ## toHaveReturnedTimes - - **Type**: `(amount: number) => Awaitable` +- **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`. +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' +```ts +import { expect, test, vi } from 'vitest' - test('spy function returns a value two times', () => { - const sell = vi.fn((product: string) => ({ product })) +test('spy function returns a value two times', () => { + const sell = vi.fn((product: string) => ({ product })) - sell('apples') - sell('bananas') + sell('apples') + sell('bananas') - expect(sell).toHaveReturnedTimes(2) - }) - ``` + expect(sell).toHaveReturnedTimes(2) +}) +``` ## toHaveReturnedWith - - **Type**: `(returnValue: any) => Awaitable` +- **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`. +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' +```ts +import { expect, test, vi } from 'vitest' - test('spy function returns a product', () => { - const sell = vi.fn((product: string) => ({ product })) +test('spy function returns a product', () => { + const sell = vi.fn((product: string) => ({ product })) - sell('apples') + sell('apples') - expect(sell).toHaveReturnedWith({ product: 'apples' }) - }) - ``` + expect(sell).toHaveReturnedWith({ product: 'apples' }) +}) +``` ## toHaveLastReturnedWith - - **Type**: `(returnValue: any) => Awaitable` +- **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`. +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' +```ts +import { expect, test, vi } from 'vitest' - test('spy function returns bananas on a last call', () => { - const sell = vi.fn((product: string) => ({ product })) +test('spy function returns bananas on a last call', () => { + const sell = vi.fn((product: string) => ({ product })) - sell('apples') - sell('bananas') + sell('apples') + sell('bananas') - expect(sell).toHaveLastReturnedWith({ product: 'bananas' }) - }) - ``` + expect(sell).toHaveLastReturnedWith({ product: 'bananas' }) +}) +``` ## toHaveNthReturnedWith - - **Type**: `(time: number, returnValue: any) => Awaitable` +- **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`. +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' +```ts +import { expect, test, vi } from 'vitest' - test('spy function returns bananas on second call', () => { - const sell = vi.fn((product: string) => ({ product })) +test('spy function returns bananas on second call', () => { + const sell = vi.fn((product: string) => ({ product })) - sell('apples') - sell('bananas') + sell('apples') + sell('bananas') - expect(sell).toHaveNthReturnedWith(2, { product: 'bananas' }) - }) - ``` + expect(sell).toHaveNthReturnedWith(2, { product: 'bananas' }) +}) +``` ## toSatisfy - - **Type:** `(predicate: (value: any) => boolean) => Awaitable` +- **Type:** `(predicate: (value: any) => boolean) => Awaitable` - This assertion checks if a value satisfies a certain predicate. +This assertion checks if a value satisfies a certain predicate. - ```ts - describe('toSatisfy()', () => { - const isOdd = (value: number) => value % 2 !== 0 +```ts +describe('toSatisfy()', () => { + const isOdd = (value: number) => value % 2 !== 0 - it('pass with 0', () => { - expect(1).toSatisfy(isOdd) - }) + it('pass with 0', () => { + expect(1).toSatisfy(isOdd) + }) - it('pass with negotiation', () => { - expect(2).not.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. +`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. +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: +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' +```ts +import { expect, test } from 'vitest' - async function buyApples() { - return fetch('/buy/apples').then(r => r.json()) - } +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 - }) - ``` +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). - ::: +:::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. +`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. +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: +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' +```ts +import { expect, test } from 'vitest' - async function buyApples(id) { - if (!id) - throw new Error('no id') - } +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') - }) - ``` +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). - ::: +:::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. +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. +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 })), - ) +```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() } - 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) - }) - ``` - ::: warning - When using `assertions` with async concurrent tests, `expect` from the local [Test Context](/guide/test-context.md) must be used to ensure the right test is detected. - ::: - + await doAsync(callback1, callback2) +}) +``` +::: warning +When using `assertions` with async concurrent tests, `expect` from the local [Test Context](/guide/test-context.md) must be used to ensure the right test is detected. +::: ## 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. +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. +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.js' +```ts +import { expect, test } from 'vitest' +import { db } from './db.js' - const cbs = [] +const cbs = [] - function onSelect(cb) { - cbs.push(cb) - } +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)), - ) - }) - } +// 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) +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.unreachable - **Type:** `(message?: string) => never` - This method is used to asserting that a line should never be reached. - - For example, if we want to test that `build()` throws due to receiving directories having no `src` folder, and also handle each error separately, we could do this: +This method is used to asserting that a line should never be reached. - ```ts - import { expect, test } from 'vitest' +For example, if we want to test that `build()` throws due to receiving directories having no `src` folder, and also handle each error separately, we could do this: - async function build(dir) { - if (dir.includes('no-src')) - throw new Error(`${dir}/src does not exist`) +```ts +import { expect, test } from 'vitest' + +async function build(dir) { + if (dir.includes('no-src')) + throw new Error(`${dir}/src does not exist`) +} + +const errorDirs = [ + 'no-src-folder', + // ... +] + +test.each(errorDirs)('build fails with "%s"', async (dir) => { + try { + await build(dir) + expect.unreachable('Should not pass build') } - - const errorDirs = [ - 'no-src-folder', - // ... - ] - - test.each(errorDirs)('build fails with "%s"', async (dir) => { - try { - await build(dir) - expect.unreachable('Should not pass build') - } - catch (err: any) { - expect(err).toBeInstanceOf(Error) - expect(err.stack).toContain('build') - - switch (dir) { - case 'no-src-folder': - expect(err.message).toBe(`${dir}/src does not exist`) - break - default: - // to exhaust all error tests - expect.unreachable('All error test must be handled') - break - } + catch (err: any) { + expect(err).toBeInstanceOf(Error) + expect(err.stack).toContain('build') + + switch (dir) { + case 'no-src-folder': + expect(err.message).toBe(`${dir}/src does not exist`) + break + default: + // to exhaust all error tests + expect.unreachable('All error test must be handled') + break } - }) - ``` - - + } +}) +``` ## 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. +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' +```ts +import { expect, test } from 'vitest' - test('object has "apples" key', () => { - expect({ apples: 22 }).toEqual({ apples: expect.anything() }) - }) - ``` +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. +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.js' +```ts +import { expect, test } from 'vitest' +import { generateId } from './generators.js' - test('"id" is a number', () => { - expect({ id: generateId() }).toEqual({ id: expect.any(Number) }) - }) - ``` +test('"id" is a number', () => { + expect({ id: generateId() }).toEqual({ id: expect.any(Number) }) +}) +``` ## expect.closeTo @@ -1237,174 +1234,174 @@ test('compare float in object properties', () => { - **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. +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']) - }) +```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. - ::: +:::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. +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' +```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' }), - ] - }) +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. - ::: +:::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. +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' +```ts +import { expect, test } from 'vitest' - test('variety has "Emp" in its name', () => { - const variety = { - name: 'Empire', - count: 1, - } - expect(variety).toEqual({ - name: expect.stringContaining('Emp'), - count: 1, - }) +test('variety has "Emp" in its name', () => { + const variety = { + name: 'Empire', + count: 1, + } + expect(variety).toEqual({ + name: expect.stringContaining('Emp'), + count: 1, }) - ``` +}) +``` - :::tip - You can use `expect.not` with this matcher to negate the expected value. - ::: +:::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. +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' +```ts +import { expect, test } from 'vitest' - test('variety ends with "re"', () => { - const variety = { - name: 'Empire', - count: 1, - } - expect(variety).toEqual({ - name: expect.stringMatching(/re$/), - count: 1, - }) +test('variety ends with "re"', () => { + const variety = { + name: 'Empire', + count: 1, + } + expect(variety).toEqual({ + name: expect.stringMatching(/re$/), + count: 1, }) - ``` +}) +``` - :::tip - You can use `expect.not` with this matcher to negate the expected value. - ::: +:::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). +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. +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. - ::: +:::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`. +You can extend default matchers with your own. This function is used to extend the matchers object with custom matchers. - ```ts - import { expect, test } from 'vitest' +When you define matchers that way, you also create asymmetric matchers that can be used like `expect.stringContaining`. - test('custom matchers', () => { - expect.extend({ - toBeFoo: (received, expected) => { - if (received !== 'foo') { - return { - message: () => `expected ${received} to be foo`, - pass: false, - } +```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). - ::: + expect('foo').toBeFoo() + expect({ foo: 'foo' }).toEqual({ foo: expect.toBeFoo() }) +}) +``` - This function is compatible with Jest's `expect.extend`, so any library that uses it to create custom matchers will work with Vitest. +::: tip +If you want your matchers to appear in every test, you should call this method inside [`setupFiles`](/config/#setupFiles). +::: - If you are using TypeScript, since Vitest 0.31.0 you can extend default `Assertion` interface in an ambient declaration file (e.g: `vitest.d.ts`) with the code below: +This function is compatible with Jest's `expect.extend`, so any library that uses it to create custom matchers will work with Vitest. - ```ts - interface CustomMatchers { - toBeFoo(): R - } +If you are using TypeScript, since Vitest 0.31.0 you can extend default `Assertion` interface in an ambient declaration file (e.g: `vitest.d.ts`) with the code below: - declare module 'vitest' { - interface Assertion extends CustomMatchers {} - interface AsymmetricMatchersContaining extends CustomMatchers {} - } - ``` +```ts +interface CustomMatchers { + toBeFoo(): R +} + +declare module 'vitest' { + interface Assertion extends CustomMatchers {} + interface AsymmetricMatchersContaining extends CustomMatchers {} +} +``` - ::: warning - Don't forget to include the ambient declaration file in your `tsconfig.json`. - ::: +::: warning +Don't forget to include the ambient declaration file in your `tsconfig.json`. +::: - :::tip - If you want to know more, checkout [guide on extending matchers](/guide/extending-matchers). - ::: +:::tip +If you want to know more, checkout [guide on extending matchers](/guide/extending-matchers). +::: diff --git a/docs/api/index.md b/docs/api/index.md index 2848986a892c..50dc8555bbca 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -43,17 +43,17 @@ In Jest, `TestFunction` can also be of type `(done: DoneCallback) => void`. If t - **Type:** `(name: string | Function, fn: TestFunction, timeout?: number | TestOptions) => void` - **Alias:** `it` - `test` defines a set of related expectations. It receives the test name and a function that holds the expectations to test. +`test` defines a set of related expectations. It receives the test name and a function that holds the expectations to test. - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds, and can be configured globally with [testTimeout](/config/#testtimeout) +Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds, and can be configured globally with [testTimeout](/config/#testtimeout) - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - test('should work as expected', () => { - expect(Math.sqrt(4)).toBe(2) - }) - ``` +test('should work as expected', () => { + expect(Math.sqrt(4)).toBe(2) +}) +``` ### test.extend @@ -61,75 +61,75 @@ In Jest, `TestFunction` can also be of type `(done: DoneCallback) => void`. If t - **Alias:** `it.extend` - **Version:** Vitest 0.32.3 - Use `test.extend` to extend the test context with custom fixtures. This will return a new `test` and it's also extendable, so you can compose more fixtures or override existing ones by extending it as you need. See [Extend Test Context](/guide/test-context.html#test-extend) for more information. +Use `test.extend` to extend the test context with custom fixtures. This will return a new `test` and it's also extendable, so you can compose more fixtures or override existing ones by extending it as you need. See [Extend Test Context](/guide/test-context.html#test-extend) for more information. - ```ts - import { expect, test } from 'vitest' - - const todos = [] - const archive = [] - - const myTest = test.extend({ - todos: async ({ task }, use) => { - todos.push(1, 2, 3) - await use(todos) - todos.length = 0 - }, - archive - }) - - myTest('add item', ({ todos }) => { - expect(todos.length).toBe(3) - - todos.push(4) - expect(todos.length).toBe(4) - }) - ``` +```ts +import { expect, test } from 'vitest' + +const todos = [] +const archive = [] + +const myTest = test.extend({ + todos: async ({ task }, use) => { + todos.push(1, 2, 3) + await use(todos) + todos.length = 0 + }, + archive +}) + +myTest('add item', ({ todos }) => { + expect(todos.length).toBe(3) + + todos.push(4) + expect(todos.length).toBe(4) +}) +``` ### test.skip - **Type:** `(name: string | Function, fn: TestFunction, timeout?: number | TestOptions) => void` - **Alias:** `it.skip` - If you want to skip running certain tests, but you don't want to delete the code due to any reason, you can use `test.skip` to avoid running them. +If you want to skip running certain tests, but you don't want to delete the code due to any reason, you can use `test.skip` to avoid running them. - ```ts - import { assert, test } from 'vitest' +```ts +import { assert, test } from 'vitest' - test.skip('skipped test', () => { - // Test skipped, no error - assert.equal(Math.sqrt(4), 3) - }) - ``` +test.skip('skipped test', () => { + // Test skipped, no error + assert.equal(Math.sqrt(4), 3) +}) +``` - You can also skip test by calling `skip` on its [context](/guide/test-context) dynamically: +You can also skip test by calling `skip` on its [context](/guide/test-context) dynamically: - ```ts - import { assert, test } from 'vitest' +```ts +import { assert, test } from 'vitest' - test('skipped test', (context) => { - context.skip() - // Test skipped, no error - assert.equal(Math.sqrt(4), 3) - }) - ``` +test('skipped test', (context) => { + context.skip() + // Test skipped, no error + assert.equal(Math.sqrt(4), 3) +}) +``` ### test.skipIf - **Type:** `(condition: any) => Test` - **Alias:** `it.skipIf` - In some cases you might run tests multiple times with different environments, and some of the tests might be environment-specific. Instead of wrapping the test code with `if`, you can use `test.skipIf` to skip the test whenever the condition is truthy. +In some cases you might run tests multiple times with different environments, and some of the tests might be environment-specific. Instead of wrapping the test code with `if`, you can use `test.skipIf` to skip the test whenever the condition is truthy. - ```ts - import { assert, test } from 'vitest' +```ts +import { assert, test } from 'vitest' - const isDev = process.env.NODE_ENV === 'development' +const isDev = process.env.NODE_ENV === 'development' - test.skipIf(isDev)('prod only test', () => { - // this test only runs in production - }) - ``` +test.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). @@ -140,17 +140,17 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t - **Type:** `(condition: any) => Test` - **Alias:** `it.runIf` - Opposite of [test.skipIf](#test-skipif). +Opposite of [test.skipIf](#test-skipif). - ```ts - import { assert, test } from 'vitest' +```ts +import { assert, test } from 'vitest' - const isDev = process.env.NODE_ENV === 'development' +const isDev = process.env.NODE_ENV === 'development' - test.runIf(isDev)('dev only test', () => { - // this test only runs in development - }) - ``` +test.runIf(isDev)('dev only test', () => { + // this test only runs in development +}) +``` ::: warning You cannot use this syntax, when using Vitest as [type checker](/guide/testing-types). @@ -161,64 +161,64 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t - **Type:** `(name: string | Function, fn: TestFunction, timeout?: number) => void` - **Alias:** `it.only` - Use `test.only` to only run certain tests in a given suite. This is useful when debugging. +Use `test.only` to only run certain tests in a given suite. This is useful when debugging. - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds, and can be configured globally with [testTimeout](/config/#testtimeout). +Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds, and can be configured globally with [testTimeout](/config/#testtimeout). - ```ts - import { assert, test } from 'vitest' +```ts +import { assert, test } from 'vitest' - test.only('test', () => { - // Only this test (and others marked with only) are run - assert.equal(Math.sqrt(4), 2) - }) - ``` +test.only('test', () => { + // Only this test (and others marked with only) are run + assert.equal(Math.sqrt(4), 2) +}) +``` - Sometimes it is very useful to run `only` tests in a certain file, ignoring all other tests from the whole test suite, which pollute the output. +Sometimes it is very useful to run `only` tests in a certain file, ignoring all other tests from the whole test suite, which pollute the output. - In order to do that run `vitest` with specific file containing the tests in question. - ``` - # vitest interesting.test.ts - ``` +In order to do that run `vitest` with specific file containing the tests in question. +``` +# vitest interesting.test.ts +``` ### test.concurrent - **Type:** `(name: string | Function, fn: TestFunction, timeout?: number) => void` - **Alias:** `it.concurrent` - `test.concurrent` marks consecutive tests to be run in parallel. It receives the test name, an async function with the tests to collect, and an optional timeout (in milliseconds). +`test.concurrent` marks consecutive tests to be run in parallel. It receives the test name, an async function with the tests to collect, and an optional timeout (in milliseconds). - ```ts - import { describe, test } from 'vitest' - - // The two tests marked with concurrent will be run in parallel - describe('suite', () => { - test('serial test', async () => { /* ... */ }) - test.concurrent('concurrent test 1', async () => { /* ... */ }) - test.concurrent('concurrent test 2', async () => { /* ... */ }) - }) - ``` +```ts +import { describe, test } from 'vitest' + +// The two tests marked with concurrent will be run in parallel +describe('suite', () => { + test('serial test', async () => { /* ... */ }) + test.concurrent('concurrent test 1', async () => { /* ... */ }) + test.concurrent('concurrent test 2', async () => { /* ... */ }) +}) +``` - `test.skip`, `test.only`, and `test.todo` works with concurrent tests. All the following combinations are valid: +`test.skip`, `test.only`, and `test.todo` works with concurrent tests. All the following combinations are valid: - ```ts - test.concurrent(/* ... */) - test.skip.concurrent(/* ... */) // or test.concurrent.skip(/* ... */) - test.only.concurrent(/* ... */) // or test.concurrent.only(/* ... */) - test.todo.concurrent(/* ... */) // or test.concurrent.todo(/* ... */) - ``` +```ts +test.concurrent(/* ... */) +test.skip.concurrent(/* ... */) // or test.concurrent.skip(/* ... */) +test.only.concurrent(/* ... */) // or test.concurrent.only(/* ... */) +test.todo.concurrent(/* ... */) // or test.concurrent.todo(/* ... */) +``` - When running concurrent tests, Snapshots and Assertions must use `expect` from the local [Test Context](/guide/test-context.md) to ensure the right test is detected. +When running concurrent tests, Snapshots and Assertions must use `expect` from the local [Test Context](/guide/test-context.md) to ensure the right test is detected. - ```ts - test.concurrent('test 1', async ({ expect }) => { - expect(foo).toMatchSnapshot() - }) - test.concurrent('test 2', async ({ expect }) => { - expect(foo).toMatchSnapshot() - }) - ``` +```ts +test.concurrent('test 1', async ({ expect }) => { + expect(foo).toMatchSnapshot() +}) +test.concurrent('test 2', async ({ expect }) => { + expect(foo).toMatchSnapshot() +}) +``` ::: warning You cannot use this syntax, when using Vitest as [type checker](/guide/testing-types). @@ -229,30 +229,30 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t - **Type:** `(name: string | Function) => void` - **Alias:** `it.todo` - Use `test.todo` to stub tests to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement. +Use `test.todo` to stub tests to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement. - ```ts - // An entry will be shown in the report for this test - test.todo('unimplemented test') - ``` +```ts +// An entry will be shown in the report for this test +test.todo('unimplemented test') +``` ### test.fails - **Type:** `(name: string | Function, fn: TestFunction, timeout?: number) => void` - **Alias:** `it.fails` - Use `test.fails` to indicate that an assertion will fail explicitly. +Use `test.fails` to indicate that an assertion will fail explicitly. - ```ts - import { expect, test } from 'vitest' +```ts +import { expect, test } from 'vitest' - function myAsyncFunc() { - return new Promise(resolve => resolve(1)) - } - test.fails('fail test', async () => { - await expect(myAsyncFunc()).rejects.toBe(1) - }) - ``` +function myAsyncFunc() { + return new Promise(resolve => resolve(1)) +} +test.fails('fail test', async () => { + await expect(myAsyncFunc()).rejects.toBe(1) +}) +``` ::: warning You cannot use this syntax, when using Vitest as [type checker](/guide/testing-types). @@ -263,88 +263,87 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t - **Type:** `(cases: ReadonlyArray, ...args: any[]) => void` - **Alias:** `it.each` - Use `test.each` when you need to run the same test with different variables. - You can inject parameters with [printf formatting](https://nodejs.org/api/util.html#util_util_format_format_args) in the test name in the order of the test function parameters. +Use `test.each` when you need to run the same test with different variables. +You can inject parameters with [printf formatting](https://nodejs.org/api/util.html#util_util_format_format_args) in the test name in the order of the test function parameters. - - `%s`: string - - `%d`: number - - `%i`: integer - - `%f`: floating point value - - `%j`: json - - `%o`: object - - `%#`: index of the test case - - `%%`: single percent sign ('%') +- `%s`: string +- `%d`: number +- `%i`: integer +- `%f`: floating point value +- `%j`: json +- `%o`: object +- `%#`: index of the test case +- `%%`: single percent sign ('%') + +```ts +test.each([ + [1, 1, 2], + [1, 2, 3], + [2, 1, 3], +])('add(%i, %i) -> %i', (a, b, expected) => { + expect(a + b).toBe(expected) +}) + +// this will return +// ✓ add(1, 1) -> 2 +// ✓ add(1, 2) -> 3 +// ✓ add(2, 1) -> 3 +``` + +You can also access object properties with `$` prefix, if you are using objects as arguments: ```ts test.each([ - [1, 1, 2], - [1, 2, 3], - [2, 1, 3], - ])('add(%i, %i) -> %i', (a, b, expected) => { + { a: 1, b: 1, expected: 2 }, + { a: 1, b: 2, expected: 3 }, + { a: 2, b: 1, expected: 3 }, + ])('add($a, $b) -> $expected', ({ a, b, expected }) => { expect(a + b).toBe(expected) }) - // this will return - // ✓ add(1, 1) -> 2 - // ✓ add(1, 2) -> 3 - // ✓ add(2, 1) -> 3 - ``` +// this will return +// ✓ add(1, 1) -> 2 +// ✓ add(1, 2) -> 3 +// ✓ add(2, 1) -> 3 +``` - You can also access object properties with `$` prefix, if you are using objects as arguments: +You can also access Object attributes with `.`, if you are using objects as arguments: - ```ts - test.each([ - { a: 1, b: 1, expected: 2 }, - { a: 1, b: 2, expected: 3 }, - { a: 2, b: 1, expected: 3 }, - ])('add($a, $b) -> $expected', ({ a, b, expected }) => { - expect(a + b).toBe(expected) - }) + ```ts + test.each` + a | b | expected + ${{ val: 1 }} | ${'b'} | ${'1b'} + ${{ val: 2 }} | ${'b'} | ${'2b'} + ${{ val: 3 }} | ${'b'} | ${'3b'} + `('add($a.val, $b) -> $expected', ({ a, b, expected }) => { + expect(a.val + b).toBe(expected) + }) // this will return - // ✓ add(1, 1) -> 2 - // ✓ add(1, 2) -> 3 - // ✓ add(2, 1) -> 3 + // ✓ add(1, b) -> 1b + // ✓ add(2, b) -> 2b + // ✓ add(3, b) -> 3b ``` - You can also access Object attributes with `.`, if you are using objects as arguments: - - ```ts - test.each` - a | b | expected - ${{ val: 1 }} | ${'b'} | ${'1b'} - ${{ val: 2 }} | ${'b'} | ${'2b'} - ${{ val: 3 }} | ${'b'} | ${'3b'} - `('add($a.val, $b) -> $expected', ({ a, b, expected }) => { - expect(a.val + b).toBe(expected) - }) - - // this will return - // ✓ add(1, b) -> 1b - // ✓ add(2, b) -> 2b - // ✓ add(3, b) -> 3b - ``` - - - Starting from Vitest 0.25.3, you can also use template string table. +Starting from Vitest 0.25.3, you can also use template string table. - * First row should be column names, separated by `|`; - * One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax. +* First row should be column names, separated by `|`; +* One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax. - ```ts - test.each` - a | b | expected - ${1} | ${1} | ${2} - ${'a'} | ${'b'} | ${'ab'} - ${[]} | ${'b'} | ${'b'} - ${{}} | ${'b'} | ${'[object Object]b'} - ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} - `('returns $expected when $a is added $b', ({ a, b, expected }) => { - expect(a + b).toBe(expected) - }) - ``` +```ts +test.each` + a | b | expected + ${1} | ${1} | ${2} + ${'a'} | ${'b'} | ${'ab'} + ${[]} | ${'b'} | ${'b'} + ${{}} | ${'b'} | ${'[object Object]b'} + ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} +`('returns $expected when $a is added $b', ({ a, b, expected }) => { + expect(a + b).toBe(expected) +}) +``` - If you want to have access to `TestContext`, use `describe.each` with a single test. +If you want to have access to `TestContext`, use `describe.each` with a single test. ::: tip Vitest processes `$values` with chai `format` method. If the value is too truncated, you can increase [chaiConfig.truncateThreshold](/config/#chaiconfig-truncatethreshold) in your config file. @@ -362,64 +361,64 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t Vitest uses [`tinybench`](https://github.com/tinylibs/tinybench) library under the hood, inheriting all its options that can be used as a third argument. - ```ts - import { bench } from 'vitest' +```ts +import { bench } from 'vitest' - bench('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) - }, { time: 1000 }) - ``` +bench('normal sorting', () => { + const x = [1, 5, 4, 2, 3] + x.sort((a, b) => { + return a - b + }) +}, { time: 1000 }) +``` - ```ts - export interface Options { - /** - * time needed for running a benchmark task (milliseconds) - * @default 500 - */ - time?: number - - /** - * number of times that a task should run if even the time option is finished - * @default 10 - */ - iterations?: number - - /** - * function to get the current timestamp in milliseconds - */ - now?: () => number - - /** - * An AbortSignal for aborting the benchmark - */ - signal?: AbortSignal - - /** - * warmup time (milliseconds) - * @default 100ms - */ - warmupTime?: number - - /** - * warmup iterations - * @default 5 - */ - warmupIterations?: number - - /** - * setup function to run before each benchmark task (cycle) - */ - setup?: Hook - - /** - * teardown function to run after each benchmark task (cycle) - */ - teardown?: Hook - } - ``` +```ts +export interface Options { + /** + * time needed for running a benchmark task (milliseconds) + * @default 500 + */ + time?: number + + /** + * number of times that a task should run if even the time option is finished + * @default 10 + */ + iterations?: number + + /** + * function to get the current timestamp in milliseconds + */ + now?: () => number + + /** + * An AbortSignal for aborting the benchmark + */ + signal?: AbortSignal + + /** + * warmup time (milliseconds) + * @default 100ms + */ + warmupTime?: number + + /** + * warmup iterations + * @default 5 + */ + warmupIterations?: number + + /** + * setup function to run before each benchmark task (cycle) + */ + setup?: Hook + + /** + * teardown function to run after each benchmark task (cycle) + */ + teardown?: Hook +} +``` ### bench.skip @@ -427,16 +426,16 @@ Vitest uses [`tinybench`](https://github.com/tinylibs/tinybench) library under t You can use `bench.skip` syntax to skip running certain benchmarks. - ```ts - import { bench } from 'vitest' +```ts +import { bench } from 'vitest' - bench.skip('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) +bench.skip('normal sorting', () => { + const x = [1, 5, 4, 2, 3] + x.sort((a, b) => { + return a - b }) - ``` +}) +``` ### bench.only @@ -444,16 +443,16 @@ You can use `bench.skip` syntax to skip running certain benchmarks. Use `bench.only` to only run certain benchmarks in a given suite. This is useful when debugging. - ```ts - import { bench } from 'vitest' +```ts +import { bench } from 'vitest' - bench.only('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) +bench.only('normal sorting', () => { + const x = [1, 5, 4, 2, 3] + x.sort((a, b) => { + return a - b }) - ``` +}) +``` ### bench.todo @@ -461,124 +460,124 @@ Use `bench.only` to only run certain benchmarks in a given suite. This is useful Use `bench.todo` to stub benchmarks to be implemented later. - ```ts - import { bench } from 'vitest' +```ts +import { bench } from 'vitest' - bench.todo('unimplemented test') - ``` +bench.todo('unimplemented test') +``` ## describe When you use `test` or `bench` in the top level of file, they are collected as part of the implicit suite for it. Using `describe` you can define a new suite in the current context, as a set of related tests or benchmarks and other nested suites. A suite lets you organize your tests and benchmarks so reports are more clear. - ```ts - // basic.spec.ts - // organizing tests +```ts +// basic.spec.ts +// organizing tests - import { describe, expect, test } from 'vitest' +import { describe, expect, test } from 'vitest' - const person = { - isActive: true, - age: 32, - } +const person = { + isActive: true, + age: 32, +} - describe('person', () => { - test('person is defined', () => { - expect(person).toBeDefined() - }) +describe('person', () => { + test('person is defined', () => { + expect(person).toBeDefined() + }) - test('is active', () => { - expect(person.isActive).toBeTruthy() - }) + test('is active', () => { + expect(person.isActive).toBeTruthy() + }) - test('age limit', () => { - expect(person.age).toBeLessThanOrEqual(32) - }) + test('age limit', () => { + expect(person.age).toBeLessThanOrEqual(32) }) - ``` +}) +``` - ```ts - // basic.bench.ts - // organizing benchmarks +```ts +// basic.bench.ts +// organizing benchmarks - import { bench, describe } from 'vitest' +import { bench, describe } from 'vitest' - describe('sort', () => { - bench('normal', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) +describe('sort', () => { + bench('normal', () => { + const x = [1, 5, 4, 2, 3] + x.sort((a, b) => { + return a - b }) + }) - bench('reverse', () => { - const x = [1, 5, 4, 2, 3] - x.reverse().sort((a, b) => { - return a - b - }) + bench('reverse', () => { + const x = [1, 5, 4, 2, 3] + x.reverse().sort((a, b) => { + return a - b }) }) - ``` +}) +``` - You can also nest describe blocks if you have a hierarchy of tests or benchmarks: +You can also nest describe blocks if you have a hierarchy of tests or benchmarks: - ```ts - import { describe, expect, test } from 'vitest' +```ts +import { describe, expect, test } from 'vitest' - function numberToCurrency(value) { - if (typeof value !== 'number') - throw new Error('Value must be a number') +function numberToCurrency(value) { + if (typeof value !== 'number') + throw new Error('Value must be a number') - return value.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') - } + return value.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') +} - describe('numberToCurrency', () => { - describe('given an invalid number', () => { - test('composed of non-numbers to throw error', () => { - expect(() => numberToCurrency('abc')).toThrowError() - }) +describe('numberToCurrency', () => { + describe('given an invalid number', () => { + test('composed of non-numbers to throw error', () => { + expect(() => numberToCurrency('abc')).toThrowError() }) + }) - describe('given a valid number', () => { - test('returns the correct currency format', () => { - expect(numberToCurrency(10000)).toBe('10,000.00') - }) + describe('given a valid number', () => { + test('returns the correct currency format', () => { + expect(numberToCurrency(10000)).toBe('10,000.00') }) }) - ``` +}) +``` ### describe.skip - **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` - Use `describe.skip` in a suite to avoid running a particular describe block. +Use `describe.skip` in a suite to avoid running a particular describe block. - ```ts - import { assert, describe, test } from 'vitest' +```ts +import { assert, describe, test } from 'vitest' - describe.skip('skipped suite', () => { - test('sqrt', () => { - // Suite skipped, no error - assert.equal(Math.sqrt(4), 3) - }) +describe.skip('skipped suite', () => { + test('sqrt', () => { + // Suite skipped, no error + assert.equal(Math.sqrt(4), 3) }) - ``` +}) +``` ### 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. +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 { describe, test } from 'vitest' +```ts +import { describe, test } from 'vitest' - const isDev = process.env.NODE_ENV === 'development' +const isDev = process.env.NODE_ENV === 'development' - describe.skipIf(isDev)('prod only test', () => { - // this test only runs in production - }) - ``` +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). @@ -588,65 +587,65 @@ You cannot use this syntax when using Vitest as [type checker](/guide/testing-ty - **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` - Use `describe.only` to only run certain suites +Use `describe.only` to only run certain suites - ```ts - // Only this suite (and others marked with only) are run - describe.only('suite', () => { - test('sqrt', () => { - assert.equal(Math.sqrt(4), 3) - }) +```ts +// Only this suite (and others marked with only) are run +describe.only('suite', () => { + test('sqrt', () => { + assert.equal(Math.sqrt(4), 3) }) +}) - describe('other suite', () => { - // ... will be skipped - }) - ``` +describe('other suite', () => { + // ... will be skipped +}) +``` - Sometimes it is very useful to run `only` tests in a certain file, ignoring all other tests from the whole test suite, which pollute the output. +Sometimes it is very useful to run `only` tests in a certain file, ignoring all other tests from the whole test suite, which pollute the output. - In order to do that run `vitest` with specific file containing the tests in question. - ``` - # vitest interesting.test.ts - ``` +In order to do that run `vitest` with specific file containing the tests in question. +``` +# vitest interesting.test.ts +``` ### describe.concurrent - **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` - `describe.concurrent` in a suite marks every tests as concurrent +`describe.concurrent` in a suite marks every tests as concurrent - ```ts - // All tests within this suite will be run in parallel - describe.concurrent('suite', () => { - test('concurrent test 1', async () => { /* ... */ }) - test('concurrent test 2', async () => { /* ... */ }) - test.concurrent('concurrent test 3', async () => { /* ... */ }) - }) - ``` +```ts +// All tests within this suite will be run in parallel +describe.concurrent('suite', () => { + test('concurrent test 1', async () => { /* ... */ }) + test('concurrent test 2', async () => { /* ... */ }) + test.concurrent('concurrent test 3', async () => { /* ... */ }) +}) +``` - `.skip`, `.only`, and `.todo` works with concurrent suites. All the following combinations are valid: +`.skip`, `.only`, and `.todo` works with concurrent suites. All the following combinations are valid: - ```ts - describe.concurrent(/* ... */) - describe.skip.concurrent(/* ... */) // or describe.concurrent.skip(/* ... */) - describe.only.concurrent(/* ... */) // or describe.concurrent.only(/* ... */) - describe.todo.concurrent(/* ... */) // or describe.concurrent.todo(/* ... */) - ``` +```ts +describe.concurrent(/* ... */) +describe.skip.concurrent(/* ... */) // or describe.concurrent.skip(/* ... */) +describe.only.concurrent(/* ... */) // or describe.concurrent.only(/* ... */) +describe.todo.concurrent(/* ... */) // or describe.concurrent.todo(/* ... */) +``` When running concurrent tests, Snapshots and Assertions must use `expect` from the local [Test Context](/guide/test-context.md) to ensure the right test is detected. - - ```ts - describe.concurrent('suite', () => { - test('concurrent test 1', async ({ expect }) => { - expect(foo).toMatchSnapshot() - }) - test('concurrent test 2', async ({ expect }) => { - expect(foo).toMatchSnapshot() - }) +```ts +describe.concurrent('suite', () => { + test('concurrent test 1', async ({ expect }) => { + expect(foo).toMatchSnapshot() }) - ``` + test('concurrent test 2', async ({ expect }) => { + expect(foo).toMatchSnapshot() + }) +}) +``` + ::: warning You cannot use this syntax, when using Vitest as [type checker](/guide/testing-types). ::: @@ -655,34 +654,34 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t - **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` - `describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequential within `describe.concurrent` or with the `--sequence.concurrent` command option. +`describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequential within `describe.concurrent` or with the `--sequence.concurrent` command option. - ```ts - describe.concurrent('suite', () => { - test('concurrent test 1', async () => { /* ... */ }) - test('concurrent test 2', async () => { /* ... */ }) +```ts +describe.concurrent('suite', () => { + test('concurrent test 1', async () => { /* ... */ }) + test('concurrent test 2', async () => { /* ... */ }) - describe.sequential('', () => { - test('sequential test 1', async () => { /* ... */ }) - test('sequential test 2', async () => { /* ... */ }) - }) + describe.sequential('', () => { + test('sequential test 1', async () => { /* ... */ }) + test('sequential test 2', async () => { /* ... */ }) }) - ``` +}) +``` ### describe.shuffle - **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` - Vitest provides a way to run all tests in random order via CLI flag [`--sequence.shuffle`](/guide/cli) or config option [`sequence.shuffle`](/config/#sequence-shuffle), but if you want to have only part of your test suite to run tests in random order, you can mark it with this flag. +Vitest provides a way to run all tests in random order via CLI flag [`--sequence.shuffle`](/guide/cli) or config option [`sequence.shuffle`](/config/#sequence-shuffle), but if you want to have only part of your test suite to run tests in random order, you can mark it with this flag. - ```ts - describe.shuffle('suite', () => { - test('random test 1', async () => { /* ... */ }) - test('random test 2', async () => { /* ... */ }) - test('random test 3', async () => { /* ... */ }) - }) - // order depends on sequence.seed option in config (Date.now() by default) - ``` +```ts +describe.shuffle('suite', () => { + test('random test 1', async () => { /* ... */ }) + test('random test 2', async () => { /* ... */ }) + test('random test 3', async () => { /* ... */ }) +}) +// order depends on sequence.seed option in config (Date.now() by default) +``` `.skip`, `.only`, and `.todo` works with random suites. @@ -694,58 +693,58 @@ You cannot use this syntax, when using Vitest as [type checker](/guide/testing-t - **Type:** `(name: string | Function) => void` - Use `describe.todo` to stub suites to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement. +Use `describe.todo` to stub suites to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement. - ```ts - // An entry will be shown in the report for this suite - describe.todo('unimplemented suite') - ``` +```ts +// An entry will be shown in the report for this suite +describe.todo('unimplemented suite') +``` ### describe.each - **Type:** `(cases: ReadonlyArray, ...args: any[]): (name: string | Function, fn: (...args: T[]) => void, options?: number | TestOptions) => void` - Use `describe.each` if you have more than one test that depends on the same data. +Use `describe.each` if you have more than one test that depends on the same data. - ```ts - describe.each([ - { a: 1, b: 1, expected: 2 }, - { a: 1, b: 2, expected: 3 }, - { a: 2, b: 1, expected: 3 }, - ])('describe object add($a, $b)', ({ a, b, expected }) => { - test(`returns ${expected}`, () => { - expect(a + b).toBe(expected) - }) +```ts +describe.each([ + { a: 1, b: 1, expected: 2 }, + { a: 1, b: 2, expected: 3 }, + { a: 2, b: 1, expected: 3 }, +])('describe object add($a, $b)', ({ a, b, expected }) => { + test(`returns ${expected}`, () => { + expect(a + b).toBe(expected) + }) - test(`returned value not be greater than ${expected}`, () => { - expect(a + b).not.toBeGreaterThan(expected) - }) + test(`returned value not be greater than ${expected}`, () => { + expect(a + b).not.toBeGreaterThan(expected) + }) - test(`returned value not be less than ${expected}`, () => { - expect(a + b).not.toBeLessThan(expected) - }) + test(`returned value not be less than ${expected}`, () => { + expect(a + b).not.toBeLessThan(expected) }) - ``` +}) +``` - Starting from Vitest 0.25.3, you can also use template string table. +Starting from Vitest 0.25.3, you can also use template string table. - * First row should be column names, separated by `|`; - * One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax. +* First row should be column names, separated by `|`; +* One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax. - ```ts - describe.each` - a | b | expected - ${1} | ${1} | ${2} - ${'a'} | ${'b'} | ${'ab'} - ${[]} | ${'b'} | ${'b'} - ${{}} | ${'b'} | ${'[object Object]b'} - ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} - `('describe template string add($a, $b)', ({ a, b, expected }) => { - test(`returns ${expected}`, () => { - expect(a + b).toBe(expected) - }) +```ts +describe.each` + a | b | expected + ${1} | ${1} | ${2} + ${'a'} | ${'b'} | ${'ab'} + ${[]} | ${'b'} | ${'b'} + ${{}} | ${'b'} | ${'[object Object]b'} + ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} +`('describe template string add($a, $b)', ({ a, b, expected }) => { + test(`returns ${expected}`, () => { + expect(a + b).toBe(expected) }) - ``` +}) +``` ::: warning You cannot use this syntax, when using Vitest as [type checker](/guide/testing-types). @@ -759,107 +758,108 @@ These functions allow you to hook into the life cycle of tests to avoid repeatin - **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. +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. +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' +```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' }) - }) - ``` +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. +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`). +Since Vitest v0.10.0, `beforeEach` also accepts an optional cleanup function (equivalent to `afterEach`). - ```ts - import { beforeEach } from 'vitest' +```ts +import { beforeEach } from 'vitest' - beforeEach(async () => { - // called once before each test run - await prepareSomething() +beforeEach(async () => { + // called once before each test run + await prepareSomething() - // clean up function, called once after each test run - return async () => { - await resetSomething() - } - }) - ``` + // 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. +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. +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' +```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. +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. +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. +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' +```ts +import { beforeAll } from 'vitest' - beforeAll(async () => { - await startMocking() // called once before all tests run - }) - ``` +beforeAll(async () => { + await startMocking() // called once before all tests run +}) +``` - Here the `beforeAll` ensures that the mock data is set up before 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`). +Since Vitest v0.10.0, `beforeAll` also accepts an optional cleanup function (equivalent to `afterAll`). - ```ts - import { beforeAll } from 'vitest' +```ts +import { beforeAll } from 'vitest' - beforeAll(async () => { - // called once before all tests run - await startMocking() +beforeAll(async () => { + // called once before all tests run + await startMocking() - // clean up function, called once after all tests run - return async () => { - await stopMocking() - } - }) - ``` + // 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. +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. +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' +```ts +import { afterAll } from 'vitest' - afterAll(async () => { - await stopMocking() // this method is called after all tests run - }) - ``` +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. +Here the `afterAll` ensures that `stopMocking` method is called after all tests run. diff --git a/docs/guide/migration.md b/docs/guide/migration.md index afae329f06a2..a242d6d63ddd 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -84,7 +84,7 @@ export default defineConfig({ ### Types -Vitest doesn't expose a lot of types on `Vi` namespace, it exists mainly for compatibility with matchers, so you might need to import types directly from `vitest` instead of relying on `Vi` namespace: +Vitest doesn't have an equivalent to `jest` namespace, so you will need to import types directly from `vitest`: ```ts let fn: jest.Mock // [!code --] From 9fe38737984113a7e26bdf855ac8104ef1aeaba4 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 2 Nov 2023 23:10:34 +0900 Subject: [PATCH 8/9] fix: copy custom asymmetric matchers to local `expect` (#4405) --- packages/expect/src/constants.ts | 1 + packages/expect/src/jest-extend.ts | 15 ++++++- packages/expect/src/state.ts | 6 ++- .../vitest/src/integrations/chai/index.ts | 3 +- test/core/test/local-context.test.ts | 39 +++++++++++++++++++ 5 files changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/expect/src/constants.ts b/packages/expect/src/constants.ts index 97a47089fd1a..70055a68b521 100644 --- a/packages/expect/src/constants.ts +++ b/packages/expect/src/constants.ts @@ -1,3 +1,4 @@ export const MATCHERS_OBJECT = Symbol.for('matchers-object') export const JEST_MATCHERS_OBJECT = Symbol.for('$$jest-matchers-object') export const GLOBAL_EXPECT = Symbol.for('expect-global') +export const ASYMMETRIC_MATCHERS_OBJECT = Symbol.for('asymmetric-matchers-object') diff --git a/packages/expect/src/jest-extend.ts b/packages/expect/src/jest-extend.ts index f6160c63fb83..fba626bcefb4 100644 --- a/packages/expect/src/jest-extend.ts +++ b/packages/expect/src/jest-extend.ts @@ -6,7 +6,7 @@ import type { MatchersObject, SyncExpectationResult, } from './types' -import { JEST_MATCHERS_OBJECT } from './constants' +import { ASYMMETRIC_MATCHERS_OBJECT, JEST_MATCHERS_OBJECT } from './constants' import { AsymmetricMatcher } from './jest-asymmetric-matchers' import { getState } from './state' @@ -108,10 +108,12 @@ function JestExtendPlugin(expect: ExpectStatic, matchers: MatchersObject): ChaiP } } + const customMatcher = (...sample: [unknown, ...unknown[]]) => new CustomMatcher(false, ...sample) + Object.defineProperty(expect, expectAssertionName, { configurable: true, enumerable: true, - value: (...sample: [unknown, ...unknown[]]) => new CustomMatcher(false, ...sample), + value: customMatcher, writable: true, }) @@ -121,6 +123,15 @@ function JestExtendPlugin(expect: ExpectStatic, matchers: MatchersObject): ChaiP value: (...sample: [unknown, ...unknown[]]) => new CustomMatcher(true, ...sample), writable: true, }) + + // keep track of asymmetric matchers on global so that it can be copied over to local context's `expect`. + // note that the negated variant is automatically shared since it's assigned on the single `expect.not` object. + Object.defineProperty(((globalThis as any)[ASYMMETRIC_MATCHERS_OBJECT]), expectAssertionName, { + configurable: true, + enumerable: true, + value: customMatcher, + writable: true, + }) }) } } diff --git a/packages/expect/src/state.ts b/packages/expect/src/state.ts index 4d830e926a85..7600a6d8dda0 100644 --- a/packages/expect/src/state.ts +++ b/packages/expect/src/state.ts @@ -1,9 +1,10 @@ import type { ExpectStatic, MatcherState } from './types' -import { GLOBAL_EXPECT, JEST_MATCHERS_OBJECT, MATCHERS_OBJECT } from './constants' +import { ASYMMETRIC_MATCHERS_OBJECT, GLOBAL_EXPECT, JEST_MATCHERS_OBJECT, MATCHERS_OBJECT } from './constants' if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) { const globalState = new WeakMap() const matchers = Object.create(null) + const assymetricMatchers = Object.create(null) Object.defineProperty(globalThis, MATCHERS_OBJECT, { get: () => globalState, }) @@ -14,6 +15,9 @@ if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) { matchers, }), }) + Object.defineProperty(globalThis, ASYMMETRIC_MATCHERS_OBJECT, { + get: () => assymetricMatchers, + }) } export function getState(expect: ExpectStatic): State { diff --git a/packages/vitest/src/integrations/chai/index.ts b/packages/vitest/src/integrations/chai/index.ts index 41b64d16a700..3422094956d5 100644 --- a/packages/vitest/src/integrations/chai/index.ts +++ b/packages/vitest/src/integrations/chai/index.ts @@ -4,7 +4,7 @@ import * as chai from 'chai' import './setup' import type { TaskPopulated, Test } from '@vitest/runner' import { getCurrentTest } from '@vitest/runner' -import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect' +import { ASYMMETRIC_MATCHERS_OBJECT, GLOBAL_EXPECT, getState, setState } from '@vitest/expect' import type { Assertion, ExpectStatic } from '@vitest/expect' import type { MatcherState } from '../../types/chai' import { getFullName } from '../../utils/tasks' @@ -23,6 +23,7 @@ export function createExpect(test?: TaskPopulated) { return assert }) as ExpectStatic Object.assign(expect, chai.expect) + Object.assign(expect, (globalThis as any)[ASYMMETRIC_MATCHERS_OBJECT]) expect.getState = () => getState(expect) expect.setState = state => setState(state as Partial, expect) diff --git a/test/core/test/local-context.test.ts b/test/core/test/local-context.test.ts index 8ace7b7e3489..65fa3bae2f87 100644 --- a/test/core/test/local-context.test.ts +++ b/test/core/test/local-context.test.ts @@ -36,3 +36,42 @@ describe('context expect', () => { expect(localExpect.getState().snapshotState).toBeDefined() }) }) + +describe('custom matcher are inherited by local context', () => { + expect.extend({ + toEqual_testCustom(received, expected) { + return { + pass: received === expected, + message: () => `test`, + } + }, + }) + + it('basic', ({ expect: localExpect }) => { + // as assertion + expect(expect('test')).toHaveProperty('toEqual_testCustom') + expect(expect.soft('test')).toHaveProperty('toEqual_testCustom') + expect(localExpect('test')).toHaveProperty('toEqual_testCustom') + expect(localExpect.soft('test')).toHaveProperty('toEqual_testCustom') + + // as asymmetric matcher + expect(expect).toHaveProperty('toEqual_testCustom') + expect(expect.not).toHaveProperty('toEqual_testCustom') + expect(localExpect).toHaveProperty('toEqual_testCustom') + expect(localExpect.not).toHaveProperty('toEqual_testCustom'); + + (expect(0) as any).toEqual_testCustom(0); + (expect(0) as any).not.toEqual_testCustom(1); + (localExpect(0) as any).toEqual_testCustom(0); + (localExpect(0) as any).not.toEqual_testCustom(1) + + expect(0).toEqual((expect as any).toEqual_testCustom(0)) + localExpect(0).toEqual((localExpect as any).toEqual_testCustom(0)) + expect(0).toEqual((expect.not as any).toEqual_testCustom(1)) + localExpect(0).toEqual((localExpect.not as any).toEqual_testCustom(1)) + + // asymmetric matcher function is identical + expect((expect as any).toEqual_testCustom).toBe((localExpect as any).toEqual_testCustom) + expect((expect.not as any).toEqual_testCustom).toBe((localExpect.not as any).toEqual_testCustom) + }) +}) From ac3097267454905e918f9a334d7d80b90b50741e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 2 Nov 2023 23:11:35 +0900 Subject: [PATCH 9/9] fix: apply serializer to `Error` instance for thrown snapshot (#4396) Co-authored-by: Vladimir --- docs/api/expect.md | 4 - docs/guide/snapshot.md | 29 +++++- examples/mocks/test/error-mock.spec.ts | 2 +- .../vitest/src/integrations/snapshot/chai.ts | 17 ++-- .../test/__snapshots__/mocked.test.ts.snap | 16 ++-- .../test/__snapshots__/snapshot.test.ts.snap | 4 +- test/core/test/jest-expect.test.ts | 10 +-- test/core/test/nested-test.test.ts | 10 +-- .../test/snapshot-custom-serializer.test.ts | 88 +++++++++++++++++++ test/core/test/snapshot-inline.test.ts | 4 +- test/core/test/wait.test.ts | 4 +- test/snapshots/test/snapshots-async.test.ts | 2 +- test/utils/test/display.spec.ts | 2 +- 13 files changed, 148 insertions(+), 44 deletions(-) create mode 100644 test/core/test/snapshot-custom-serializer.test.ts diff --git a/docs/api/expect.md b/docs/api/expect.md index 0fee5b78f5d6..f5322a8e52cd 100644 --- a/docs/api/expect.md +++ b/docs/api/expect.md @@ -748,16 +748,12 @@ Note that since file system operation is async, you need to use `await` with `to 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` diff --git a/docs/guide/snapshot.md b/docs/guide/snapshot.md index aaa9be0b173c..85f52bb649b4 100644 --- a/docs/guide/snapshot.md +++ b/docs/guide/snapshot.md @@ -237,5 +237,32 @@ exports[`toThrowErrorMatchingSnapshot: hint 1`] = `"error"`; In Vitest, the equivalent snapshot will be: ```console -exports[`toThrowErrorMatchingSnapshot > hint 1`] = `"error"`; +exports[`toThrowErrorMatchingSnapshot > hint 1`] = `[Error: error]`; +``` + +#### 4. default `Error` snapshot is different for `toThrowErrorMatchingSnapshot` and `toThrowErrorMatchingInlineSnapshot` + +```js +test('snapshot', () => { + // + // in Jest + // + + expect(new Error('error')).toMatchInlineSnapshot(`[Error: error]`) + + // Jest snapshots `Error.message` for `Error` instance + expect(() => { + throw new Error('error') + }).toThrowErrorMatchingInlineSnapshot(`"error"`) + + // + // in Vitest + // + + expect(new Error('error')).toMatchInlineSnapshot(`[Error: error]`) + + expect(() => { + throw new Error('error') + }).toThrowErrorMatchingInlineSnapshot(`[Error: error]`) +}) ``` diff --git a/examples/mocks/test/error-mock.spec.ts b/examples/mocks/test/error-mock.spec.ts index d3be7092c524..4383147d208d 100644 --- a/examples/mocks/test/error-mock.spec.ts +++ b/examples/mocks/test/error-mock.spec.ts @@ -4,5 +4,5 @@ vi.mock('../src/default', () => { test('when using top level variable, gives helpful message', async () => { await expect(() => import('../src/default').then(m => m.default)).rejects - .toThrowErrorMatchingInlineSnapshot('"[vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock"') + .toThrowErrorMatchingInlineSnapshot(`[Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock]`) }) diff --git a/packages/vitest/src/integrations/snapshot/chai.ts b/packages/vitest/src/integrations/snapshot/chai.ts index 76d18b4c5847..4f86d0b0668f 100644 --- a/packages/vitest/src/integrations/snapshot/chai.ts +++ b/packages/vitest/src/integrations/snapshot/chai.ts @@ -18,27 +18,20 @@ export function getSnapshotClient(): SnapshotClient { return _client } -function getErrorMessage(err: unknown) { - if (err instanceof Error) - return err.message - - return err -} - -function getErrorString(expected: () => void | Error, promise: string | undefined) { +function getError(expected: () => void | Error, promise: string | undefined) { if (typeof expected !== 'function') { if (!promise) throw new Error(`expected must be a function, received ${typeof expected}`) // when "promised", it receives thrown error - return getErrorMessage(expected) + return expected } try { expected() } catch (e) { - return getErrorMessage(e) + return e } throw new Error('snapshot function didn\'t throw') @@ -141,7 +134,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { const promise = utils.flag(this, 'promise') as string | undefined const errorMessage = utils.flag(this, 'message') getSnapshotClient().assert({ - received: getErrorString(expected, promise), + received: getError(expected, promise), message, errorMessage, ...getTestNames(test), @@ -162,7 +155,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { const errorMessage = utils.flag(this, 'message') getSnapshotClient().assert({ - received: getErrorString(expected, promise), + received: getError(expected, promise), message, inlineSnapshot, isInline: true, diff --git a/test/core/test/__snapshots__/mocked.test.ts.snap b/test/core/test/__snapshots__/mocked.test.ts.snap index 8d62b2f3967b..3662a734f533 100644 --- a/test/core/test/__snapshots__/mocked.test.ts.snap +++ b/test/core/test/__snapshots__/mocked.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`mocked function which fails on toReturnWith > just one call 1`] = ` -"expected "spy" to return with: 2 at least once +[AssertionError: expected "spy" to return with: 2 at least once Received: @@ -12,11 +12,11 @@ Received: Number of calls: 1 -" +] `; exports[`mocked function which fails on toReturnWith > multi calls 1`] = ` -"expected "spy" to return with: 2 at least once +[AssertionError: expected "spy" to return with: 2 at least once Received: @@ -37,11 +37,11 @@ Received: Number of calls: 3 -" +] `; exports[`mocked function which fails on toReturnWith > oject type 1`] = ` -"expected "spy" to return with: { a: '4' } at least once +[AssertionError: expected "spy" to return with: { a: '4' } at least once Received: @@ -68,16 +68,16 @@ Received: Number of calls: 3 -" +] `; exports[`mocked function which fails on toReturnWith > zero call 1`] = ` -"expected "spy" to return with: 2 at least once +[AssertionError: expected "spy" to return with: 2 at least once Received: Number of calls: 0 -" +] `; diff --git a/test/core/test/__snapshots__/snapshot.test.ts.snap b/test/core/test/__snapshots__/snapshot.test.ts.snap index 75594ef64a1e..f3d33dccdfa1 100644 --- a/test/core/test/__snapshots__/snapshot.test.ts.snap +++ b/test/core/test/__snapshots__/snapshot.test.ts.snap @@ -60,7 +60,7 @@ exports[`single line snapshot 6`] = `"some string'"`; exports[`single line snapshot 7`] = `"some 'string'"`; -exports[`throwing 1`] = `"omega"`; +exports[`throwing 1`] = `[Error: omega]`; exports[`throwing 2`] = `"omega"`; @@ -70,7 +70,7 @@ exports[`throwing 3`] = ` } `; -exports[`throwing 4`] = `"omega"`; +exports[`throwing 4`] = `[Error: omega]`; exports[`with big array 1`] = ` { diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 9387910cc0b9..086c765960be 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -167,7 +167,7 @@ describe('jest-expect', () => { }).toEqual({ sum: expect.closeTo(0.4), }) - }).toThrowErrorMatchingInlineSnapshot(`"expected { sum: 0.30000000000000004 } to deeply equal { sum: CloseTo{ …(4) } }"`) + }).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { sum: 0.30000000000000004 } to deeply equal { sum: CloseTo{ …(4) } }]`) // TODO: support set // expect(new Set(['bar'])).not.toEqual(new Set([expect.stringContaining('zoo')])) @@ -302,14 +302,14 @@ describe('jest-expect', () => { expect(() => { expect(complex).toHaveProperty('a-b', false) - }).toThrowErrorMatchingInlineSnapshot('"expected { \'0\': \'zero\', foo: 1, …(4) } to have property "a-b" with value false"') + }).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { '0': 'zero', foo: 1, …(4) } to have property "a-b" with value false]`) expect(() => { const x = { a: { b: { c: 1 } } } const y = { a: { b: { c: 2 } } } Object.freeze(x.a) expect(x).toEqual(y) - }).toThrowErrorMatchingInlineSnapshot(`"expected { a: { b: { c: 1 } } } to deeply equal { a: { b: { c: 2 } } }"`) + }).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { a: { b: { c: 1 } } } to deeply equal { a: { b: { c: 2 } } }]`) }) it('assertions', () => { @@ -406,14 +406,14 @@ describe('jest-expect', () => { expect(() => { expect(() => { }).toThrow(Error) - }).toThrowErrorMatchingInlineSnapshot('"expected function to throw an error, but it didn\'t"') + }).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected function to throw an error, but it didn't]`) }) it('async wasn\'t awaited', () => { expect(() => { expect(async () => { }).toThrow(Error) - }).toThrowErrorMatchingInlineSnapshot('"expected function to throw an error, but it didn\'t"') + }).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected function to throw an error, but it didn't]`) }) }) }) diff --git a/test/core/test/nested-test.test.ts b/test/core/test/nested-test.test.ts index 54efdb671258..23cf7d38d29a 100644 --- a/test/core/test/nested-test.test.ts +++ b/test/core/test/nested-test.test.ts @@ -3,28 +3,28 @@ import { describe, expect, test } from 'vitest' test('nested test should throw error', () => { expect(() => { test('test inside test', () => {}) - }).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`) + }).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`) expect(() => { test.each([1, 2, 3])('test.each inside test %d', () => {}) - }).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`) + }).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`) expect(() => { test.skipIf(false)('test.skipIf inside test', () => {}) - }).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`) + }).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`) }) describe('parallel tests', () => { test.concurrent('parallel test 1 with nested test', () => { expect(() => { test('test inside test', () => {}) - }).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`) + }).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`) }) test.concurrent('parallel test 2 without nested test', () => {}) test.concurrent('parallel test 3 without nested test', () => {}) test.concurrent('parallel test 4 with nested test', () => { expect(() => { test('test inside test', () => {}) - }).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`) + }).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`) }) }) diff --git a/test/core/test/snapshot-custom-serializer.test.ts b/test/core/test/snapshot-custom-serializer.test.ts new file mode 100644 index 000000000000..a3e337361bd3 --- /dev/null +++ b/test/core/test/snapshot-custom-serializer.test.ts @@ -0,0 +1,88 @@ +import { expect, test } from 'vitest' + +test('basic', () => { + // example from docs/guide/snapshot.md + + const bar = { + foo: { + x: 1, + y: 2, + }, + } + + // without custom serializer + expect(bar).toMatchInlineSnapshot(` + { + "foo": { + "x": 1, + "y": 2, + }, + } + `) + + // with custom serializer + expect.addSnapshotSerializer({ + serialize(val, config, indentation, depth, refs, printer) { + return `Pretty foo: ${printer( + val.foo, + config, + indentation, + depth, + refs, + )}` + }, + test(val) { + return val && Object.prototype.hasOwnProperty.call(val, 'foo') + }, + }) + + expect(bar).toMatchInlineSnapshot(` + Pretty foo: { + "x": 1, + "y": 2, + } + `) +}) + +test('throwning snapshot', () => { + // example from https://github.com/vitest-dev/vitest/issues/3655 + + class ErrorWithDetails extends Error { + readonly details: unknown + + constructor(message: string, options: ErrorOptions & { details: unknown }) { + super(message, options) + this.details = options.details + } + } + + // without custom serializer + const error = new ErrorWithDetails('some-error', { + details: 'some-detail', + }) + expect(error).toMatchInlineSnapshot(`[Error: some-error]`) + expect(() => { + throw error + }).toThrowErrorMatchingInlineSnapshot(`[Error: some-error]`) + + // with custom serializer + expect.addSnapshotSerializer({ + serialize(val, config, indentation, depth, refs, printer) { + const error = val as ErrorWithDetails + return `Pretty ${error.message}: ${printer( + error.details, + config, + indentation, + depth, + refs, + )}` + }, + test(val) { + return val && val instanceof ErrorWithDetails + }, + }) + expect(error).toMatchInlineSnapshot(`Pretty some-error: "some-detail"`) + expect(() => { + throw error + }).toThrowErrorMatchingInlineSnapshot(`Pretty some-error: "some-detail"`) +}) diff --git a/test/core/test/snapshot-inline.test.ts b/test/core/test/snapshot-inline.test.ts index 4a8bbb22fc2a..657e7a243940 100644 --- a/test/core/test/snapshot-inline.test.ts +++ b/test/core/test/snapshot-inline.test.ts @@ -62,7 +62,7 @@ test('template literal', () => { test('throwing inline snapshots', async () => { expect(() => { throw new Error('omega') - }).toThrowErrorMatchingInlineSnapshot('"omega"') + }).toThrowErrorMatchingInlineSnapshot(`[Error: omega]`) expect(() => { // eslint-disable-next-line no-throw-literal @@ -102,7 +102,7 @@ test('throwing inline snapshots', async () => { await expect(async () => { throw new Error('omega') - }).rejects.toThrowErrorMatchingInlineSnapshot('"omega"') + }).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: omega]`) }) test('throwing expect should be a function', async () => { diff --git a/test/core/test/wait.test.ts b/test/core/test/wait.test.ts index a70d20037f80..3280e0408da7 100644 --- a/test/core/test/wait.test.ts +++ b/test/core/test/wait.test.ts @@ -24,7 +24,7 @@ describe('waitFor', () => { timeout: 60, interval: 30, }), - ).rejects.toThrowErrorMatchingInlineSnapshot('"interval error"') + ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: interval error]`) expect(callback).toHaveBeenCalledTimes(2) }) @@ -125,7 +125,7 @@ describe('waitUntil', () => { timeout: 60, interval: 30, }), - ).rejects.toThrowErrorMatchingInlineSnapshot('"Timed out in waitUntil!"') + ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Timed out in waitUntil!]`) expect(callback).toHaveBeenCalledTimes(2) }) diff --git a/test/snapshots/test/snapshots-async.test.ts b/test/snapshots/test/snapshots-async.test.ts index 9b61651c5e40..d176fcec317c 100644 --- a/test/snapshots/test/snapshots-async.test.ts +++ b/test/snapshots/test/snapshots-async.test.ts @@ -13,5 +13,5 @@ test('resolved inline', async () => { test('rejected inline', async () => { await expect(reject()).rejects.toMatchInlineSnapshot('[Error: foo]') - await expect(reject()).rejects.toThrowErrorMatchingInlineSnapshot('"foo"') + await expect(reject()).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: foo]`) }) diff --git a/test/utils/test/display.spec.ts b/test/utils/test/display.spec.ts index 3e304fca1f94..92b1e6eeabe2 100644 --- a/test/utils/test/display.spec.ts +++ b/test/utils/test/display.spec.ts @@ -61,7 +61,7 @@ describe('format', () => { }) test('cannont serialize some values', () => { - expect(() => format('%j', 100n)).toThrowErrorMatchingInlineSnapshot('"Do not know how to serialize a BigInt"') + expect(() => format('%j', 100n)).toThrowErrorMatchingInlineSnapshot(`[TypeError: Do not know how to serialize a BigInt]`) }) test.each(