Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(jest): use @jest/globals package #44365

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

SimenB
Copy link
Contributor

@SimenB SimenB commented Apr 29, 2020

  • Use a meaningful title for the pull request. Include the name of the package modified.
  • Test the change in your own code. (Compile and run.)
  • Add or edit tests to reflect the change. (Run with npm test.)
  • Follow the advice from the readme.
  • Avoid common mistakes.
  • Run npm run lint package-name (or tsc if no tslint.json is present).

If changing an existing definition:

  • Provide a URL to documentation or source code which provides context for the suggested changes: feat: add @jest/globals package for importing globals explicitly聽jestjs/jest#9801
  • If this PR brings the type definitions up to date with a new version of the JS library, update the version number in the header.
  • Include tests for your changes
  • If you are making substantial changes, consider adding a tslint.json containing { "extends": "dtslint/dt.json" }. If for reason the any rule need to be disabled, disable it for that line using // tslint:disable-next-line [ruleName] and not for whole package so that the need for disabling can be reviewed.

Hey all! 馃憢 Jest maintainer here 馃檪

In jest@25.5 we've introduced a new package - @jest/globals. This package was added for people who don't want to use globals in their code (I realize now the name could be better...), but I think it could also be used by DT to expose the types on the global environment.

This PR is of course not meant to be merged as is, but I personally want this to be the end goal for @types/jest - sticking Jest's types on the global namespace, but doing essentially nothing else. I think it's an improvement to have the types come from the project itself. Jest is written in TypeScript, so we should be in a good position to ship good typings.

Now, there are a few things broken by this

  1. Lots of helpers (like jest.DoneCallback, Serializer types, etc.) are no longer easily accessible - we should restore that functionality. But they should fetch those types from Jest's packages, not define them here
  2. jasmine types are gone (those should stay here in DT - I have no interest in exposing more of the Jasmine stuff from Jest. It's just late at night, so I'm too tired to revert those changes 馃槄)
  3. Right now the types in the Jest repo itself are strictly worse, so we need to port over as much as possible. I'll be available to review and release, so I'm hoping that will be achievable in a timely fashion. This is also a chance to make sure types and implementation match - I noticed a few genuine errors in the types in the sea of red following these changes
  4. Most importantly, this type definition has lots of really strong contributors to it, and I'd hate to see those contributions dry up. Anything we (or I specifically) can do to help contributors to the Jest types continue contributing in the main repo?

/cc @orta

@typescript-bot

This comment has been minimized.

@typescript-bot typescript-bot moved this from Other to Needs Author Action in New Pull Request Status Board Apr 29, 2020
@typescript-bot typescript-bot moved this from Needs Author Action to Waiting for Code Reviews in New Pull Request Status Board Apr 29, 2020
@SimenB
Copy link
Contributor Author

SimenB commented Apr 29, 2020

Hmm, I'm not gonna be fixing tests here before we've discussed this properly. I was hoping the bot would ping all the maintainers, but it seems it has changed behavior since I was last in this repo 馃槄

@orta @alloy thoughts on how to best explore this further? I can open up an issue either here or in fb/jest of course.

@typescript-bot typescript-bot moved this from Waiting for Code Reviews to Needs Author Action in New Pull Request Status Board Apr 29, 2020
@orta
Copy link
Collaborator

orta commented Apr 29, 2020

Changing this PR to a draft, and the bot will be quiet 馃憤

@orta
Copy link
Collaborator

orta commented Apr 29, 2020

... oops, so ping @NoHomey @jwbay @asvetliakov @alexjoverm @epicallan @ikatyang @wsmd @JamieMason @douglasduteil @ahnpnl @JoshuaKGoldberg @UselessPickles @r3nya @Hotell @sebald @andys8 @antoinebrault @favna @gstamac @ExE-Boss @quassnoi @Belco90 @tonyhallett @ycmjason @devanshj @pawfa @regevbr

