Upgrade jest to v29 and remove `@types/jest`
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) => {
~~~~~~~~~~~~~~~~~~