mirror of
https://github.com/hanzoai/vitest.git
synced 2026-09-24 17:04:25 +00:00
feat(types)!: add better promise support in expects and matchers (#8266)
Co-authored-by: Vladimir Sheremet <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Vladimir Sheremet
parent
50689ab3d1
commit
0b07848e4c
+17
-14
@@ -9,8 +9,11 @@ type Awaitable<T> = T | PromiseLike<T>
|
||||
`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 built on top of `chai`. Since Vitest 4.1, for spy/mock testing, Vitest also provides Chai-style assertions (e.g., [`expect(spy).to.have.been.called()`](#called)) alongside Jest-style assertions (e.g., `expect(spy).toHaveBeenCalled()`). Unlike `Jest`, Vitest supports a message as the second argument - if the assertion fails, the error message will be equal to it.
|
||||
|
||||
```ts
|
||||
export interface ExpectStatic extends Chai.ExpectStatic, AsymmetricMatchersContaining {
|
||||
<T>(actual: T, message?: string): Assertion<T>
|
||||
export interface ExpectStatic
|
||||
extends Chai.ExpectStatic,
|
||||
Matchers<any>,
|
||||
AsymmetricMatchersContaining {
|
||||
<T>(actual: T, message?: string): Assertion<void, T>
|
||||
extend: (expects: MatchersObject) => void
|
||||
anything: () => any
|
||||
any: (constructor: unknown) => any
|
||||
@@ -2242,12 +2245,11 @@ 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,
|
||||
}
|
||||
toBeFoo(received) {
|
||||
const { isNot } = this
|
||||
return {
|
||||
message: () => `expected ${received} is${isNot ? ' not' : ''} foo`,
|
||||
pass: received === 'foo',
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -2263,19 +2265,20 @@ If you want your matchers to appear in every test, you should call this method i
|
||||
|
||||
This function is compatible with Jest's `expect.extend`, so any library that uses it to create custom matchers will work with Vitest.
|
||||
|
||||
If you are using TypeScript, 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:
|
||||
If you are using TypeScript, you can extend the default `Matchers` interface in an ambient declaration file (e.g: `vitest.d.ts`) with the code below:
|
||||
|
||||
```ts
|
||||
interface CustomMatchers<R = unknown> {
|
||||
toBeFoo: () => R
|
||||
}
|
||||
import 'vitest'
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> extends CustomMatchers<T> {}
|
||||
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
||||
interface Matchers<R, T> {
|
||||
toBeFoo: () => R
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`R` is the assertion return type, and `T` is the type of the received value.
|
||||
|
||||
::: warning
|
||||
Don't forget to include the ambient declaration file in your `tsconfig.json`.
|
||||
:::
|
||||
|
||||
@@ -239,7 +239,7 @@ Vitest now has a `Matchers` type that you can extend to add type support for all
|
||||
|
||||
For example, to have a type-safe `toBeFoo` matcher, you can write something like this:
|
||||
|
||||
```ts twoslash
|
||||
```ts
|
||||
import { expect } from 'vitest'
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
@@ -252,7 +252,6 @@ declare module 'vitest' {
|
||||
|
||||
expect.extend({
|
||||
toBeFoo(actual, arg) {
|
||||
// ^?
|
||||
// ... implementation
|
||||
return {
|
||||
pass: true,
|
||||
|
||||
@@ -12,7 +12,7 @@ To extend default matchers, call `expect.extend` with an object containing your
|
||||
|
||||
```ts
|
||||
expect.extend({
|
||||
toBeFoo(received, expected) {
|
||||
toBeFoo(received) {
|
||||
const { isNot } = this
|
||||
return {
|
||||
// do not alter your "pass" based on isNot. Vitest does it for you
|
||||
@@ -29,12 +29,24 @@ If you are using TypeScript, you can extend default `Matchers` interface in an a
|
||||
import 'vitest'
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Matchers<T = any> {
|
||||
interface Matchers<R, T> {
|
||||
toBeFoo: () => R
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`R` is the assertion return type, and `T` is the type of the received value.
|
||||
|
||||
Return `R` from matchers that run synchronously. This makes the return type `void` for a regular assertion and `Promise<void>` when the assertion is used with `.resolves`, `.rejects`, [`expect.poll`](/api/expect#poll), or [`expect.element`](/api/browser/assertions). You can use `T` when an expected argument should have the same type as the received value:
|
||||
|
||||
```ts
|
||||
declare module 'vitest' {
|
||||
interface Matchers<R, T> {
|
||||
toEqualTyped: (expected: T) => R
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
Importing `vitest` makes TypeScript think this is an ES module file, type declaration won't work without it.
|
||||
:::
|
||||
@@ -45,30 +57,42 @@ Extending the `Matchers` interface will add a type to `expect.extend`, `expect()
|
||||
Don't forget to include the ambient declaration file in your `tsconfig.json`.
|
||||
:::
|
||||
|
||||
The return value of a matcher should be compatible with the following interface:
|
||||
The return value of a matcher should be compatible with the following types:
|
||||
|
||||
```ts
|
||||
interface MatcherResult {
|
||||
interface SyncMatcherResult {
|
||||
pass: boolean
|
||||
message: () => string
|
||||
// If you pass these, they will automatically appear inside a diff when
|
||||
// the matcher does not pass, so you don't need to print the diff yourself
|
||||
actual?: unknown
|
||||
expected?: unknown
|
||||
meta?: object
|
||||
}
|
||||
|
||||
type MatcherResult = SyncMatcherResult | Promise<SyncMatcherResult>
|
||||
```
|
||||
|
||||
::: warning
|
||||
If you create an asynchronous matcher, don't forget to `await` the result (`await expect('foo').toBeFoo()`) in the test itself:
|
||||
If a matcher implementation is asynchronous, declare its return type as `Promise<void>` instead of `R` and don't forget to `await` it in the test:
|
||||
|
||||
```ts
|
||||
expect.extend({
|
||||
async toBeAsyncAssertion() {
|
||||
// ...
|
||||
async toBeAsyncAssertion(received) {
|
||||
return {
|
||||
pass: received === 'foo',
|
||||
message: () => `expected ${received} to be foo`,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await expect().toBeAsyncAssertion()
|
||||
declare module 'vitest' {
|
||||
interface Matchers<R, T> {
|
||||
toBeAsyncAssertion: () => Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
await expect('foo').toBeAsyncAssertion()
|
||||
```
|
||||
:::
|
||||
|
||||
@@ -155,7 +179,7 @@ The name of the current [`environment`](/config/environment) (for example, `jsdo
|
||||
|
||||
Was assertion called as a [`soft`](/api/expect#soft) one. You don't need to respect it, Vitest will always catch the error.
|
||||
|
||||
## `assertion` <Advanced /> <Version type="experimental">4.1.4</Version> {#assertion}
|
||||
## `assertion` <Advanced /> <Version>5.0.0</Version> {#assertion}
|
||||
|
||||
The underlying [Chai assertion](https://www.chaijs.com/guide/plugins/) object. This is the same instance that Chai plugins receive, giving you access to Chai's flag system and chainable methods. This can be useful for building custom matchers that need to interact with Chai's internals.
|
||||
|
||||
|
||||
@@ -225,6 +225,43 @@ To assert that a thrown error has an empty message, match the pattern explicitly
|
||||
expect(() => { throw new Error('boom') }).not.toThrow(/^$/)
|
||||
```
|
||||
|
||||
### Assertion Types Expose Return and Received Types
|
||||
|
||||
Assertion interfaces now use two type parameters: `R` is the matcher return type and `T` is the received value type. Synchronous assertions use `void`, while assertions accessed through `.resolves`, `.rejects`, [`expect.poll`](/api/expect#poll), or [`expect.element`](/api/browser/assertions) use `Promise<void>`.
|
||||
|
||||
If you declare custom matchers, augment the `Matchers<R, T>` interface. It adds the matcher to instance assertions, asymmetric matchers, and the type accepted by `expect.extend`:
|
||||
|
||||
```ts [vitest.d.ts]
|
||||
import 'vitest'
|
||||
|
||||
interface CustomMatchers<R = unknown, T = unknown> {
|
||||
toBeFoo: () => R
|
||||
toEqualTyped: (expected: T) => R
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Matchers<R, T> extends CustomMatchers<R, T> {}
|
||||
}
|
||||
```
|
||||
|
||||
This makes custom matcher return types reflect how the matcher is used:
|
||||
|
||||
```ts
|
||||
const syncResult = expect('value').toEqualTyped('other') // void
|
||||
const asyncResult = expect(Promise.resolve('value')).resolves.toEqualTyped('other') // Promise<void>
|
||||
await asyncResult
|
||||
```
|
||||
|
||||
Code that refers to assertion types directly must also provide the return type first:
|
||||
|
||||
```ts
|
||||
Assertion<string> // [!code --]
|
||||
Assertion<void, string> // [!code ++]
|
||||
Assertion<Promise<void>, string> // asynchronous assertion
|
||||
```
|
||||
|
||||
Vitest no longer reads custom matcher declarations from the global `jest.Matchers` interface. Libraries that support both Jest and Vitest should augment `jest.Matchers` and `vitest.Matchers` separately. This only affects TypeScript declarations; registering matchers with `expect.extend` works as before.
|
||||
|
||||
### `expect.poll` Fails When It Times Out
|
||||
|
||||
[`expect.poll`](/api/expect#poll) now rejects when its callback, or the polled assertion, does not settle within `timeout`. Previously a callback that resolved after the deadline, or an assertion that only passed on a late attempt, could still succeed. The callback now also receives an `AbortSignal` that aborts when the timeout elapses, so you can cancel in-flight work:
|
||||
|
||||
+25
-11
@@ -246,8 +246,8 @@ import { expect, test, Snapshots } from 'vitest'
|
||||
const { toMatchFileSnapshot, toMatchInlineSnapshot, toMatchSnapshot } = Snapshots
|
||||
|
||||
expect.extend({
|
||||
toMatchTrimmedSnapshot(received: string) {
|
||||
return toMatchSnapshot.call(this, received.slice(0, 10))
|
||||
toMatchTrimmedSnapshot(received: string, length: number) {
|
||||
return toMatchSnapshot.call(this, received.slice(0, length))
|
||||
},
|
||||
toMatchTrimmedInlineSnapshot(received: string, inlineSnapshot?: string) {
|
||||
return toMatchInlineSnapshot.call(this, received.slice(0, 10), inlineSnapshot)
|
||||
@@ -298,7 +298,7 @@ File snapshot matchers must be `async` — `toMatchFileSnapshot` returns a `Prom
|
||||
:::
|
||||
|
||||
::: warning
|
||||
When custom inline snapshot matcher is aynchronous, Vitest cannot automatically infer the call location for inline snapshot rewriting. You must capture the call site by setting the `'error'` flag on the chai assertion object:
|
||||
When custom inline snapshot matcher is asynchronous, Vitest cannot automatically infer the call location for inline snapshot rewriting. You must capture the call site by setting the `'error'` flag on the chai assertion object:
|
||||
|
||||
```ts
|
||||
import { expect, chai, Snapshots } from 'vitest'
|
||||
@@ -317,16 +317,16 @@ expect.extend({
|
||||
|
||||
:::
|
||||
|
||||
For TypeScript, extend the `Assertion` interface:
|
||||
For TypeScript, augment the `Matchers<R, T>` interface:
|
||||
|
||||
```ts
|
||||
import 'vitest'
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> {
|
||||
toMatchTrimmedSnapshot: (length: number) => T
|
||||
toMatchTrimmedInlineSnapshot: (inlineSnapshot?: string) => T
|
||||
toMatchTrimmedFileSnapshot: (file: string) => Promise<T>
|
||||
interface Matchers<R, T> {
|
||||
toMatchTrimmedSnapshot: (length: number) => R
|
||||
toMatchTrimmedInlineSnapshot: (inlineSnapshot?: string) => R
|
||||
toMatchTrimmedFileSnapshot: (file: string) => Promise<void>
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -391,14 +391,21 @@ This asymmetry is what makes `--update` work correctly: `match` returns a `resol
|
||||
Register a custom matcher with `expect.extend(...)` and call the snapshot composables from `vitest`:
|
||||
|
||||
```ts [setup.ts]
|
||||
import { expect, Snaphsots } from 'vitest'
|
||||
import { expect, Snapshots } from 'vitest'
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Matchers<R, T> {
|
||||
toMatchMyDomainSnapshot: () => R
|
||||
toMatchMyDomainInlineSnapshot: (inlineSnapshot?: string) => R
|
||||
}
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
toMatchMyDomainSnapshot(received: unknown) {
|
||||
return Snaphsots.toMatchDomainSnapshot.call(this, myAdapter, received)
|
||||
return Snapshots.toMatchDomainSnapshot.call(this, myAdapter, received)
|
||||
},
|
||||
toMatchMyDomainInlineSnapshot(received: unknown, inlineSnapshot?: string) {
|
||||
return Snaphsots.toMatchDomainInlineSnapshot.call(
|
||||
return Snapshots.toMatchDomainInlineSnapshot.call(
|
||||
this,
|
||||
myAdapter,
|
||||
received,
|
||||
@@ -494,6 +501,13 @@ export const kvAdapter: DomainSnapshotAdapter<KVCaptured, KVExpected> = {
|
||||
import { expect, Snapshots } from 'vitest'
|
||||
import { kvAdapter } from './kv-adapter'
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Matchers<R, T> {
|
||||
toMatchKvSnapshot: () => R
|
||||
toMatchKvInlineSnapshot: (inlineSnapshot?: string) => R
|
||||
}
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
toMatchKvSnapshot(received: unknown) {
|
||||
return Snapshots.toMatchDomainSnapshot.call(this, kvAdapter, received)
|
||||
|
||||
Vendored
+4
-4
@@ -3,7 +3,7 @@
|
||||
import { ARIARole } from './aria-role.js'
|
||||
import { Locator, ScreenshotComparatorRegistry, ScreenshotMatcherOptions } from './context.js'
|
||||
|
||||
export interface TestingLibraryMatchers<E, R> {
|
||||
export interface TestingLibraryMatchers<R extends void | Promise<void>, T = unknown> {
|
||||
/**
|
||||
* @description
|
||||
* Assert whether an element is present in the document or not.
|
||||
@@ -488,7 +488,7 @@ export interface TestingLibraryMatchers<E, R> {
|
||||
* await expect.element(page.getByTestId('logo')).toHaveAccessibleDescription('The logo of Our Company')
|
||||
* @see https://vitest.dev/api/browser/assertions#tohaveaccessibledescription
|
||||
*/
|
||||
toHaveAccessibleDescription(text?: string | RegExp | E): R
|
||||
toHaveAccessibleDescription(text?: string | RegExp | T): R
|
||||
|
||||
/**
|
||||
* @description
|
||||
@@ -527,7 +527,7 @@ export interface TestingLibraryMatchers<E, R> {
|
||||
*
|
||||
* @see https://vitest.dev/api/browser/assertions#tohaveaccessibleerrormessage
|
||||
*/
|
||||
toHaveAccessibleErrorMessage(text?: string | RegExp | E): R
|
||||
toHaveAccessibleErrorMessage(text?: string | RegExp | T): R
|
||||
|
||||
/**
|
||||
* @description
|
||||
@@ -558,7 +558,7 @@ export interface TestingLibraryMatchers<E, R> {
|
||||
* await expect.element(page.getByTestId('input-title')).toHaveAccessibleName()
|
||||
* @see https://vitest.dev/api/browser/assertions#tohaveaccessiblename
|
||||
*/
|
||||
toHaveAccessibleName(text?: string | RegExp | E): R
|
||||
toHaveAccessibleName(text?: string | RegExp | T): R
|
||||
/**
|
||||
* @description
|
||||
* This allows you to assert that an element has the expected
|
||||
|
||||
Vendored
+5
-13
@@ -3,18 +3,7 @@ import type { TestingLibraryMatchers } from './jest-dom.js'
|
||||
import type { Assertion, ExpectPollOptions } from 'vitest'
|
||||
|
||||
declare module 'vitest' {
|
||||
interface JestAssertion<T = any> extends TestingLibraryMatchers<void, T> {}
|
||||
interface AsymmetricMatchersContaining extends TestingLibraryMatchers<void, void> {}
|
||||
|
||||
type Promisify<O> = {
|
||||
[K in keyof O]: O[K] extends (...args: infer A) => infer R
|
||||
? O extends R
|
||||
? Promisify<O[K]>
|
||||
: (...args: A) => Promise<R>
|
||||
: O[K];
|
||||
}
|
||||
|
||||
type PromisifyDomAssertion<T> = Promisify<Assertion<T>>
|
||||
interface Assertion<R, T> extends TestingLibraryMatchers<R, T> {}
|
||||
|
||||
interface ExpectStatic {
|
||||
/**
|
||||
@@ -23,7 +12,10 @@ declare module 'vitest' {
|
||||
* You can set default timeout via `expect.poll.timeout` option in the config.
|
||||
* @see {@link https://vitest.dev/api/expect#poll}
|
||||
*/
|
||||
element: <T extends HTMLElement | SVGElement | null | Locator>(element: T, options?: ExpectPollOptions) => PromisifyDomAssertion<Awaited<HTMLElement | SVGElement | null>>
|
||||
element: <T extends HTMLElement | SVGElement | null | Locator>(element: T, options?: ExpectPollOptions) => Assertion<
|
||||
Promise<void>,
|
||||
HTMLElement | SVGElement | null
|
||||
>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Assertion, ExpectPollOptions, PromisifyDomAssertion } from 'vitest'
|
||||
import type { Assertion, ExpectPollOptions } from 'vitest'
|
||||
import type { Locator } from 'vitest/browser'
|
||||
import type { BrowserTraceEntryStatus } from './trace'
|
||||
import { chai, expect } from 'vitest'
|
||||
@@ -11,7 +11,7 @@ import { createBrowserTraceRangeId, recordBrowserTraceEntry } from './trace'
|
||||
|
||||
const kLocator = Symbol.for('$$vitest:locator')
|
||||
|
||||
function element<T extends HTMLElement | SVGElement | null | Locator>(elementOrLocator: T, options?: ExpectPollOptions): PromisifyDomAssertion<HTMLElement | SVGElement | null> {
|
||||
function element<T extends HTMLElement | SVGElement | null | Locator>(elementOrLocator: T, options?: ExpectPollOptions): Assertion<Promise<void>, HTMLElement | SVGElement | null> {
|
||||
if (elementOrLocator != null && !(elementOrLocator instanceof HTMLElement) && !(elementOrLocator instanceof SVGElement) && !(kLocator in elementOrLocator)) {
|
||||
throw new Error(`Invalid element or locator: ${elementOrLocator}. Expected an instance of HTMLElement, SVGElement or Locator, received ${getType(elementOrLocator)}`)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Test } from '../../vitest/src/runtime/runner/types'
|
||||
import type {
|
||||
Assertion,
|
||||
ChaiPlugin,
|
||||
ExpectStatic,
|
||||
MatchersObject,
|
||||
@@ -20,7 +21,7 @@ import { getState } from './state'
|
||||
import { wrapAssertion } from './utils'
|
||||
|
||||
function getMatcherState(
|
||||
assertion: Chai.AssertionStatic & Chai.Assertion,
|
||||
assertion: Chai.AssertionStatic & Assertion,
|
||||
expect: ExpectStatic,
|
||||
) {
|
||||
const obj = assertion._obj
|
||||
@@ -91,7 +92,7 @@ function JestExtendPlugin(
|
||||
Object.entries(matchers).forEach(
|
||||
([expectAssertionName, expectAssertion]) => {
|
||||
function __VITEST_EXTEND_ASSERTION__(
|
||||
this: Chai.AssertionStatic & Chai.Assertion,
|
||||
this: Chai.AssertionStatic & Assertion,
|
||||
...args: any[]
|
||||
) {
|
||||
const { state, isNot, obj, customMessage } = getMatcherState(this, expect)
|
||||
|
||||
+95
-101
@@ -106,23 +106,23 @@ export interface RawMatcherFn<T extends MatcherState = MatcherState, E extends A
|
||||
(this: T, received: any, ...expected: E): ExpectationResult
|
||||
}
|
||||
|
||||
// Allow unused `T` to preserve its name for extensions.
|
||||
// Allow unused type parameters to preserve their names for extensions.
|
||||
// Type parameter names must be identical when extending those types.
|
||||
// eslint-disable-next-line
|
||||
export interface Matchers<T = any> {}
|
||||
export interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> {}
|
||||
|
||||
export type MatchersObject<T extends MatcherState = MatcherState> = Record<
|
||||
string,
|
||||
RawMatcherFn<T>
|
||||
> & ThisType<T> & {
|
||||
[K in keyof Matchers<T>]?: RawMatcherFn<T, Parameters<Matchers<T>[K]>>
|
||||
[K in keyof Matchers]?: RawMatcherFn<T, Parameters<Matchers[K]>>
|
||||
}
|
||||
|
||||
export interface ExpectStatic
|
||||
extends Chai.ExpectStatic,
|
||||
Matchers,
|
||||
Matchers<any>,
|
||||
AsymmetricMatchersContaining {
|
||||
<T>(actual: T, message?: string): Assertion<T>
|
||||
<T>(actual: T, message?: string): Assertion<void, T>
|
||||
extend: (expects: MatchersObject) => void
|
||||
anything: () => any
|
||||
any: (constructor: unknown) => any
|
||||
@@ -131,7 +131,7 @@ export interface ExpectStatic
|
||||
not: AsymmetricMatchersContaining
|
||||
}
|
||||
|
||||
interface CustomMatcher {
|
||||
interface CustomMatcher<R = any> {
|
||||
/**
|
||||
* Checks that a value satisfies a custom matcher function.
|
||||
*
|
||||
@@ -142,7 +142,7 @@ interface CustomMatcher {
|
||||
* expect(age).toSatisfy(val => val >= 18, 'Age must be at least 18');
|
||||
* expect(age).toEqual(expect.toSatisfy(val => val >= 18, 'Age must be at least 18'));
|
||||
*/
|
||||
toSatisfy: (matcher: (value: any) => boolean, message?: string) => any
|
||||
toSatisfy: (matcher: (value: any) => boolean, message?: string) => R
|
||||
|
||||
/**
|
||||
* Matches if the received value is one of the values in the expected array or set.
|
||||
@@ -152,10 +152,10 @@ interface CustomMatcher {
|
||||
* expect('foo').toBeOneOf([expect.any(String)])
|
||||
* expect({ a: 1 }).toEqual({ a: expect.toBeOneOf(['1', '2', '3']) })
|
||||
*/
|
||||
toBeOneOf: <T>(sample: ReadonlyArray<T> | ReadonlySet<T>) => any
|
||||
toBeOneOf: <T>(sample: ReadonlyArray<T> | ReadonlySet<T>) => R
|
||||
}
|
||||
|
||||
export interface AsymmetricMatchersContaining extends CustomMatcher {
|
||||
export interface AsymmetricMatchersContaining extends Matchers<any>, CustomMatcher {
|
||||
/**
|
||||
* Matches if the received string contains the expected substring.
|
||||
*
|
||||
@@ -219,7 +219,8 @@ export type DeeplyAllowMatchers<T> = T extends Array<infer Element>
|
||||
? WithAsymmetricMatcher<T> | { [K in keyof T]: DeeplyAllowMatchers<T[K]> }
|
||||
: WithAsymmetricMatcher<T>
|
||||
|
||||
export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMatcher {
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
export interface JestAssertion<R extends void | Promise<void>, T = unknown> extends CustomMatcher<R> {
|
||||
/**
|
||||
* Used when you want to check that two objects have the same value.
|
||||
* This matcher recursively checks the equality of all fields, rather than checking for object identity.
|
||||
@@ -227,7 +228,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(user).toEqual({ name: 'Alice', age: 30 });
|
||||
*/
|
||||
toEqual: <E>(expected: E) => void
|
||||
toEqual: <E>(expected: E) => R
|
||||
|
||||
/**
|
||||
* Use to test that objects have the same types as well as structure.
|
||||
@@ -235,7 +236,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(user).toStrictEqual({ name: 'Alice', age: 30 });
|
||||
*/
|
||||
toStrictEqual: <E>(expected: E) => void
|
||||
toStrictEqual: <E>(expected: E) => R
|
||||
|
||||
/**
|
||||
* Checks that a value is what you expect. It calls `Object.is` to compare values.
|
||||
@@ -245,7 +246,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(result).toBe(42);
|
||||
* expect(status).toBe(true);
|
||||
*/
|
||||
toBe: <E>(expected: E) => void
|
||||
toBe: <E>(expected: E) => R
|
||||
|
||||
/**
|
||||
* Check that a string matches a regular expression.
|
||||
@@ -254,7 +255,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(message).toMatch(/hello/);
|
||||
* expect(greeting).toMatch('world');
|
||||
*/
|
||||
toMatch: (expected: string | RegExp) => void
|
||||
toMatch: (expected: string | RegExp) => R
|
||||
|
||||
/**
|
||||
* Used to check that a JavaScript object matches a subset of the properties of an object
|
||||
@@ -265,7 +266,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* address: { city: 'Wonderland' }
|
||||
* });
|
||||
*/
|
||||
toMatchObject: <E extends object | any[]>(expected: E) => void
|
||||
toMatchObject: <E extends object | any[]>(expected: E) => R
|
||||
|
||||
/**
|
||||
* Used when you want to check that an item is in a list.
|
||||
@@ -275,7 +276,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(items).toContain('apple');
|
||||
* expect(numbers).toContain(5);
|
||||
*/
|
||||
toContain: <E>(item: E) => void
|
||||
toContain: <E>(item: E) => R
|
||||
|
||||
/**
|
||||
* Used when you want to check that an item is in a list.
|
||||
@@ -285,7 +286,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(items).toContainEqual({ name: 'apple', quantity: 1 });
|
||||
*/
|
||||
toContainEqual: <E>(item: E) => void
|
||||
toContainEqual: <E>(item: E) => R
|
||||
|
||||
/**
|
||||
* Use when you don't care what a value is, you just want to ensure a value
|
||||
@@ -295,7 +296,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(user.isActive).toBeTruthy();
|
||||
*/
|
||||
toBeTruthy: () => void
|
||||
toBeTruthy: () => R
|
||||
|
||||
/**
|
||||
* When you don't care what a value is, you just want to
|
||||
@@ -304,7 +305,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(user.isActive).toBeFalsy();
|
||||
*/
|
||||
toBeFalsy: () => void
|
||||
toBeFalsy: () => R
|
||||
|
||||
/**
|
||||
* For comparing floating point numbers.
|
||||
@@ -312,7 +313,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(score).toBeGreaterThan(10);
|
||||
*/
|
||||
toBeGreaterThan: (num: number | bigint) => void
|
||||
toBeGreaterThan: (num: number | bigint) => R
|
||||
|
||||
/**
|
||||
* For comparing floating point numbers.
|
||||
@@ -320,7 +321,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(score).toBeGreaterThanOrEqual(10);
|
||||
*/
|
||||
toBeGreaterThanOrEqual: (num: number | bigint) => void
|
||||
toBeGreaterThanOrEqual: (num: number | bigint) => R
|
||||
|
||||
/**
|
||||
* For comparing floating point numbers.
|
||||
@@ -328,7 +329,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(score).toBeLessThan(10);
|
||||
*/
|
||||
toBeLessThan: (num: number | bigint) => void
|
||||
toBeLessThan: (num: number | bigint) => R
|
||||
|
||||
/**
|
||||
* For comparing floating point numbers.
|
||||
@@ -336,7 +337,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(score).toBeLessThanOrEqual(10);
|
||||
*/
|
||||
toBeLessThanOrEqual: (num: number | bigint) => void
|
||||
toBeLessThanOrEqual: (num: number | bigint) => R
|
||||
|
||||
/**
|
||||
* Used to check that a variable is NaN.
|
||||
@@ -344,7 +345,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(value).toBeNaN();
|
||||
*/
|
||||
toBeNaN: () => void
|
||||
toBeNaN: () => R
|
||||
|
||||
/**
|
||||
* Used to check that a variable is undefined.
|
||||
@@ -352,7 +353,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(value).toBeUndefined();
|
||||
*/
|
||||
toBeUndefined: () => void
|
||||
toBeUndefined: () => R
|
||||
|
||||
/**
|
||||
* This is the same as `.toBe(null)` but the error messages are a bit nicer.
|
||||
@@ -361,7 +362,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(value).toBeNull();
|
||||
*/
|
||||
toBeNull: () => void
|
||||
toBeNull: () => R
|
||||
|
||||
/**
|
||||
* Used to check that a variable is nullable (null or undefined).
|
||||
@@ -369,7 +370,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(value).toBeNullable();
|
||||
*/
|
||||
toBeNullable: () => void
|
||||
toBeNullable: () => R
|
||||
|
||||
/**
|
||||
* Ensure that a variable is not undefined.
|
||||
@@ -377,7 +378,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(value).toBeDefined();
|
||||
*/
|
||||
toBeDefined: () => void
|
||||
toBeDefined: () => R
|
||||
|
||||
/**
|
||||
* Ensure that an object is an instance of a class.
|
||||
@@ -386,7 +387,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(new Date()).toBeInstanceOf(Date);
|
||||
*/
|
||||
toBeInstanceOf: <E>(expected: E) => void
|
||||
toBeInstanceOf: <E>(expected: E) => R
|
||||
|
||||
/**
|
||||
* Used to check that an object has a `.length` property
|
||||
@@ -396,7 +397,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect([1, 2, 3]).toHaveLength(3);
|
||||
* expect('hello').toHaveLength(5);
|
||||
*/
|
||||
toHaveLength: (length: number) => void
|
||||
toHaveLength: (length: number) => R
|
||||
|
||||
/**
|
||||
* Use to check if a property at the specified path exists on an object.
|
||||
@@ -414,7 +415,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
toHaveProperty: <E>(
|
||||
property: string | (string | number)[],
|
||||
value?: E,
|
||||
) => void
|
||||
) => R
|
||||
|
||||
/**
|
||||
* Using exact equality with floating point numbers is a bad idea.
|
||||
@@ -424,7 +425,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(price).toBeCloseTo(9.99, 2);
|
||||
*/
|
||||
toBeCloseTo: (number: number, numDigits?: number) => void
|
||||
toBeCloseTo: (number: number, numDigits?: number) => R
|
||||
|
||||
/**
|
||||
* Ensures that a mock function is called an exact number of times.
|
||||
@@ -434,7 +435,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveBeenCalledTimes(2);
|
||||
*/
|
||||
toHaveBeenCalledTimes: (times: number) => void
|
||||
toHaveBeenCalledTimes: (times: number) => R
|
||||
|
||||
/**
|
||||
* Ensures that a mock function is called an exact number of times.
|
||||
@@ -445,7 +446,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(mockFunc).toBeCalledTimes(2);
|
||||
* @deprecated Use `toHaveBeenCalledTimes` instead
|
||||
*/
|
||||
toBeCalledTimes: (times: number) => void
|
||||
toBeCalledTimes: (times: number) => R
|
||||
|
||||
/**
|
||||
* Ensures that a mock function is called.
|
||||
@@ -456,7 +457,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(mockFunc).toHaveBeenCalled();
|
||||
*/
|
||||
|
||||
toHaveBeenCalled: () => void
|
||||
toHaveBeenCalled: () => R
|
||||
|
||||
/**
|
||||
* Ensures that a mock function is called.
|
||||
@@ -467,7 +468,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(mockFunc).toBeCalled();
|
||||
* @deprecated Use `toHaveBeenCalled` instead
|
||||
*/
|
||||
toBeCalled: () => void
|
||||
toBeCalled: () => R
|
||||
|
||||
/**
|
||||
* Ensure that a mock function is called with specific arguments.
|
||||
@@ -477,7 +478,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveBeenCalledWith('arg1', 42);
|
||||
*/
|
||||
toHaveBeenCalledWith: <E extends any[]>(...args: E) => void
|
||||
toHaveBeenCalledWith: <E extends any[]>(...args: E) => R
|
||||
|
||||
/**
|
||||
* Ensure that a mock function is called with specific arguments.
|
||||
@@ -488,7 +489,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(mockFunc).toBeCalledWith('arg1', 42);
|
||||
* @deprecated Use `toHaveBeenCalledWith` instead
|
||||
*/
|
||||
toBeCalledWith: <E extends any[]>(...args: E) => void
|
||||
toBeCalledWith: <E extends any[]>(...args: E) => R
|
||||
|
||||
/**
|
||||
* Ensure that a mock function is called with specific arguments on an Nth call.
|
||||
@@ -498,7 +499,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveBeenNthCalledWith(2, 'secondArg');
|
||||
*/
|
||||
toHaveBeenNthCalledWith: <E extends any[]>(n: number, ...args: E) => void
|
||||
toHaveBeenNthCalledWith: <E extends any[]>(n: number, ...args: E) => R
|
||||
|
||||
/**
|
||||
* If you have a mock function, you can use `.toHaveBeenLastCalledWith`
|
||||
@@ -509,7 +510,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveBeenLastCalledWith('lastArg');
|
||||
*/
|
||||
toHaveBeenLastCalledWith: <E extends any[]>(...args: E) => void
|
||||
toHaveBeenLastCalledWith: <E extends any[]>(...args: E) => R
|
||||
|
||||
/**
|
||||
* Used to test that a function throws when it is called.
|
||||
@@ -521,7 +522,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(() => parseJSON('invalid')).toThrow(SyntaxError);
|
||||
* expect(() => { throw 42 }).toThrow(42);
|
||||
*/
|
||||
toThrow: (expected?: any) => void
|
||||
toThrow: (expected?: any) => R
|
||||
|
||||
/**
|
||||
* Used to test that a function throws when it is called.
|
||||
@@ -534,7 +535,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(() => { throw 42 }).toThrowError(42);
|
||||
* @deprecated Use `toThrow` instead
|
||||
*/
|
||||
toThrowError: (expected?: any) => void
|
||||
toThrowError: (expected?: any) => R
|
||||
|
||||
/**
|
||||
* Use to test that the mock function successfully returned (i.e., did not throw an error) at least one time
|
||||
@@ -545,7 +546,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(mockFunc).toReturn();
|
||||
* @deprecated Use `toHaveReturned` instead
|
||||
*/
|
||||
toReturn: () => void
|
||||
toReturn: () => R
|
||||
|
||||
/**
|
||||
* Use to test that the mock function successfully returned (i.e., did not throw an error) at least one time
|
||||
@@ -555,7 +556,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveReturned();
|
||||
*/
|
||||
toHaveReturned: () => void
|
||||
toHaveReturned: () => R
|
||||
|
||||
/**
|
||||
* Use to ensure that a mock function returned successfully (i.e., did not throw an error) an exact number of times.
|
||||
@@ -567,7 +568,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(mockFunc).toReturnTimes(3);
|
||||
* @deprecated Use `toHaveReturnedTimes` instead
|
||||
*/
|
||||
toReturnTimes: (times: number) => void
|
||||
toReturnTimes: (times: number) => R
|
||||
|
||||
/**
|
||||
* Use to ensure that a mock function returned successfully (i.e., did not throw an error) an exact number of times.
|
||||
@@ -578,7 +579,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveReturnedTimes(3);
|
||||
*/
|
||||
toHaveReturnedTimes: (times: number) => void
|
||||
toHaveReturnedTimes: (times: number) => R
|
||||
|
||||
/**
|
||||
* Use to ensure that a mock function returned a specific value.
|
||||
@@ -589,7 +590,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* expect(mockFunc).toReturnWith('returnValue');
|
||||
* @deprecated Use `toHaveReturnedWith` instead
|
||||
*/
|
||||
toReturnWith: <E>(value: E) => void
|
||||
toReturnWith: <E>(value: E) => R
|
||||
|
||||
/**
|
||||
* Use to ensure that a mock function returned a specific value.
|
||||
@@ -599,7 +600,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveReturnedWith('returnValue');
|
||||
*/
|
||||
toHaveReturnedWith: <E>(value: E) => void
|
||||
toHaveReturnedWith: <E>(value: E) => R
|
||||
|
||||
/**
|
||||
* Use to test the specific value that a mock function last returned.
|
||||
@@ -611,7 +612,7 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveLastReturnedWith('lastValue');
|
||||
*/
|
||||
toHaveLastReturnedWith: <E>(value: E) => void
|
||||
toHaveLastReturnedWith: <E>(value: E) => R
|
||||
|
||||
/**
|
||||
* Use to test the specific value that a mock function returned for the nth call.
|
||||
@@ -623,30 +624,32 @@ export interface JestAssertion<T = any> extends jest.Matchers<void, T>, CustomMa
|
||||
* @example
|
||||
* expect(mockFunc).toHaveNthReturnedWith(2, 'nthValue');
|
||||
*/
|
||||
toHaveNthReturnedWith: <E>(nthCall: number, value: E) => void
|
||||
toHaveNthReturnedWith: <E>(nthCall: number, value: E) => R
|
||||
}
|
||||
|
||||
type VitestAssertion<A, T> = {
|
||||
type VitestAssertion<A, R extends void | Promise<void>, T = unknown> = {
|
||||
[K in keyof A]: A[K] extends Chai.Assertion
|
||||
? Assertion<T>
|
||||
? Assertion<R, T>
|
||||
: A[K] extends (...args: any[]) => any
|
||||
? A[K] // not converting function since they may contain overload
|
||||
: VitestAssertion<A[K], T>;
|
||||
} & ((type: string, message?: string) => Assertion)
|
||||
? R extends Promise<void> ? PromisifyFunction<A[K]> : A[K]
|
||||
: VitestAssertion<A[K], R, T>;
|
||||
} & ((type: string, message?: string) => Assertion<R, T>)
|
||||
|
||||
type Promisify<O> = {
|
||||
[K in keyof O]: O[K] extends (...args: infer A) => infer R
|
||||
? Promisify<O[K]> & ((...args: A) => Promise<R>)
|
||||
: O[K];
|
||||
[K in keyof O]: PromisifyFunction<O[K]>
|
||||
}
|
||||
|
||||
export type PromisifyAssertion<T> = Promisify<Assertion<T>>
|
||||
type PromisifyFunction<T> = T extends (...args: infer A) => infer R
|
||||
? Promisify<T> & ((...args: A) => R extends Promise<any> ? R : Promise<R>)
|
||||
: T
|
||||
|
||||
export interface Assertion<T = any>
|
||||
extends VitestAssertion<Chai.Assertion, T>,
|
||||
JestAssertion<T>,
|
||||
ChaiMockAssertion,
|
||||
Matchers<T> {
|
||||
export type PromisifyAssertion<T> = Assertion<Promise<void>, Awaited<T>>
|
||||
|
||||
export interface Assertion<R extends void | Promise<void> = void, T = unknown>
|
||||
extends VitestAssertion<Chai.Assertion, R, T>,
|
||||
JestAssertion<R, T>,
|
||||
ChaiMockAssertion<R, T>,
|
||||
Matchers<R, T> {
|
||||
/**
|
||||
* Ensures a value is of a specific type.
|
||||
*
|
||||
@@ -664,7 +667,7 @@ export interface Assertion<T = any>
|
||||
| 'string'
|
||||
| 'symbol'
|
||||
| 'undefined',
|
||||
) => void
|
||||
) => R
|
||||
|
||||
/**
|
||||
* Asserts that a mock function was called exactly once.
|
||||
@@ -672,7 +675,7 @@ export interface Assertion<T = any>
|
||||
* @example
|
||||
* expect(mockFunc).toHaveBeenCalledOnce();
|
||||
*/
|
||||
toHaveBeenCalledOnce: () => void
|
||||
toHaveBeenCalledOnce: () => R
|
||||
|
||||
/**
|
||||
* Ensure that a mock function is called with specific arguments and called
|
||||
@@ -681,7 +684,7 @@ export interface Assertion<T = any>
|
||||
* @example
|
||||
* expect(mockFunc).toHaveBeenCalledExactlyOnceWith('arg1', 42);
|
||||
*/
|
||||
toHaveBeenCalledExactlyOnceWith: <E extends any[]>(...args: E) => void
|
||||
toHaveBeenCalledExactlyOnceWith: <E extends any[]>(...args: E) => R
|
||||
|
||||
/**
|
||||
* This assertion checks if a `Mock` was called before another `Mock`.
|
||||
@@ -697,7 +700,7 @@ export interface Assertion<T = any>
|
||||
*
|
||||
* expect(mock1).toHaveBeenCalledBefore(mock2)
|
||||
*/
|
||||
toHaveBeenCalledBefore: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => void
|
||||
toHaveBeenCalledBefore: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => R
|
||||
|
||||
/**
|
||||
* This assertion checks if a `Mock` was called after another `Mock`.
|
||||
@@ -713,7 +716,7 @@ export interface Assertion<T = any>
|
||||
*
|
||||
* expect(mock1).toHaveBeenCalledAfter(mock2)
|
||||
*/
|
||||
toHaveBeenCalledAfter: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => void
|
||||
toHaveBeenCalledAfter: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => R
|
||||
|
||||
/**
|
||||
* Checks that a promise resolves successfully at least once.
|
||||
@@ -721,7 +724,7 @@ export interface Assertion<T = any>
|
||||
* @example
|
||||
* await expect(promise).toHaveResolved();
|
||||
*/
|
||||
toHaveResolved: () => void
|
||||
toHaveResolved: () => R
|
||||
|
||||
/**
|
||||
* Checks that a promise resolves to a specific value.
|
||||
@@ -729,7 +732,7 @@ export interface Assertion<T = any>
|
||||
* @example
|
||||
* await expect(promise).toHaveResolvedWith('success');
|
||||
*/
|
||||
toHaveResolvedWith: <E>(value: E) => void
|
||||
toHaveResolvedWith: <E>(value: E) => R
|
||||
|
||||
/**
|
||||
* Ensures a promise resolves a specific number of times.
|
||||
@@ -737,7 +740,7 @@ export interface Assertion<T = any>
|
||||
* @example
|
||||
* expect(mockAsyncFunc).toHaveResolvedTimes(3);
|
||||
*/
|
||||
toHaveResolvedTimes: (times: number) => void
|
||||
toHaveResolvedTimes: (times: number) => R
|
||||
|
||||
/**
|
||||
* Asserts that the last resolved value of a promise matches an expected value.
|
||||
@@ -745,7 +748,7 @@ export interface Assertion<T = any>
|
||||
* @example
|
||||
* await expect(mockAsyncFunc).toHaveLastResolvedWith('finalResult');
|
||||
*/
|
||||
toHaveLastResolvedWith: <E>(value: E) => void
|
||||
toHaveLastResolvedWith: <E>(value: E) => R
|
||||
|
||||
/**
|
||||
* Ensures a specific value was returned by a promise on the nth resolution.
|
||||
@@ -753,7 +756,7 @@ export interface Assertion<T = any>
|
||||
* @example
|
||||
* await expect(mockAsyncFunc).toHaveNthResolvedWith(2, 'secondResult');
|
||||
*/
|
||||
toHaveNthResolvedWith: <E>(nthCall: number, value: E) => void
|
||||
toHaveNthResolvedWith: <E>(nthCall: number, value: E) => R
|
||||
|
||||
/**
|
||||
* Verifies that a promise resolves.
|
||||
@@ -769,14 +772,14 @@ export interface Assertion<T = any>
|
||||
* @example
|
||||
* await expect(someAsyncFunc).rejects.toThrow('error');
|
||||
*/
|
||||
rejects: PromisifyAssertion<T>
|
||||
rejects: PromisifyAssertion<unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Chai-style assertions for spy/mock testing.
|
||||
* These provide sinon-chai compatible assertion names that delegate to Jest-style implementations.
|
||||
*/
|
||||
export interface ChaiMockAssertion {
|
||||
export interface ChaiMockAssertion<R extends void | Promise<void>, T = unknown> {
|
||||
/**
|
||||
* Checks that a spy was called at least once.
|
||||
* Chai-style equivalent of `toHaveBeenCalled`.
|
||||
@@ -784,7 +787,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.been.called
|
||||
*/
|
||||
readonly called: Assertion
|
||||
readonly called: Assertion<R, T>
|
||||
|
||||
/**
|
||||
* Checks that a spy was called a specific number of times.
|
||||
@@ -793,7 +796,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.callCount(3)
|
||||
*/
|
||||
callCount: (count: number) => void
|
||||
callCount: (count: number) => R
|
||||
|
||||
/**
|
||||
* Checks that a spy was called with specific arguments at least once.
|
||||
@@ -802,7 +805,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.been.calledWith('arg1', 'arg2')
|
||||
*/
|
||||
calledWith: <E extends any[]>(...args: E) => void
|
||||
calledWith: <E extends any[]>(...args: E) => R
|
||||
|
||||
/**
|
||||
* Checks that a spy was called exactly once.
|
||||
@@ -811,7 +814,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.been.calledOnce
|
||||
*/
|
||||
readonly calledOnce: Assertion
|
||||
readonly calledOnce: Assertion<R, T>
|
||||
|
||||
/**
|
||||
* Checks that a spy was called exactly once with specific arguments.
|
||||
@@ -820,7 +823,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.been.calledOnceWith('arg1', 'arg2')
|
||||
*/
|
||||
calledOnceWith: <E extends any[]>(...args: E) => void
|
||||
calledOnceWith: <E extends any[]>(...args: E) => R
|
||||
|
||||
/**
|
||||
* Checks that the last call to a spy was made with specific arguments.
|
||||
@@ -829,7 +832,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.been.lastCalledWith('arg1', 'arg2')
|
||||
*/
|
||||
lastCalledWith: <E extends any[]>(...args: E) => void
|
||||
lastCalledWith: <E extends any[]>(...args: E) => R
|
||||
|
||||
/**
|
||||
* Checks that the nth call to a spy was made with specific arguments.
|
||||
@@ -838,7 +841,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.been.nthCalledWith(2, 'arg1', 'arg2')
|
||||
*/
|
||||
nthCalledWith: <E extends any[]>(n: number, ...args: E) => void
|
||||
nthCalledWith: <E extends any[]>(n: number, ...args: E) => R
|
||||
|
||||
/**
|
||||
* Checks that a spy returned a specific value at least once.
|
||||
@@ -847,7 +850,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.returned('value')
|
||||
*/
|
||||
returned: <E>(value: E) => void
|
||||
returned: <E>(value: E) => R
|
||||
|
||||
/**
|
||||
* Checks that a spy returned a specific value at least once.
|
||||
@@ -856,7 +859,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.returnedWith('value')
|
||||
*/
|
||||
returnedWith: <E>(value: E) => void
|
||||
returnedWith: <E>(value: E) => R
|
||||
|
||||
/**
|
||||
* Checks that a spy returned successfully a specific number of times.
|
||||
@@ -865,7 +868,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.returnedTimes(3)
|
||||
*/
|
||||
returnedTimes: (count: number) => void
|
||||
returnedTimes: (count: number) => R
|
||||
|
||||
/**
|
||||
* Checks that the last return value of a spy matches the expected value.
|
||||
@@ -874,7 +877,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.lastReturnedWith('value')
|
||||
*/
|
||||
lastReturnedWith: <E>(value: E) => void
|
||||
lastReturnedWith: <E>(value: E) => R
|
||||
|
||||
/**
|
||||
* Checks that the nth return value of a spy matches the expected value.
|
||||
@@ -883,7 +886,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.nthReturnedWith(2, 'value')
|
||||
*/
|
||||
nthReturnedWith: <E>(n: number, value: E) => void
|
||||
nthReturnedWith: <E>(n: number, value: E) => R
|
||||
|
||||
/**
|
||||
* Checks that a spy was called before another spy.
|
||||
@@ -892,7 +895,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy1).to.have.been.calledBefore(spy2)
|
||||
*/
|
||||
calledBefore: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => void
|
||||
calledBefore: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => R
|
||||
|
||||
/**
|
||||
* Checks that a spy was called after another spy.
|
||||
@@ -901,7 +904,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy1).to.have.been.calledAfter(spy2)
|
||||
*/
|
||||
calledAfter: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => void
|
||||
calledAfter: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => R
|
||||
|
||||
/**
|
||||
* Checks that a spy was called exactly twice.
|
||||
@@ -910,7 +913,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.been.calledTwice
|
||||
*/
|
||||
readonly calledTwice: Assertion
|
||||
readonly calledTwice: Assertion<R, T>
|
||||
|
||||
/**
|
||||
* Checks that a spy was called exactly three times.
|
||||
@@ -919,16 +922,7 @@ export interface ChaiMockAssertion {
|
||||
* @example
|
||||
* expect(spy).to.have.been.calledThrice
|
||||
*/
|
||||
readonly calledThrice: Assertion
|
||||
}
|
||||
|
||||
declare global {
|
||||
// support augmenting jest.Matchers by other libraries
|
||||
// eslint-disable-next-line ts/no-namespace
|
||||
namespace jest {
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars, ts/no-empty-object-type
|
||||
interface Matchers<R, T = {}> {}
|
||||
}
|
||||
readonly calledThrice: Assertion<R, T>
|
||||
}
|
||||
|
||||
export {}
|
||||
|
||||
@@ -4,21 +4,21 @@ import type { SnapshotState } from '@vitest/snapshot'
|
||||
import type { BenchResult } from '../runtime/benchmark'
|
||||
import type { Test } from '../runtime/runner/types'
|
||||
|
||||
interface SnapshotMatcher<T> {
|
||||
interface SnapshotMatcher<R extends void | Promise<void>, T = unknown> {
|
||||
<U extends { [P in keyof T]: any }>(
|
||||
snapshot: Partial<U>,
|
||||
hint?: string
|
||||
): void
|
||||
(hint?: string): void
|
||||
): R
|
||||
(hint?: string): R
|
||||
}
|
||||
|
||||
interface InlineSnapshotMatcher<T> {
|
||||
interface InlineSnapshotMatcher<R extends void | Promise<void>, T = unknown> {
|
||||
<U extends { [P in keyof T]: any }>(
|
||||
properties: Partial<U>,
|
||||
snapshot?: string,
|
||||
hint?: string
|
||||
): void
|
||||
(hint?: string): void
|
||||
): R
|
||||
(hint?: string): R
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
@@ -37,7 +37,7 @@ declare module 'vitest' {
|
||||
interface ExpectStatic {
|
||||
assert: Chai.AssertStatic
|
||||
unreachable: (message?: string) => never
|
||||
soft: <T>(actual: T, message?: string) => Assertion<T>
|
||||
soft: <T>(actual: T, message?: string) => Assertion<void, T>
|
||||
poll: <T>(
|
||||
actual: (options: { signal: AbortSignal }) => T,
|
||||
options?: ExpectPollOptions,
|
||||
@@ -48,11 +48,11 @@ declare module 'vitest' {
|
||||
addSnapshotSerializer: (plugin: PrettyFormatPlugin) => void
|
||||
}
|
||||
|
||||
interface Assertion<T> {
|
||||
interface Assertion<R, T> {
|
||||
// Snapshots are extended in @vitest/snapshot and are not part of @vitest/expect
|
||||
matchSnapshot: SnapshotMatcher<T>
|
||||
toMatchSnapshot: SnapshotMatcher<T>
|
||||
toMatchInlineSnapshot: InlineSnapshotMatcher<T>
|
||||
matchSnapshot: SnapshotMatcher<R, T>
|
||||
toMatchSnapshot: SnapshotMatcher<R, T>
|
||||
toMatchInlineSnapshot: InlineSnapshotMatcher<R, T>
|
||||
|
||||
/**
|
||||
* Checks that an error thrown by a function matches a previously recorded snapshot.
|
||||
@@ -62,7 +62,7 @@ declare module 'vitest' {
|
||||
* @example
|
||||
* expect(functionWithError).toThrowErrorMatchingSnapshot();
|
||||
*/
|
||||
toThrowErrorMatchingSnapshot: (hint?: string) => void
|
||||
toThrowErrorMatchingSnapshot: (hint?: string) => R
|
||||
|
||||
/**
|
||||
* Checks that an error thrown by a function matches an inline snapshot within the test file.
|
||||
@@ -78,7 +78,7 @@ declare module 'vitest' {
|
||||
toThrowErrorMatchingInlineSnapshot: (
|
||||
snapshot?: string,
|
||||
hint?: string,
|
||||
) => void
|
||||
) => R
|
||||
|
||||
/**
|
||||
* Compares the received value to a snapshot saved in a specified file.
|
||||
@@ -107,7 +107,7 @@ declare module 'vitest' {
|
||||
toBeFasterThan: (
|
||||
expected: BenchResult,
|
||||
options?: { delta?: number },
|
||||
) => void
|
||||
) => R
|
||||
|
||||
/**
|
||||
* Asserts that a benchmark result is slower than another benchmark result.
|
||||
@@ -124,7 +124,7 @@ declare module 'vitest' {
|
||||
toBeSlowerThan: (
|
||||
expected: BenchResult,
|
||||
options?: { delta?: number },
|
||||
) => void
|
||||
) => R
|
||||
|
||||
/**
|
||||
* Ensures a `vi.when` chain has been exhausted.
|
||||
@@ -144,6 +144,6 @@ declare module 'vitest' {
|
||||
*
|
||||
* expect(w).toHaveBeenExhausted()
|
||||
*/
|
||||
toHaveBeenExhausted: () => void
|
||||
toHaveBeenExhausted: () => R
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect, test } from 'vitest'
|
||||
import { page } from 'vitest/browser'
|
||||
|
||||
test('.toBeInTheDocument', () => {
|
||||
test('.toBeInTheDocument', async () => {
|
||||
const window = document.defaultView
|
||||
|
||||
window.customElements.define(
|
||||
@@ -29,12 +29,14 @@ test('.toBeInTheDocument', () => {
|
||||
const fakeElement = {thisIsNot: 'an html element'}
|
||||
const undefinedElement = undefined
|
||||
const nullElement = null
|
||||
const promiseValue = Promise.resolve(null)
|
||||
|
||||
expect(htmlElement).toBeInTheDocument()
|
||||
expect(svgElement).toBeInTheDocument()
|
||||
expect(customElementChild).toBeInTheDocument()
|
||||
expect(detachedElement).not.toBeInTheDocument()
|
||||
expect(nullElement).not.toBeInTheDocument()
|
||||
await (expect(promiseValue).resolves.not.toBeInTheDocument() satisfies Promise<void>)
|
||||
|
||||
expect(page.getByTestId('non-existing')).not.toBeInTheDocument()
|
||||
|
||||
@@ -62,4 +64,4 @@ test('.toBeInTheDocument', () => {
|
||||
notFound,
|
||||
)
|
||||
expect(() => expect(undefinedElement).not.toBeInTheDocument()).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,15 +54,15 @@ expect.extend({
|
||||
})
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Matchers {
|
||||
interface Matchers<R> {
|
||||
// eslint-disable-next-line ts/method-signature-style
|
||||
toReportPassedTest(testName: string, testProject?: string | BrowserInstanceOption[]): void
|
||||
toReportPassedTest(testName: string, testProject?: string | BrowserInstanceOption[]): R
|
||||
// eslint-disable-next-line ts/method-signature-style
|
||||
toReportSummaryTestFiles(options: SummaryOptions): void
|
||||
toReportSummaryTestFiles(options: SummaryOptions): R
|
||||
// eslint-disable-next-line ts/method-signature-style
|
||||
toReportSummaryTests(options: SummaryOptions): void
|
||||
toReportSummaryTests(options: SummaryOptions): R
|
||||
// eslint-disable-next-line ts/method-signature-style
|
||||
toReportNoErrors(): void
|
||||
toReportNoErrors(): R
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('dom related activity', () => {
|
||||
|
||||
// test typing
|
||||
if (0) {
|
||||
await (expect.element(div).toHaveClass('x', { exact: true }) satisfies Promise<void>)
|
||||
await expect.element(div).toHaveClass('x', { exact: true })
|
||||
await expect.element(div).toHaveClass('x', 'y')
|
||||
await expect.element(div).toHaveClass('x', /y/)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
toBeAsync: (expected: unknown) => Promise<R>;
|
||||
toBeAsync: (expected: unknown) => Promise<void>;
|
||||
toBeDividedBy(divisor: number): R
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> extends CustomMatchers<T> {}
|
||||
interface Matchers<R, T> extends CustomMatchers<R> {}
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
|
||||
@@ -10,12 +10,12 @@ const {
|
||||
interface CustomMatchers<R = unknown> {
|
||||
toMatchCustomSnapshot: (properties?: object) => R
|
||||
toMatchCustomInlineSnapshot: (snapshot?: string) => R
|
||||
toMatchCustomFileSnapshot: (filepath: string) => Promise<R>
|
||||
toMatchCustomAsyncInlineSnapshot: (snapshot?: string) => Promise<R>
|
||||
toMatchCustomFileSnapshot: (filepath: string) => Promise<void>
|
||||
toMatchCustomAsyncInlineSnapshot: (snapshot?: string) => Promise<void>
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> extends CustomMatchers<T> {}
|
||||
interface Matchers<R> extends CustomMatchers<R> {}
|
||||
}
|
||||
|
||||
function formatCustom(input: string) {
|
||||
|
||||
@@ -8,7 +8,7 @@ interface CustomMatchers<R = unknown> {
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> extends CustomMatchers<T> {}
|
||||
interface Matchers<R> extends CustomMatchers<R> {}
|
||||
}
|
||||
|
||||
const matchers: MatchersObject = {
|
||||
|
||||
@@ -6,8 +6,8 @@ import { toMatchImageSnapshot } from "jest-image-snapshot";
|
||||
expect.extend({ toMatchImageSnapshot });
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> {
|
||||
toMatchImageSnapshot(): void
|
||||
interface Matchers<R> {
|
||||
toMatchImageSnapshot(): R
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { expect, expectTypeOf, test } from 'vitest'
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
interface CustomMatchers<R = unknown, T = unknown> {
|
||||
toMatchSchema: (schema: { a: string }) => R
|
||||
toEqualMultiple: (a: string, b: number) => R
|
||||
toEqualTyped: (expected: T) => R
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Matchers<T = any> extends CustomMatchers<T> {}
|
||||
interface Matchers<R, T> extends CustomMatchers<R, T> {}
|
||||
}
|
||||
|
||||
test('infers matcher declaration type from a custom matcher type', () => {
|
||||
test('infers matcher declaration type from a custom matcher type', async () => {
|
||||
expect.extend({
|
||||
toMatchSchema(received, expected) {
|
||||
expectTypeOf(received).toBeAny()
|
||||
@@ -22,12 +23,26 @@ test('infers matcher declaration type from a custom matcher type', () => {
|
||||
expectTypeOf(a).toBeString()
|
||||
expectTypeOf(b).toBeNumber()
|
||||
|
||||
return { pass: true, message: () => '' }
|
||||
},
|
||||
toEqualTyped(received, expected) {
|
||||
expectTypeOf(received).toBeAny()
|
||||
expectTypeOf(expected).toBeAny()
|
||||
|
||||
return { pass: true, message: () => '' }
|
||||
},
|
||||
})
|
||||
|
||||
expect({ a: 1, b: '2' }).toMatchSchema({ a: '1' })
|
||||
expectTypeOf(expect({ a: 1, b: '2' }).toMatchSchema({ a: '1' })).toEqualTypeOf<void>()
|
||||
await (expect(Promise.resolve({ a: '1' })).resolves.toMatchSchema({ a: '1' }) satisfies Promise<void>)
|
||||
expect('a').toEqualMultiple('a', 1)
|
||||
expect('a').toEqualTyped('b')
|
||||
// @ts-expect-error Expected value must match the received type.
|
||||
expect('a').toEqualTyped(1)
|
||||
await (expect(Promise.resolve('a')).resolves.not.toEqualTyped('b') satisfies Promise<void>)
|
||||
// @ts-expect-error Expected value must match the resolved type.
|
||||
await expect(Promise.resolve('a')).resolves.toEqualTyped(1)
|
||||
await (expect(Promise.reject(new Error('error'))).rejects.toEqualTyped(new Error('error')) satisfies Promise<void>)
|
||||
})
|
||||
|
||||
test('automatically extends asymmetric matchers', () => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { expect, test, vi } from 'vitest'
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Matchers<R> {
|
||||
toBePollCompatible: () => R
|
||||
}
|
||||
}
|
||||
|
||||
test('simple usage', async () => {
|
||||
await expect.poll(() => false).toBe(false)
|
||||
await expect.poll(() => false).not.toBe(true)
|
||||
@@ -38,7 +44,7 @@ test('timeout', async () => {
|
||||
await expect.poll(() => false, { timeout: 100, interval: 10 }).toBe(true)
|
||||
}).rejects.toThrow(expect.objectContaining({
|
||||
message: 'expected false to be true // Object.is equality',
|
||||
stack: expect.stringContaining('expect-poll.test.ts:38:68'),
|
||||
stack: expect.stringContaining('expect-poll.test.ts:44:68'),
|
||||
cause: expect.objectContaining({
|
||||
message: 'Matcher did not succeed in time.',
|
||||
}),
|
||||
@@ -85,7 +91,7 @@ test('custom matcher works correctly', async () => {
|
||||
const fn = vi.fn()
|
||||
let idx = 0
|
||||
expect.extend({
|
||||
toBeJestCompatible() {
|
||||
toBePollCompatible() {
|
||||
idx++
|
||||
fn({ poll: this.poll })
|
||||
return {
|
||||
@@ -94,7 +100,7 @@ test('custom matcher works correctly', async () => {
|
||||
}
|
||||
},
|
||||
})
|
||||
await expect.poll(() => 1, { interval: 10 }).toBeJestCompatible()
|
||||
await expect.poll(() => 1, { interval: 10 }).toBePollCompatible()
|
||||
expect(fn).toHaveBeenCalledTimes(3)
|
||||
expect(fn).toHaveBeenCalledWith({ poll: true })
|
||||
})
|
||||
|
||||
@@ -2,35 +2,43 @@
|
||||
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
test('expect.* allows asymmetrict mattchers with different types', () => {
|
||||
test('expect.* allows asymmetrict mattchers with different types', async () => {
|
||||
// types.ts examples: stringContaining
|
||||
expect('I have an apple').toEqual(expect.stringContaining('apple'))
|
||||
expect('I have an apple').toEqual<string>(expect.stringContaining('apple'))
|
||||
await (expect(Promise.resolve('I have an apple')).resolves.toEqual<string>(expect.stringContaining('apple')) satisfies Promise<void>)
|
||||
|
||||
expect({ a: 'test string' }).toEqual({ a: expect.stringContaining('test') })
|
||||
expect({ a: 'test string' }).toEqual<{ a: string }>({ a: expect.stringContaining('test') })
|
||||
await (expect(Promise.resolve({ a: 'test string' })).resolves.toEqual<{ a: string }>({ a: expect.stringContaining('test') }) satisfies Promise<void>)
|
||||
|
||||
// types.ts examples: objectContaining
|
||||
expect({ a: '1', b: 2 }).toEqual(expect.objectContaining({ a: '1' }))
|
||||
expect({ a: '1', b: 2 }).toEqual<{ a: string; b: string }>(expect.objectContaining({ a: '1' }))
|
||||
await (expect(Promise.resolve({ a: '1', b: 2 })).resolves.toEqual<{ a: string; b: string }>(expect.objectContaining({ a: '1' })) satisfies Promise<void>)
|
||||
|
||||
// types.ts examples: arrayContaining
|
||||
expect(['a', 'b', 'c']).toEqual(expect.arrayContaining(['b', 'a']))
|
||||
expect(['a', 'b', 'c']).toEqual<string[]>(expect.arrayContaining(['b', 'a']))
|
||||
await (expect(Promise.resolve(['a', 'b', 'c'])).resolves.toEqual<string[]>(expect.arrayContaining(['b', 'a'])) satisfies Promise<void>)
|
||||
|
||||
// types.ts examples: stringMatching
|
||||
expect('hello world').toEqual(expect.stringMatching(/^hello/))
|
||||
expect('hello world').toEqual<string>(expect.stringMatching(/^hello/))
|
||||
await (expect(Promise.resolve('hello world')).resolves.toEqual<string>(expect.stringMatching(/^hello/)) satisfies Promise<void>)
|
||||
|
||||
expect('hello world').toEqual(expect.stringMatching('hello'))
|
||||
expect('hello world').toEqual<string>(expect.stringMatching('hello'))
|
||||
await (expect(Promise.resolve('hello world')).resolves.toEqual<string>(expect.stringMatching('hello')) satisfies Promise<void>)
|
||||
|
||||
// types.ts examples: closeTo
|
||||
expect(10.45).toEqual(expect.closeTo(10.5, 1))
|
||||
expect(10.45).toEqual<number>(expect.closeTo(10.5, 1))
|
||||
await (expect(Promise.resolve(10.45)).resolves.toEqual<number>(expect.closeTo(10.5, 1)) satisfies Promise<void>)
|
||||
|
||||
expect(5.11).toEqual(expect.closeTo(5.12))
|
||||
expect(5.11).toEqual<number>(expect.closeTo(5.12))
|
||||
await (expect(Promise.resolve(5.11)).resolves.toEqual<number>(expect.closeTo(5.12)) satisfies Promise<void>)
|
||||
|
||||
// expect.any(String)
|
||||
// https://github.com/vitest-dev/vitest/pull/7016#issuecomment-2517674066
|
||||
@@ -52,6 +60,14 @@ test('expect.* allows asymmetrict mattchers with different types', () => {
|
||||
id: expect.any(String),
|
||||
name: 'Amelia',
|
||||
})
|
||||
|
||||
await (expect(Promise.resolve(obj)).resolves.toEqual<{
|
||||
id: string
|
||||
name: string
|
||||
}>({
|
||||
id: expect.any(String),
|
||||
name: 'Amelia',
|
||||
}) satisfies Promise<void>)
|
||||
}
|
||||
|
||||
// expect.any(Date)
|
||||
@@ -81,6 +97,16 @@ test('expect.* allows asymmetrict mattchers with different types', () => {
|
||||
createdAt: expect.any(Date),
|
||||
})
|
||||
|
||||
await (expect(Promise.resolve(actual)).resolves.toEqual<{
|
||||
foo: string
|
||||
bar: string
|
||||
createdAt: Date
|
||||
}>({
|
||||
foo: 'foo',
|
||||
bar: 'bar',
|
||||
createdAt: expect.any(Date),
|
||||
}) satisfies Promise<void>)
|
||||
|
||||
expect(actual).toEqual<{
|
||||
foo: string
|
||||
bar: string
|
||||
@@ -92,6 +118,18 @@ test('expect.* allows asymmetrict mattchers with different types', () => {
|
||||
createdAt: expect.any(Date),
|
||||
},
|
||||
])
|
||||
|
||||
await (expect(Promise.resolve(actual)).resolves.toEqual<{
|
||||
foo: string
|
||||
bar: string
|
||||
createdAt: Date
|
||||
}[]>([
|
||||
{
|
||||
foo: 'foo',
|
||||
bar: 'bar',
|
||||
createdAt: expect.any(Date),
|
||||
},
|
||||
]) satisfies Promise<void>)
|
||||
}
|
||||
|
||||
// expect.arrayContaining
|
||||
@@ -99,18 +137,22 @@ test('expect.* allows asymmetrict mattchers with different types', () => {
|
||||
{
|
||||
expect([1, 2, 3]).toEqual(expect.arrayContaining(['a']))
|
||||
expect([1, 2, 3]).toEqual<number[]>(expect.arrayContaining(['a']))
|
||||
await (expect(Promise.resolve([1, 2, 3])).resolves.toEqual<number[]>(expect.arrayContaining(['a'])) satisfies Promise<void>)
|
||||
|
||||
expect([1, 2, 3]).toEqual(expect.arrayContaining([expect.any(Number)]))
|
||||
expect([1, 2, 3]).toEqual<number[]>(expect.arrayContaining([expect.any(Number)]))
|
||||
await (expect(Promise.resolve([1, 2, 3])).resolves.toEqual<number[]>(expect.arrayContaining([expect.any(Number)])) satisfies Promise<void>)
|
||||
|
||||
expect([1, 2, 3]).toEqual(expect.arrayContaining([expect.anything()]))
|
||||
expect([1, 2, 3]).toEqual<number[]>(expect.arrayContaining([expect.anything()]))
|
||||
await (expect(Promise.resolve([1, 2, 3])).resolves.toEqual<number[]>(expect.arrayContaining([expect.anything()])) satisfies Promise<void>)
|
||||
}
|
||||
|
||||
// expect.any(Array)
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/62831/files#diff-ff7b882e4a29e7fe0e348a6bdf8b11774d606eaa221009b166b01389576d921fR1237
|
||||
expect({ list: [1, 2, 3] }).toMatchObject({ list: expect.any(Array) })
|
||||
expect({ list: [1, 2, 3] }).toMatchObject<{ list: number[] }>({ list: expect.any(Array) })
|
||||
await (expect(Promise.resolve({ list: [1, 2, 3] })).resolves.toMatchObject<{ list: number[] }>({ list: expect.any(Array) }) satisfies Promise<void>)
|
||||
|
||||
// expect<T>
|
||||
// https://github.com/vitest-dev/vitest/issues/8081
|
||||
@@ -140,3 +182,7 @@ test('expect.* allows asymmetrict mattchers with different types', () => {
|
||||
expect('A').toBeOneOf(new Set(['A', 'B']))
|
||||
}
|
||||
})
|
||||
|
||||
test('chai assertion', async () => {
|
||||
await (expect(Promise.resolve('a')).resolves.eq('a') satisfies Promise<(type: string, message?: string) => Chai.Assertion>)
|
||||
})
|
||||
|
||||
@@ -16,17 +16,7 @@ interface CustomMatchers<R = unknown> {
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion<T = any> extends CustomMatchers<T> {}
|
||||
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line ts/no-namespace
|
||||
namespace jest {
|
||||
interface Matchers<R> {
|
||||
toBeJestCompatible: () => R
|
||||
}
|
||||
}
|
||||
interface Matchers<R> extends CustomMatchers<R> {}
|
||||
}
|
||||
|
||||
describe('jest-expect', () => {
|
||||
@@ -280,12 +270,6 @@ describe('jest-expect', () => {
|
||||
message: () => 'toBeTestedPromise',
|
||||
})
|
||||
},
|
||||
toBeJestCompatible() {
|
||||
return {
|
||||
pass: true,
|
||||
message: () => '',
|
||||
}
|
||||
},
|
||||
toBeTestedMatcherContext<T>(received: unknown, expected: T) {
|
||||
if (typeof this.utils?.stringify !== 'function') {
|
||||
throw new TypeError('this.utils.stringify is not available.')
|
||||
@@ -308,8 +292,6 @@ describe('jest-expect', () => {
|
||||
expect(() => expect(null).toBeTestedSync()).toThrow('toBeTestedSync')
|
||||
await expect(async () => await expect(null).toBeTestedAsync()).rejects.toThrow('toBeTestedAsync')
|
||||
await expect(async () => await expect(null).toBeTestedPromise()).rejects.toThrow('toBeTestedPromise')
|
||||
|
||||
expect(expect).toBeJestCompatible()
|
||||
})
|
||||
|
||||
it('object', () => {
|
||||
@@ -815,6 +797,10 @@ describe('toSatisfy()', () => {
|
||||
snapshotError(() => expect({ value: 2 }).toEqual({ value: expect.toSatisfy(isOdd) }))
|
||||
snapshotError(() => expect({ value: 2 }).toEqual({ value: expect.toSatisfy(isOdd, 'ODD') }))
|
||||
})
|
||||
|
||||
it('supports a promise return type', async () => {
|
||||
await (expect(Promise.resolve(1)).resolves.toSatisfy(isOdd) satisfies Promise<void>)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toHaveBeenCalled', () => {
|
||||
|
||||
@@ -8,10 +8,10 @@ function reject() {
|
||||
}
|
||||
|
||||
test('resolved inline', async () => {
|
||||
await expect(resolve()).resolves.toMatchInlineSnapshot('"foo"')
|
||||
await (expect(resolve()).resolves.toMatchInlineSnapshot('"foo"') satisfies Promise<void>)
|
||||
})
|
||||
|
||||
test('rejected inline', async () => {
|
||||
await expect(reject()).rejects.toMatchInlineSnapshot('[Error: foo]')
|
||||
await (expect(reject()).rejects.toMatchInlineSnapshot('[Error: foo]') satisfies Promise<void>)
|
||||
await expect(reject()).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: foo]`)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user