Conceptually, I love this idea 馃憤 it's a solid path forwards.

There are probably a few jest-x-y modules that will depend on @types/jest but that's also fixable via the same @jest/globals in the end.

For me, the tricky bit on a transition like this is that it's trivial for DT to have massive amounts of JSDoc info on anything exposed to the user. This is done in a way that's not intrusive to working on Jest itself, whereas I think working in a codebase with that much documentation might be more annoying for devs.

Re: Contributors, good q - I'd like to hope anyone experienced enough to contribute here would contribute on facebook/jest - given that most of the types live in one spot it should be pretty reasonable for folks to make changes

@SimenB
Copy link
Contributor Author

SimenB commented Apr 29, 2020

Thanks Orta!

For me, the tricky bit on a transition like this is that it's trivial for DT to have massive amounts of JSDoc info on anything exposed to the user.

Right, good point. I guess we could re-export more granularly and keep the examples here somehow?

given that most of the types live in one spot it should be pretty reasonable for folks to make changes

Yeah, hopefully. It's somewhat spread out as matchers, matcher utils, diffing options etc are in separate packages, even if the globals themselves are fairly co-located. I'm happy to make structural changes to ease contributions of course. I've already moved some types that are generally useful into @jest/types, such as @jest/transform types, and re-export them from the specific packages. I'm very much open to doing that for more packages so the types themselves can be more co-located, but still be the source of truth for the code

@favna
Copy link
Contributor

favna commented Apr 30, 2020

