Skip to content

Suggesting R = unknown for custom matchers makes asymmetric matchers error #7731

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
6 tasks done
kettanaito opened this issue Mar 24, 2025 · 1 comment
Open
6 tasks done
Labels
documentation Improvements or additions to documentation

Comments

@kettanaito
Copy link
Contributor

kettanaito commented Mar 24, 2025

Describe the bug

Here's how Vitest docs recommend declaring types for custom matchers.

interface CustomMatchers<R = unknown> {
  toBeFoo: () => R
}

While this works with symmetric matchers (expect(a).toBeFoo()), when you apply this to an asymmetric one, the unknown return type of the matcher will result in a type error.

interface O {
  s: string
}

expect({ s: 'foo' }).toEqual<O>({ s: expect.toBeFoo() })
Type 'unknown' is not assignable to type '{ s string; }'.ts(2322)

Suggested solutions

  1. Consider defaulting to any instead of unknown. The two are not the same and are not interchangeable[1]. Here, it looks like any may be a better fit.

In fact, I think it should've been any from the start. The matcher itself cannot infer or guarantee a type of the value (that'd be out of its scope). The best it can do is be anything.

Reproduction

https://stackblitz.com/edit/vitest-dev-vitest-2narajwa?file=vite.config.ts,test%2Fbasic.test.ts&initialPath=__vitest__/

Go to test/basic.test.ts and see the type error when asserting on the s property. It must not be there.

System Info

Irrelevant.

Used Package Manager

npm

Validations

@sheremet-va sheremet-va added documentation Improvements or additions to documentation and removed pending triage labels Mar 24, 2025
@sheremet-va
Copy link
Member

sheremet-va commented Mar 24, 2025

Sure. Although in a perfect world, there is only one declaration that covers both matchers and asymmetric matchers:

interface ExpectMatchers extends CustomMatchers {}

// both now work
expect().toBeFoo()
expect.toBeFoo()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants