core icon indicating copy to clipboard operation
core copied to clipboard

Upgrade jest to v29 and remove `@types/jest`

Open MajorLift opened this issue 2 years ago • 0 comments

Motivation

As part of Shared Libraries Q2 2024 OKRs (O3/KR5), we will be upgrading Jest to v29 in all core libraries.

We will also remove @types/jest entirely and migrate to using the TypeScript types that now ship with Jest.

jest.fn

One impactful improvement in v29 is better typing for mock functions.

Currently, @types/jest types jest.fn() as jest.Mock<any, any>. That is, it requires both the return type and parameters type of the mocked function as generic parameters, and uses any as the default type for both. The any type in particular resulted in dangerous silent failures (see: TypeScript Guidelines).

In v29, jest.fn() (jest.Mock<T>) only takes the mocked function type as a generic parameter, and if omitted, infers its type from the mock implementation. This simplified interface enables us to enforce through code review and documentation that T is always explicitly passed in for maximum type safety.

Applying this change may require some substantial updates due to how Jest recommends importing types now: jestjs.io/docs/getting-started#type-definitions.

  • For previous discussion on this subject, see: https://github.com/MetaMask/core/pull/3609#discussion_r1424664309

Explanation

The following are categories of errors caused by breaking changes in Jest v28 and v29.

As the first step of this upgrade, we will need to resolve these errors.

JSDOM

TypeError: Cannot read properties of undefined (reading 'html')

       5 | // in order to add TextEncoder to jsdom. TextEncoder is expected by jose.
       6 |
    >  7 | module.exports = class CustomTestEnvironment extends JSDOMEnvironment {
         |                  ^
       [8](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468025475?pr=3646#step:5:9) |   async setup() {
       [9](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468025475?pr=3646#step:5:10) |     await super.setup();
      [10](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468025475?pr=3646#step:5:11) |     if (typeof this.global.TextEncoder === 'undefined') {

      at new JSDOMEnvironment (../../node_modules/jest-environment-jsdom/build/index.js:72:44)
      at new CustomTestEnvironment (jest.environment.js:7:[18](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468025475?pr=3646#step:5:19))

ESM parse failure

Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /home/runner/work/core/core/node_modules/uuid/dist/esm-browser/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { default as v1 } from './v1.js';
                                                                                      ^^^^^^

    SyntaxError: Unexpected token 'export'

      3 | import type { Json } from '@metamask/utils';
      4 | import stringify from 'fast-json-stable-stringify';
    > 5 | import { v4 as random } from 'uuid';
        | ^
      6 |
      7 | // Mixin classes require a constructor with an `...any[]` parameter
      [8](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468030172?pr=3646#step:5:9) | // See TS2545

      at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1505:[14](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468030172?pr=3646#step:5:15))
      at Object.require (src/PollingController.ts:5:1)
      at Object.<anonymous> (src/PollingController.test.ts:5:1)

Snapshot

● Package exports › has expected exports

    expect(received).toMatchInlineSnapshot(snapshot)

    Snapshot name: `Package exports has expected exports 1`

    - Snapshot  - 1
    + Received  + 1

    - Array [
    + [
        "PermissionLogController",
      ]

      3 | describe('Package exports', () => {
      4 |   it('has expected exports', () => {
    > 5 |     expect(Object.keys(allExports)).toMatchInlineSnapshot(`
        |                                     ^
      [6](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468029594?pr=3646#step:5:7) |       Array [
      [7](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468029594?pr=3646#step:5:8) |         "PermissionLogController",
      [8](https://github.com/MetaMask/core/actions/runs/7147833591/job/19468029594?pr=3646#step:5:9) |       ]

      at Object.<anonymous> (tests/index.test.ts:5:37)

Mock functions not found

mockImplementation, mockResolvedValue, mockResolvedValueOnce, mockRejectedValueOnce

src/SignatureController.test.ts:161:24 - error TS2339: Property 'mockImplementation' does not exist on type '<ActionType extends "ApprovalController:addRequest" | "KeyringController:signMessage" | "KeyringController:signPersonalMessage" | "KeyringController:signTypedMessage" | "LoggingController:add" | "SignatureController:getState">(actionType: ActionType, ...params: ExtractActionParameters<...> | ... 4 more ... | Extract...'.
 
     161     messengerMock.call.mockImplementation((actionName, ...rest) => {
                                ~~~~~~~~~~~~~~~~~~

MajorLift avatar Dec 14 '23 22:12 MajorLift