Personally I know some work was required on jest-environment-puppeteer (#44033) so I'm concerned about that with this change. If you could verify it (taking the comments of the linked PR into account) that would be great.

@ExE-Boss
Copy link
Contributor

It聽might be聽better to聽do:

import { ... } from "@jest/globals";

declare global {
	export { ... }; // Or whatever the syntax for doing this is.
};

@SimenB
Copy link
Contributor Author

SimenB commented May 5, 2020

Looking at the test results, it seems first order of business is to figure out how to add custom matchers

@SimenB
Copy link
Contributor Author

SimenB commented May 5, 2020

@orta do you have any suggested syntax for adding custom matchers? Using interface merging, or something else? I know next to nothing about how that works, tbh

@orta
Copy link
Collaborator

orta commented May 5, 2020

Interface merging is the right option IMO - so if you make sure the returned value ofexpect function is an interface:

declare namespace jest {
  interface Matchers {
     toBeCalled(): void
  }
export const expect: Matchers
}

Then anyone else can extend it by

declare namespace jest {
  interface Matchers {
    toHaveSVGSnapshot(): void
  }
}

in their own d.ts files

@tonyhallett
Copy link
Contributor

@SimenB

end goal for聽@types/jest聽- sticking Jest's types on the global namespace, but doing essentially nothing else. I think it's an improvement to have the types come from the project itself. Jest is written in TypeScript, so we should be in a good position to ship good typings.

No problem with this as long as 1. and 3. are addressed.

Now, there are a few things broken by this

  1. Lots of helpers (like jest.DoneCallback, Serializer types, etc.) are no longer easily accessible - we should restore that functionality. But they should fetch those types from Jest's packages, not define them here
  1. Right now the types in the Jest repo itself are strictly worse, so we need to port over as much as possible.

@orta @SimenB
With regards to custom matchers, I corrected and enhanced the expect package with this commit.
If the new definitions I added were not pulled into jest then we would have the scenario where jest is using types from definitelytyped from 9/3/2019. So please port them over.

jest globals

import importedExpect = require('expect');
export declare const expect: typeof importedExpect;
..

expect

import type { AsyncExpectationResult, Expect, ExpectationResult, MatcherState as JestMatcherState, Matchers as MatcherInterface, MatchersObject, PromiseMatcherFn, RawMatcherFn, SyncExpectationResult, ThrowingMatcherFn, } from './types'; 
const expectExport = expect as Expect; // eslint-disable-next-line no-redeclare 
namespace expectExport { 
export type MatcherState = JestMatcherState;
export interface Matchers<R> extends MatcherInterface<R> {} } 
export = expectExport;

types


export type Expect = { <T = unknown>(actual: T): Matchers<T>; 
//no typing
extend(arg0: any): void; 
// none of the built-in inverse asymmetric matchers 
not: {[id: string]: AsymmetricMatcher};
}
// **************** from 9/3/2019
// This is a copy from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/de6730f4463cba69904698035fafd906a72b9664/types/jest/index.d.ts#L570-L817

export interface Matchers<R> {

@orta do you have any suggested syntax for adding custom matchers? Using interface merging, or something else? I know next to nothing about how that works, tbh

With the types that I added there is a helper for interface merging and an alternative that I mentioned in #9810.

  1. ExtendedExpect
const customMatcher = (expected: any, actual: {prop: string}, option1: boolean) => {
    return {pass: true, message: () => ''};
};
const asyncMatcher = () => {
    return Promise.resolve({pass: true, message: () => ''});
};

const customMatchers = {customMatcher, asyncMatcher};
expect.extend(customMatchers);

//**************************
const extendedExpect: jest.ExtendedExpect<typeof customMatchers> = expect as any;

//now have access to all the built in matchers / asymmetric matchers and inverse asymmetric matchers
//****** and all custom matchers e.g
extendedExpect({}).customMatcher
//****** and applicable ( non async ) custom matchers are available as asymmetric matchers and inverse asymmetric matchers
extendedExpect.customMatcher
//this is not present on the typing - extendedExpect.asyncMatcher

Perhaps expect code could change to return itself and there would be no need for the cast ?

const extendedExpect = expect.extend(customMatchers);
extendedExpect.customMatcher
  1. CustomJestMatchers and CustomAsymmetricMatchers.
    For usage in your declaration merging. Write your matchers and use type mapping to obtain the necessary interfaces.
import聽{聽customMatchers聽}聽from聽'./customMatchers'; 

type聽AdditionalMatchers聽=聽typeof聽customMatchers;
type聽AdditionalAsymmetricMatchers聽=聽jest.CustomAsymmetricMatchers<AdditionalMatchers> 

declare聽global聽聽{ namespace聽jest{ 
  interface聽Matchers<R>聽extends聽CustomJestMatchers<AdditionalMatchers,R>{聽} 

  interface聽InverseAsymmetricMatchers聽extends聽AdditionalAsymmetricMatchers{} 
  interface聽Expect聽extends聽AdditionalAsymmetricMatchers{} } }

Only drawback of both of these
If the generic T from expect(T) is used in a custom matcher then parameters that depend on it cannot be ( or cannot be with my type mappings ) inferred correctly.

interface Expect {
    <T = any>(actual: T): JestMatchers<T>;
}
interface Matchers<R, T = {}> {

So if your matcher does not need TActual then there is no need for declaration merging if you are writing your own matchers for immediate usage. If you are writing a matchers library and do not need TActual you can use type mapping and there is no need to manually type / build generate and there is safety in the asymmetric matchers.

@SimenB
The draft pull request that I mentioned, Document asymmetric matchers mentions these additional types. Am I safe to continue knowing that my changes will become part of jest ?

Finally. Whilst checking the type changes that I introduced I noticed that there is an error in one of my type names. It is CustomAsyncMatchers when it should be CustomAsymmetricMatchers ( discussed in 2. ) I have changed this in my local branch as well as refactoring to expose the type CustomJestMatchers ( also discussed in 2.).
I assume that the move to jest globals could take some time and that I should proceed with this definitely typed pull request.

@SimenB
Copy link
Contributor Author

SimenB commented Feb 18, 2022

Latest in this space, for anyone following along: jestjs/jest#12424

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging this pull request may close these issues.

None yet

8 participants