jest-extended icon indicating copy to clipboard operation
jest-extended copied to clipboard

TypeScript declarations are not working when used with @jest/globals

Open slavafomin opened this issue 1 year ago • 3 comments

Hello!

Thank you for this great library!

However, when using TypeScript and Jest with @jest/globals, the declarations provided by the package doesn't work. E.g.:

import 'jest-extended';

import { expect } from '@jest/globals';

// TS2339: Property toIncludeSameMembers does not exist on type
expect([]).toIncludeSameMembers([]);

Have you considered this use case? Thank you.

slavafomin avatar Jan 16 '24 19:01 slavafomin

To make the asymmetric matchers visible, you can do the following in your globals.d.ts or equivalent:

import * as jestExtended from 'jest-extended';
type JestExtended = typeof jestExtended;
declare module 'expect' {
    interface AsymmetricMatchers extends JestExtended {}
}

However, this requires you to use asymmetric matching in all cases by using toEqual; e.g. for your original example:

expect([]).toEqual(expect.toIncludeSameMembers([]));

For regular matchers you can also kinda sorta do this:

declare module 'expect' {
    interface Matchers extends JestExtended {}
}

However the problem is that the object exported by jest-extended is of type CustomMatchers<any>, which results in expect() returning an object that allows arbitrary members, compromising type safety. If the package exported the generic CustomMatchers interface itself, alleviating the need for typeof, we could do:

import {CustomMatchers} from 'jest-extended';
declare module 'expect' {
    interface Matchers<R> extends CustomMatchers<R> {}
}

Of course, ideally, the package should just do this itself for both kinds of matchers.

int19h avatar Aug 07 '24 01:08 int19h

Working with TS 5.4+

// global.d.ts

declare module 'expect' {
  interface Matchers<R> extends CustomMatchers<R> {}
  interface AsymmetricMatchers extends CustomMatchers<unknown> {}
}

export {};

alexgwolff avatar Dec 02 '24 20:12 alexgwolff

To get typings working in my project I used the following, which also fixes the fixed any issue:

/* eslint-disable @typescript-eslint/no-explicit-any */

import "@jest/globals";

import JestExtendedMatchers from "jest-extended";

type ReplaceReturnType<T extends (...args: any) => any, TNewReturn> = (
  ...args: Parameters<T>
) => TNewReturn;

/**
 * Jest matchers for Jest Extended.
 * JestExtendedMatchers is fixed on type `any`.
 * To make them work they need to be of type `R` (which is `void | Promise<void>`).
 */
type JestExtendedMatchersFixed<R> = {
  [K in keyof typeof JestExtendedMatchers]: ReplaceReturnType<
    (typeof JestExtendedMatchers)[K],
    R
  >;
};

declare global {
  namespace jest {
    interface AsymmetricMatchers extends JestExtendedMatchers<R> {}
    interface Matchers<R> extends JestExtendedMatchers<R> {}
  }
}

declare module "expect" {
  interface AsymmetricMatchers extends JestExtendedMatchersFixed<R> {}
  interface Matchers<R> extends JestExtendedMatchersFixed<R> {}
}

This adheres to the TypeScript example, as documented here: https://jestjs.io/docs/expect#expectextendmatchers image

jerone avatar Jan 08 '25 16:01 jerone