diff --git a/packages/expect/src/jest-extend.ts b/packages/expect/src/jest-extend.ts index 3b7df473d..8da7636db 100644 --- a/packages/expect/src/jest-extend.ts +++ b/packages/expect/src/jest-extend.ts @@ -80,16 +80,17 @@ function JestExtendPlugin( if ( result && typeof result === 'object' - && result instanceof Promise + && typeof (result as any).then === 'function' ) { - return result.then(({ pass, message, actual, expected }) => { + const thenable = result as PromiseLike + return thenable.then(({ pass, message, actual, expected }) => { if ((pass && isNot) || (!pass && !isNot)) { throw new JestExtendError(message(), actual, expected) } }) } - const { pass, message, actual, expected } = result + const { pass, message, actual, expected } = result as SyncExpectationResult if ((pass && isNot) || (!pass && !isNot)) { throw new JestExtendError(message(), actual, expected) diff --git a/packages/expect/src/utils.ts b/packages/expect/src/utils.ts index ab33bd7dc..ba09f89d7 100644 --- a/packages/expect/src/utils.ts +++ b/packages/expect/src/utils.ts @@ -1,5 +1,6 @@ import type { Test } from '@vitest/runner/types' import type { Assertion } from './types' +import { noop } from '@vitest/utils' import { processError } from '@vitest/utils/error' export function createAssertionMessage( @@ -73,12 +74,19 @@ export function recordAsyncExpect( return promise } +function handleTestError(test: Test, err: unknown) { + test.result ||= { state: 'fail' } + test.result.state = 'fail' + test.result.errors ||= [] + test.result.errors.push(processError(err)) +} + export function wrapAssertion( utils: Chai.ChaiUtils, name: string, - fn: (this: Chai.AssertionStatic & Assertion, ...args: any[]) => void, + fn: (this: Chai.AssertionStatic & Assertion, ...args: any[]) => void | PromiseLike, ) { - return function (this: Chai.AssertionStatic & Assertion, ...args: any[]): void { + return function (this: Chai.AssertionStatic & Assertion, ...args: any[]): void | PromiseLike { // private if (name !== 'withTest') { utils.flag(this, '_name', name) @@ -95,13 +103,18 @@ export function wrapAssertion( } try { - return fn.apply(this, args) + const result = fn.apply(this, args) + + if (result && typeof result === 'object' && typeof result.then === 'function') { + return result.then(noop, (err) => { + handleTestError(test, err) + }) + } + + return result } catch (err) { - test.result ||= { state: 'fail' } - test.result.state = 'fail' - test.result.errors ||= [] - test.result.errors.push(processError(err)) + handleTestError(test, err) } } } diff --git a/test/cli/fixtures/expect-soft/expects/soft.test.ts b/test/cli/fixtures/expect-soft/expects/soft.test.ts index ff20fe61a..6d72e483c 100644 --- a/test/cli/fixtures/expect-soft/expects/soft.test.ts +++ b/test/cli/fixtures/expect-soft/expects/soft.test.ts @@ -1,6 +1,7 @@ import { expect, test } from 'vitest' interface CustomMatchers { + toBeAsync: (expected: unknown) => Promise; toBeDividedBy(divisor: number): R } @@ -9,6 +10,12 @@ declare module 'vitest' { } expect.extend({ + toBeAsync: async function (received, expected) { + return { + pass: received === expected, + message: () => `expected ${received} to be ${expected} (asynchronously)`, + }; + }, toBeDividedBy(received, divisor) { const pass = received % divisor === 0 if (pass) { @@ -62,6 +69,12 @@ test('with expect.extend', () => { expect(5).toEqual(6) }) +test('promise with expect.extend', async () => { + await expect.soft(1 + 1).toBeAsync(3); + await expect.soft(1 + 2).toBeAsync(3); + await expect.soft(2 + 2).toBeAsync(3); +}); + test('passed', () => { expect.soft(1).toEqual(1) expect(10).toEqual(10) diff --git a/test/cli/test/expect-soft.test.ts b/test/cli/test/expect-soft.test.ts index b968e78a9..d96ad33e2 100644 --- a/test/cli/test/expect-soft.test.ts +++ b/test/cli/test/expect-soft.test.ts @@ -40,6 +40,12 @@ describe('expect.soft', () => { expect.soft(stderr).toContain('AssertionError: expected 5 to deeply equal 6') }) + test('promise with expect.extend', async () => { + const { stderr } = await run() + expect.soft(stderr).toContain('Error: expected 2 to be 3') + expect.soft(stderr).toContain('Error: expected 4 to be 3') + }) + test('passed', async () => { const { stdout } = await run() expect.soft(stdout).toContain('soft.test.ts > passed')