-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
expect(x).toBeTruthy() should properly narrow typescript types to be non-falsy #2883
Comments
This would be so great! 👍 ProblemWorkararoundsRight now we need to do one of the following (maybe there are other workarounds out there): 1. using non-null assertions// ...
expect(foo).toBeDefined()
expect(foo!.bar).toBeDefined() // ouch, the linter cries and if we remove the line above, the test will crash
expect(foo!.bar!.baz).toBe(1337) // ouch, the linter cries and if we remove the line 2 lines above, the test will crash
// ... 2. returning after each assertion// ...
expect(foo).toBeDefined()
if(!foo) return // satisfy TS - but will never be reached
expect(foo.bar).toBeDefined()
if(!foo.bar) return // satisfy TS - but will never be reached
expect(foo.bar.baz).toBe(1337)
if(!foo.bar.baz) return // satisfy TS - but will never be reached
// ... 3. throwing after each assertion// ...
expect(foo).toBeDefined()
if(!foo) throw new Error('satisfy TS - but will never be reached')
expect(foo.bar).toBeDefined()
if(!foo.bar) throw new Error('satisfy TS - but will never be reached')
expect(foo.bar.baz).toBe(1337)
if(!foo.bar.baz) throw new Error('satisfy TS - but will never be reached')
// ... 4. writing our own assert functionsfunction expectToBeDefined<T>(val?: T): asserts val is NonNullable<T> {
expect(val).toBeDefined()
}
export function expectToBe<T>(val?: unknown, compareVal?: T): asserts val is T {
expect(val).toBe(compareVal)
}
// ...
expectToBeDefined(foo)
expectToBeDefined(foo.bar)
expectToBe(foo.bar.baz, 1337)
// ... Proposed SolutionIt would be awesome, if I tried to figure out where vitest gets the types from and searched for similar issues/prs in Jest (issue) and DefinitelyTyped (issue, PR (was reverted)). So maybe the PR should be done in one of those packages, but I'm not really sure right now. Hope this helps! Thanks for the awesome package! ❤️ |
@gregor-mueller it looks like the typings for this are part of vitest, if you're willing to see if a PR would be accepted: vitest/packages/expect/src/types.ts Lines 102 to 147 in c21c0ef
I agree that there are a lot of great assertions that would be ideal here. I constantly have to do stuff like this: expect(queryDefinition.kind).toEqual(Kind.OPERATION_DEFINITION);
if (queryDefinition.kind !== Kind.OPERATION_DEFINITION) throw new Error(); But that second line should be unnecessary if this: vitest/packages/expect/src/types.ts Line 104 in c21c0ef
were changed to this: toEqual<E extends T>(expected: E): asserts expected is E |
This will break valid code: expect(1).toEqual('2') This code throws, but it is expected to throw. I would recommend improving |
Not sure if I understand correctly. The proposed changes would not change whether it throws or not. It would just make TypeScript understand that after successfully executing the function (in case it does not throw, so basically in the next line), what type the parameter has.
Having looked deeper into the issue, it seems that this is not really possible right now. I found a related TypeScript issue here: |
Throwing an error here is expected behavior. TypeScript should not fail here. But it will because |
Yes, I see. That's what I was going for simpler examples like Something like this would be needed: toEqual<E>(expected: E): asserts T is E // not possible or toEqual<E>(expected: E): asserts this is E // assuming 'this' would be the argument of 'expect()' Looks like it is just not possible right now, because of the limitations mentioned in the linked TS issues. |
In short, what I am trying to say is that TypeScript will show an error in this example, but as a developer I expect this code to fail, so I don't need TypeScript interfering. Vitest also provides |
That makes sense, I got mixed up on the assertion needing to be on the actual value that isn't an argument to the expect method. It looks like the types for Chai assertions come from here: They do look like they'd get around the linked TS issues to me. But to avoid the compiler issues that @sheremet-va mentioned, it may require multiple overloads to be provided for some assertions methods. |
It would be awesome to have this directly in the built-in type definitions. For now I use the following helper function: export function expectToBeDefined<T>(
value: T | undefined
): asserts value is T {
expect(value).toBeDefined();
} |
The team doesn't think this is something that we want to pursue in the Vitest core due to the edge case that will be introduced with this change (see oven-sh/bun#6934).
|
Clear and concise description of the problem
Currently, my tests must look like the following:
The "invariant" function from tiny-invariant throws when falsy, similar to the
.toBeTruthy()
but has proper type narrowing.I believe
expect(response).toBeTruthy()
should apply some sort of "asserts" to properly type narrow.Suggested solution
should become
Alternative
No response
Additional context
No response
Validations
The text was updated successfully, but these errors were encountered: