diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index f2a301a360e9..4dac555b59b0 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -112,6 +112,25 @@ jobs: with: os: windows-latest + test-leak: + name: Node LTS on Ubuntu with leak detection + runs-on: ubuntu-latest + needs: prepare-yarn-cache-ubuntu + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js LTS + uses: actions/setup-node@v3 + with: + node-version: lts/* + cache: yarn + - name: install + run: yarn --immutable + - name: build + run: yarn build:js + - name: run tests with leak detection + run: yarn test-leak + test-coverage: name: Node LTS on Ubuntu with coverage (${{ matrix.shard }}) strategy: @@ -136,9 +155,7 @@ jobs: id: cpu-cores uses: SimenB/github-actions-cpu-cores@v1 - name: run tests with coverage - run: | - yarn jest-coverage --color --config jest.config.ci.mjs --max-workers ${{ steps.cpu-cores.outputs.count }} --shard=${{ matrix.shard }} - yarn test-leak + run: yarn jest-coverage --color --config jest.config.ci.mjs --max-workers ${{ steps.cpu-cores.outputs.count }} --shard=${{ matrix.shard }} - name: map coverage run: node ./scripts/mapCoverage.mjs if: always() diff --git a/CHANGELOG.md b/CHANGELOG.md index df79f2032556..1f150ee4acb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,17 @@ ### Features +- `[@jest/globals, jest-mock]` Add `jest.Spied*` utility types ([#13440](https://github.com/facebook/jest/pull/13440)) + ### Fixes +- `[jest-environment-node]` make `globalThis.performance` writable for Node 19 and fake timers ([#13467](https://github.com/facebook/jest/pull/13467)) + ### Chore & Maintenance ### Performance +- `[*]` Use sha1 instead of sha256 for hashing [#13421](https://github.com/facebook/jest/pull/13421) - `[jest-transform]` Defer creation of cache directory [#13420](https://github.com/facebook/jest/pull/13420) ## 29.2.0 diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index 6314b7f0797c..a961dab0fd0d 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -699,6 +699,10 @@ test('plays audio', () => { }); ``` +### `jest.Spied` + +See [TypeScript Usage](MockFunctionAPI.md#jestspiedsource) chapter of Mock Functions page for documentation. + ### `jest.clearAllMocks()` Clears the `mock.calls`, `mock.instances`, `mock.contexts` and `mock.results` properties of all mocks. Equivalent to calling [`.mockClear()`](MockFunctionAPI.md#mockfnmockclear) on every mocked function. diff --git a/docs/MockFunctionAPI.md b/docs/MockFunctionAPI.md index fef7d983aec6..58b02dc8d8f0 100644 --- a/docs/MockFunctionAPI.md +++ b/docs/MockFunctionAPI.md @@ -644,3 +644,37 @@ test('direct usage', () => { expect(jest.mocked(console.log).mock.calls).toHaveLength(1); }); ``` + +### `jest.Spied` + +Constructs the type of a spied class or function (i.e. the return type of `jest.spyOn()`). + +```ts title="__utils__/setDateNow.ts" +import {jest} from '@jest/globals'; + +export function setDateNow(now: number): jest.Spied { + return jest.spyOn(Date, 'now').mockReturnValue(now); +} +``` + +```ts +import {afterEach, expect, jest, test} from '@jest/globals'; +import {setDateNow} from './__utils__/setDateNow'; + +let spiedDateNow: jest.Spied | undefined = undefined; + +afterEach(() => { + spiedDateNow?.mockReset(); +}); + +test('renders correctly with a given date', () => { + spiedDateNow = setDateNow(1482363367071); + // ... + + expect(spiedDateNow).toHaveBeenCalledTimes(1); +}); +``` + +Types of a class or function can be passed as type argument to `jest.Spied`. If you prefer to constrain the input type, use: `jest.SpiedClass` or `jest.SpiedFunction`. + +Use `jest.SpiedGetter` or `jest.SpiedSetter` to create the type of a spied getter or setter respectively. diff --git a/jest.config.mjs b/jest.config.mjs index 59f81009fb15..b7bfeca7f18f 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -63,7 +63,6 @@ export default { '/packages/jest-runtime/src/__tests__/NODE_PATH_dir', '/packages/jest-snapshot/src/__tests__/plugins', '/packages/jest-snapshot/src/__tests__/fixtures/', - '/packages/jest-validate/src/__tests__/fixtures/', '/e2e/__tests__/iterator-to-null-test.ts', '/e2e/__tests__/tsIntegration.test.ts', // this test needs types to be build, it runs in a separate CI job through `jest.config.ts.mjs` ], diff --git a/package.json b/package.json index 4d4bac5f8990..be2d5a9654d7 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "test": "yarn lint && yarn jest", "typecheck": "yarn typecheck:examples && yarn typecheck:tests", "typecheck:examples": "tsc -p examples/angular --noEmit && tsc -p examples/expect-extend --noEmit && tsc -p examples/typescript --noEmit", - "typecheck:tests": "tsc -b packages/{babel-jest,babel-plugin-jest-hoist,diff-sequences,expect,expect-utils,jest-circus,jest-cli,jest-config,jest-console,jest-snapshot,pretty-format}/**/__tests__", + "typecheck:tests": "tsc -b packages/{babel-jest,babel-plugin-jest-hoist,diff-sequences,expect,expect-utils,jest-circus,jest-cli,jest-config,jest-console,jest-snapshot,jest-util,jest-worker,pretty-format}/**/__tests__", "verify-old-ts": "node ./scripts/verifyOldTs.mjs", "verify-pnp": "node ./scripts/verifyPnP.mjs", "watch": "yarn build:js && node ./scripts/watch.mjs", diff --git a/packages/babel-jest/src/index.ts b/packages/babel-jest/src/index.ts index cc51dbc4ea0d..151df90cad8b 100644 --- a/packages/babel-jest/src/index.ts +++ b/packages/babel-jest/src/index.ts @@ -78,7 +78,7 @@ function getCacheKeyFromConfig( const configPath = [babelOptions.config ?? '', babelOptions.babelrc ?? '']; - return createHash('sha256') + return createHash('sha1') .update(THIS_FILE) .update('\0', 'utf8') .update(JSON.stringify(babelOptions.options)) diff --git a/packages/jest-circus/src/__mocks__/testUtils.ts b/packages/jest-circus/src/__mocks__/testUtils.ts index 300159fd3e71..e68db421baa3 100644 --- a/packages/jest-circus/src/__mocks__/testUtils.ts +++ b/packages/jest-circus/src/__mocks__/testUtils.ts @@ -31,7 +31,7 @@ interface Result extends ExecaSyncReturnValue { } export const runTest = (source: string) => { - const filename = createHash('sha256') + const filename = createHash('sha1') .update(source) .digest('hex') .substring(0, 32); diff --git a/packages/jest-config/src/__tests__/normalize.test.ts b/packages/jest-config/src/__tests__/normalize.test.ts index 60176360650d..9e70d96dd220 100644 --- a/packages/jest-config/src/__tests__/normalize.test.ts +++ b/packages/jest-config/src/__tests__/normalize.test.ts @@ -72,7 +72,7 @@ afterEach(() => { it('picks an id based on the rootDir', async () => { const rootDir = '/root/path/foo'; - const expected = createHash('sha256') + const expected = createHash('sha1') .update('/root/path/foo') .update(String(Infinity)) .digest('hex') @@ -1082,6 +1082,10 @@ describe('preset', () => { jest.requireActual('./jest-preset.json'), ); + const errorMessage = semver.satisfies(process.versions.node, '<19.0.0') + ? /Unexpected token } in JSON at position (104|110)[\s\S]* at / + : 'SyntaxError: Expected double-quoted property name in JSON at position 104'; + await expect( normalize( { @@ -1090,9 +1094,7 @@ describe('preset', () => { }, {} as Config.Argv, ), - ).rejects.toThrow( - /Unexpected token } in JSON at position (104|110)[\s\S]* at /, - ); + ).rejects.toThrow(errorMessage); }); test('throws when preset evaluation throws type error', async () => { @@ -1105,9 +1107,9 @@ describe('preset', () => { {virtual: true}, ); - const errorMessage = semver.satisfies(process.versions.node, '>=16.9.1') - ? "TypeError: Cannot read properties of undefined (reading 'call')" - : /TypeError: Cannot read property 'call' of undefined[\s\S]* at /; + const errorMessage = semver.satisfies(process.versions.node, '<16.9.1') + ? /TypeError: Cannot read property 'call' of undefined[\s\S]* at / + : "TypeError: Cannot read properties of undefined (reading 'call')"; await expect( normalize( diff --git a/packages/jest-config/src/normalize.ts b/packages/jest-config/src/normalize.ts index d20a36437d1b..c69dfac5c61c 100644 --- a/packages/jest-config/src/normalize.ts +++ b/packages/jest-config/src/normalize.ts @@ -301,7 +301,7 @@ const normalizeMissingOptions = ( projectIndex: number, ): Config.InitialOptionsWithRootDir => { if (!options.id) { - options.id = createHash('sha256') + options.id = createHash('sha1') .update(options.rootDir) // In case we load config from some path that has the same root dir .update(configPath || '') diff --git a/packages/jest-create-cache-key-function/src/index.ts b/packages/jest-create-cache-key-function/src/index.ts index 835edc088885..4c056f30e0d8 100644 --- a/packages/jest-create-cache-key-function/src/index.ts +++ b/packages/jest-create-cache-key-function/src/index.ts @@ -49,7 +49,7 @@ function getGlobalCacheKey(files: Array, values: Array) { ] .reduce( (hash, chunk) => hash.update('\0', 'utf8').update(chunk || ''), - createHash('sha256'), + createHash('sha1'), ) .digest('hex') .substring(0, 32); @@ -62,7 +62,7 @@ function getCacheKeyFunction(globalCacheKey: string): GetCacheKeyFunction { const inferredOptions = options || configString; const {config, instrument} = inferredOptions; - return createHash('sha256') + return createHash('sha1') .update(globalCacheKey) .update('\0', 'utf8') .update(sourceText) diff --git a/packages/jest-diff/src/__tests__/diff.test.ts b/packages/jest-diff/src/__tests__/diff.test.ts index a95320c0a344..6c2b40bb7c65 100644 --- a/packages/jest-diff/src/__tests__/diff.test.ts +++ b/packages/jest-diff/src/__tests__/diff.test.ts @@ -20,7 +20,7 @@ const optionsCounts: DiffOptions = { }; // Use only in toBe assertions for edge case messages. -const stripped = (a: unknown, b: unknown) => stripAnsi(diff(a, b) || ''); +const stripped = (a: unknown, b: unknown) => stripAnsi(diff(a, b) ?? ''); // Use in toBe assertions for comparison lines. const optionsBe: DiffOptions = { @@ -62,7 +62,7 @@ describe('different types', () => { test(`'${String(a)}' and '${String(b)}'`, () => { expect(stripped(a, b)).toBe( ' Comparing two different types of values. ' + - `Expected ${typeA} but received ${typeB}.`, + `Expected ${String(typeA)} but received ${String(typeB)}.`, ); }); }); diff --git a/packages/jest-environment-node/src/index.ts b/packages/jest-environment-node/src/index.ts index 5d326167e133..a48507a99e2c 100644 --- a/packages/jest-environment-node/src/index.ts +++ b/packages/jest-environment-node/src/index.ts @@ -90,7 +90,10 @@ export default class NodeEnvironment implements JestEnvironment { configurable: descriptor.configurable, enumerable: descriptor.enumerable, value: val, - writable: descriptor.writable, + writable: + descriptor.writable === true || + // Node 19 makes performance non-readable. This is probably not the correct solution. + nodeGlobalsKey === 'performance', }); return val; }, diff --git a/packages/jest-globals/src/index.ts b/packages/jest-globals/src/index.ts index 956d2b3fece7..c2bd3abb7f59 100644 --- a/packages/jest-globals/src/index.ts +++ b/packages/jest-globals/src/index.ts @@ -16,6 +16,11 @@ import type { MockedClass as JestMockedClass, MockedFunction as JestMockedFunction, MockedObject as JestMockedObject, + Spied as JestSpied, + SpiedClass as JestSpiedClass, + SpiedFunction as JestSpiedFunction, + SpiedGetter as JestSpiedGetter, + SpiedSetter as JestSpiedSetter, UnknownFunction, } from 'jest-mock'; @@ -58,6 +63,26 @@ declare namespace jest { * Wraps an object type with Jest mock type definitions. */ export type MockedObject = JestMockedObject; + /** + * Constructs the type of a spied class or function. + */ + export type Spied = JestSpied; + /** + * Constructs the type of a spied class. + */ + export type SpiedClass = JestSpiedClass; + /** + * Constructs the type of a spied function. + */ + export type SpiedFunction = JestSpiedFunction; + /** + * Constructs the type of a spied getter. + */ + export type SpiedGetter = JestSpiedGetter; + /** + * Constructs the type of a spied setter. + */ + export type SpiedSetter = JestSpiedSetter; } export {jest}; diff --git a/packages/jest-haste-map/src/index.ts b/packages/jest-haste-map/src/index.ts index 1ab221bbe82a..5c3458c96686 100644 --- a/packages/jest-haste-map/src/index.ts +++ b/packages/jest-haste-map/src/index.ts @@ -295,7 +295,7 @@ class HasteMap extends EventEmitter implements IHasteMap { } private async setupCachePath(options: Options): Promise { - const rootDirHash = createHash('sha256') + const rootDirHash = createHash('sha1') .update(options.rootDir) .digest('hex') .substring(0, 32); @@ -344,7 +344,7 @@ class HasteMap extends EventEmitter implements IHasteMap { id: string, ...extra: Array ): string { - const hash = createHash('sha256').update(extra.join('')); + const hash = createHash('sha1').update(extra.join('')); return path.join( tmpdir, `${id.replace(/\W/g, '-')}-${hash.digest('hex').substring(0, 32)}`, diff --git a/packages/jest-mock/__typetests__/mock-functions.test.ts b/packages/jest-mock/__typetests__/mock-functions.test.ts index 3d72cc4d37d0..58efe52a828b 100644 --- a/packages/jest-mock/__typetests__/mock-functions.test.ts +++ b/packages/jest-mock/__typetests__/mock-functions.test.ts @@ -11,7 +11,15 @@ import { expectNotAssignable, expectType, } from 'tsd-lite'; -import {Mock, SpyInstance, fn, spyOn} from 'jest-mock'; +import { + Mock, + SpiedClass, + SpiedFunction, + SpiedGetter, + SpiedSetter, + fn, + spyOn, +} from 'jest-mock'; // jest.fn() @@ -320,26 +328,30 @@ expectNotAssignable(spy); // eslint-disable-line @typescript-eslint/ba expectError(spy()); expectError(new spy()); -expectType>( +expectType>( spyOn(spiedObject, 'methodA'), ); -expectType>( +expectType>( spyOn(spiedObject, 'methodB'), ); -expectType>( +expectType>( spyOn(spiedObject, 'methodC'), ); -expectType boolean>>(spyOn(spiedObject, 'propertyB', 'get')); -expectType void>>( +expectType>( + spyOn(spiedObject, 'propertyB', 'get'), +); +expectType>( spyOn(spiedObject, 'propertyB', 'set'), ); expectError(spyOn(spiedObject, 'propertyB')); expectError(spyOn(spiedObject, 'methodB', 'get')); expectError(spyOn(spiedObject, 'methodB', 'set')); -expectType string>>(spyOn(spiedObject, 'propertyA', 'get')); -expectType void>>( +expectType>( + spyOn(spiedObject, 'propertyA', 'get'), +); +expectType>( spyOn(spiedObject, 'propertyA', 'set'), ); expectError(spyOn(spiedObject, 'propertyA')); @@ -351,40 +363,38 @@ expectError(spyOn(true, 'methodA')); expectError(spyOn(spiedObject)); expectError(spyOn()); -expectType boolean>>( +expectType>( spyOn(spiedArray as unknown as ArrayConstructor, 'isArray'), ); expectError(spyOn(spiedArray, 'isArray')); -expectType string>>( +expectType>( spyOn(spiedFunction as unknown as Function, 'toString'), // eslint-disable-line @typescript-eslint/ban-types ); expectError(spyOn(spiedFunction, 'toString')); -expectType Date>>( - spyOn(globalThis, 'Date'), -); -expectType number>>(spyOn(Date, 'now')); +expectType>(spyOn(globalThis, 'Date')); +expectType>(spyOn(Date, 'now')); // object with index signatures -expectType>( +expectType>( spyOn(indexSpiedObject, 'methodA'), ); -expectType>( +expectType>( spyOn(indexSpiedObject, 'methodB'), ); -expectType>( +expectType>( spyOn(indexSpiedObject, 'methodC'), ); -expectType>( +expectType>( spyOn(indexSpiedObject, 'methodE'), ); -expectType {a: string}>>( +expectType>( spyOn(indexSpiedObject, 'propertyA', 'get'), ); -expectType void>>( +expectType>( spyOn(indexSpiedObject, 'propertyA', 'set'), ); expectError(spyOn(indexSpiedObject, 'propertyA')); @@ -419,48 +429,48 @@ interface OptionalInterface { const optionalSpiedObject = {} as OptionalInterface; -expectType SomeClass>>( +expectType>>( spyOn(optionalSpiedObject, 'constructorA'), ); -expectType SomeClass>>( +expectType>( spyOn(optionalSpiedObject, 'constructorB'), ); expectError(spyOn(optionalSpiedObject, 'constructorA', 'get')); expectError(spyOn(optionalSpiedObject, 'constructorA', 'set')); -expectType void>>( +expectType>>( spyOn(optionalSpiedObject, 'methodA'), ); -expectType boolean>>( +expectType>( spyOn(optionalSpiedObject, 'methodB'), ); expectError(spyOn(optionalSpiedObject, 'methodA', 'get')); expectError(spyOn(optionalSpiedObject, 'methodA', 'set')); -expectType number>>( +expectType>>( spyOn(optionalSpiedObject, 'propertyA', 'get'), ); -expectType void>>( +expectType>>( spyOn(optionalSpiedObject, 'propertyA', 'set'), ); -expectType number>>( +expectType>>( spyOn(optionalSpiedObject, 'propertyB', 'get'), ); -expectType void>>( +expectType>>( spyOn(optionalSpiedObject, 'propertyB', 'set'), ); -expectType number | undefined>>( +expectType>( spyOn(optionalSpiedObject, 'propertyC', 'get'), ); -expectType void>>( +expectType>( spyOn(optionalSpiedObject, 'propertyC', 'set'), ); -expectType string>>( +expectType>( spyOn(optionalSpiedObject, 'propertyD', 'get'), ); -expectType void>>( +expectType>( spyOn(optionalSpiedObject, 'propertyD', 'set'), ); diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 408690efaaab..d0c6ba44ac8c 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -100,6 +100,29 @@ export type MockedShallow = T extends ClassLike : T; export type UnknownFunction = (...args: Array) => unknown; +export type UnknownClass = {new (...args: Array): unknown}; + +export type SpiedClass = MockInstance< + (...args: ConstructorParameters) => InstanceType +>; + +export type SpiedFunction = + MockInstance<(...args: Parameters) => ReturnType>; + +export type SpiedGetter = MockInstance<() => T>; + +export type SpiedSetter = MockInstance<(arg: T) => void>; + +export type Spied = T extends ClassLike + ? MockInstance<(...args: ConstructorParameters) => InstanceType> + : T extends FunctionLike + ? MockInstance<(...args: Parameters) => ReturnType> + : never; + +// TODO in Jest 30 remove `SpyInstance` in favour of `Spied` +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SpyInstance + extends MockInstance {} /** * All what the internal typings need is to be sure that we have any-function. @@ -149,10 +172,6 @@ export interface MockInstance { mockRejectedValueOnce(value: RejectType): this; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface SpyInstance - extends MockInstance {} - type MockFunctionResultIncomplete = { type: 'incomplete'; /** @@ -1080,10 +1099,9 @@ export class ModuleMocker { } isMockFunction( - fn: SpyInstance, - ): fn is SpyInstance; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint - isMockFunction

, R extends unknown>( + fn: MockInstance, + ): fn is MockInstance; + isMockFunction

, R>( fn: (...args: P) => R, ): fn is Mock<(...args: P) => R>; isMockFunction(fn: unknown): fn is Mock; @@ -1107,35 +1125,25 @@ export class ModuleMocker { T extends object, K extends PropertyLikeKeys, V extends Required[K], - >(object: T, methodKey: K, accessType: 'get'): SpyInstance<() => V>; - - spyOn< - T extends object, - K extends PropertyLikeKeys, - V extends Required[K], - >(object: T, methodKey: K, accessType: 'set'): SpyInstance<(arg: V) => void>; - - spyOn< - T extends object, - K extends ConstructorLikeKeys, - V extends Required[K], + A extends 'get' | 'set', >( object: T, methodKey: K, - ): V extends ClassLike - ? SpyInstance<(...args: ConstructorParameters) => InstanceType> + accessType: A, + ): A extends 'get' + ? SpiedGetter + : A extends 'set' + ? SpiedSetter : never; spyOn< T extends object, - K extends MethodLikeKeys, + K extends ConstructorLikeKeys | MethodLikeKeys, V extends Required[K], >( object: T, methodKey: K, - ): V extends FunctionLike - ? SpyInstance<(...args: Parameters) => ReturnType> - : never; + ): V extends ClassLike | FunctionLike ? Spied : never; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types spyOn>( @@ -1205,8 +1213,8 @@ export class ModuleMocker { let mock: Mock; - if (accessType == 'get' && descriptor['get']) { - const originalAccessor = descriptor['get']; + if (accessType == 'get' && descriptor.get) { + const originalAccessor = descriptor.get; mock = this._makeComponent( { type: 'function', @@ -1222,8 +1230,8 @@ export class ModuleMocker { return originalAccessor.call(this); }); Object.defineProperty(object, methodKey, descriptor); - } else if (accessType == 'set' && descriptor['set']) { - const originalAccessor = descriptor['set']; + } else if (accessType == 'set' && descriptor.set) { + const originalAccessor = descriptor.set; mock = this._makeComponent( { type: 'function', diff --git a/packages/jest-repl/src/cli/repl.ts b/packages/jest-repl/src/cli/repl.ts index 757af8df679e..3a486c6bfbb7 100644 --- a/packages/jest-repl/src/cli/repl.ts +++ b/packages/jest-repl/src/cli/repl.ts @@ -10,13 +10,14 @@ declare const jestProjectConfig: Config.ProjectConfig; import * as path from 'path'; import * as repl from 'repl'; +import * as util from 'util'; import {runInThisContext} from 'vm'; import type {SyncTransformer} from '@jest/transform'; import type {Config} from '@jest/types'; import {interopRequireDefault} from 'jest-util'; // TODO: support async as well -let transformer: SyncTransformer; +let transformer: SyncTransformer | undefined; let transformerConfig: unknown; const evalCommand: repl.REPLEval = ( @@ -25,12 +26,12 @@ const evalCommand: repl.REPLEval = ( _filename: string, callback: (e: Error | null, result?: unknown) => void, ) => { - let result; + let result: unknown; try { - if (transformer) { + if (transformer != null) { const transformResult = transformer.process( cmd, - jestGlobalConfig.replname || 'jest.js', + jestGlobalConfig.replname ?? 'jest.js', { cacheFS: new Map(), config: jestProjectConfig, @@ -48,15 +49,19 @@ const evalCommand: repl.REPLEval = ( ? transformResult : transformResult.code; } - result = runInThisContext(cmd); + result = runInThisContext(cmd) as unknown; } catch (e: any) { return callback(isRecoverableError(e) ? new repl.Recoverable(e) : e); } return callback(null, result); }; -const isRecoverableError = (error: Error) => { - if (error && error.name === 'SyntaxError') { +const isRecoverableError = (error: unknown) => { + if (!util.types.isNativeError(error)) { + return false; + } + + if (error.name === 'SyntaxError') { return [ 'Unterminated template', 'Missing } in template expression', @@ -77,7 +82,7 @@ if (jestProjectConfig.transform) { break; } } - if (transformerPath) { + if (transformerPath != null) { const transformerOrFactory = interopRequireDefault( require(transformerPath), ).default; @@ -88,7 +93,7 @@ if (jestProjectConfig.transform) { transformer = transformerOrFactory; } - if (typeof transformer.process !== 'function') { + if (typeof transformer?.process !== 'function') { throw new TypeError( 'Jest: a transformer must export a `process` function.', ); @@ -106,5 +111,5 @@ replInstance.context.require = (moduleName: string) => { if (/(\/|\\|\.)/.test(moduleName)) { moduleName = path.resolve(process.cwd(), moduleName); } - return require(moduleName); + return require(moduleName) as unknown; }; diff --git a/packages/jest-repl/src/cli/runtime-cli.ts b/packages/jest-repl/src/cli/runtime-cli.ts index 7cfbb123f197..ad772abf47a7 100644 --- a/packages/jest-repl/src/cli/runtime-cli.ts +++ b/packages/jest-repl/src/cli/runtime-cli.ts @@ -7,6 +7,7 @@ import {cpus} from 'os'; import * as path from 'path'; +import * as util from 'util'; import chalk = require('chalk'); import yargs = require('yargs'); import {CustomConsole} from '@jest/console'; @@ -24,7 +25,7 @@ export async function run( cliArgv?: Config.Argv, cliInfo?: Array, ): Promise { - let argv; + let argv: Config.Argv; if (cliArgv) { argv = cliArgv; } else { @@ -36,13 +37,13 @@ export async function run( validateCLIOptions(argv, {...args.options, deprecationEntries}); } - if (argv.help) { + if (argv.help === true) { yargs.showHelp(); process.on('exit', () => (process.exitCode = 1)); return; } - if (argv.version) { + if (argv.version == true) { console.log(`v${VERSION}\n`); return; } @@ -56,7 +57,7 @@ export async function run( const root = tryRealpath(process.cwd()); const filePath = path.resolve(root, argv._[0].toString()); - if (argv.debug) { + if (argv.debug === true) { const info = cliInfo ? `, ${cliInfo.join(', ')}` : ''; console.log(`Using Jest Runtime v${VERSION}${info}`); } @@ -130,7 +131,9 @@ export async function run( runtime.requireModule(filePath); } } catch (e: any) { - console.error(chalk.red(e.stack || e)); - process.on('exit', () => (process.exitCode = 1)); + console.error(chalk.red(util.types.isNativeError(e) ? e.stack : e)); + process.on('exit', () => { + process.exitCode = 1; + }); } } diff --git a/packages/jest-repl/src/cli/version.ts b/packages/jest-repl/src/cli/version.ts index 589132f9db10..5c122cc08bc4 100644 --- a/packages/jest-repl/src/cli/version.ts +++ b/packages/jest-repl/src/cli/version.ts @@ -6,4 +6,6 @@ */ // For some reason, doing `require`ing here works, while inside `cli` fails -export const VERSION: string = require('../../package.json').version; +export const VERSION = ( + require('../../package.json') as Record +).version as string; diff --git a/packages/jest-reporters/assets/jest_logo.png b/packages/jest-reporters/assets/jest_logo.png index 079356bc1611..1e8274df81de 100644 Binary files a/packages/jest-reporters/assets/jest_logo.png and b/packages/jest-reporters/assets/jest_logo.png differ diff --git a/packages/jest-reporters/src/Status.ts b/packages/jest-reporters/src/Status.ts index f44c960e3ff1..6e867cb56a8c 100644 --- a/packages/jest-reporters/src/Status.ts +++ b/packages/jest-reporters/src/Status.ts @@ -85,7 +85,7 @@ export default class Status { private _aggregatedResults?: AggregatedResult; private _showStatus: boolean; - constructor(private _globalConfig: Config.GlobalConfig) { + constructor(private readonly _globalConfig: Config.GlobalConfig) { this._cache = null; this._currentTests = new CurrentTestList(); this._currentTestCases = []; diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index 463aab72dc28..1143b98d5aa1 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -117,14 +117,14 @@ class ScriptTransformer { transformerCacheKey: string | undefined, ): string { if (transformerCacheKey != null) { - return createHash('sha256') + return createHash('sha1') .update(transformerCacheKey) .update(CACHE_VERSION) .digest('hex') .substring(0, 32); } - return createHash('sha256') + return createHash('sha1') .update(fileData) .update(transformOptions.configString) .update(transformOptions.instrument ? 'instrument' : '') @@ -878,7 +878,7 @@ const stripShebang = (content: string) => { * could get corrupted, out-of-sync, etc. */ function writeCodeCacheFile(cachePath: string, code: string) { - const checksum = createHash('sha256') + const checksum = createHash('sha1') .update(code) .digest('hex') .substring(0, 32); @@ -897,7 +897,7 @@ function readCodeCacheFile(cachePath: string): string | null { return null; } const code = content.substring(33); - const checksum = createHash('sha256') + const checksum = createHash('sha1') .update(code) .digest('hex') .substring(0, 32); diff --git a/packages/jest-types/__typetests__/jest.test.ts b/packages/jest-types/__typetests__/jest.test.ts index e6a9cc895e1f..ad5717d43645 100644 --- a/packages/jest-types/__typetests__/jest.test.ts +++ b/packages/jest-types/__typetests__/jest.test.ts @@ -9,13 +9,13 @@ import {expectAssignable, expectError, expectType} from 'tsd-lite'; import {jest} from '@jest/globals'; import type { Mock, + MockInstance, Mocked, MockedClass, MockedFunction, MockedObject, MockedShallow, ModuleMocker, - SpyInstance, } from 'jest-mock'; expectType( @@ -216,7 +216,7 @@ const spiedObject = { const surelySpy = jest.spyOn(spiedObject, 'methodA'); if (jest.isMockFunction(surelySpy)) { - expectType boolean>>(surelySpy); + expectType boolean>>(surelySpy); surelySpy.mockReturnValueOnce(false); expectError(surelyMock.mockReturnValueOnce(123)); @@ -287,6 +287,8 @@ function someFunction(a: string, b?: number): boolean { const someObject = { SomeClass, + _propertyC: false, + methodA() { return; }, @@ -304,8 +306,16 @@ const someObject = { }, propertyA: 123, + propertyB: 'value', + set propertyC(value) { + this._propertyC = value; + }, + get propertyC() { + return this._propertyC; + }, + someClassInstance: new SomeClass('value'), }; @@ -433,6 +443,34 @@ expectError( expectAssignable(mockObjectB); +// Spied + +expectAssignable>( + jest.spyOn(someObject, 'methodA'), +); + +expectAssignable>( + jest.spyOn(someObject, 'SomeClass'), +); + +// Spied* + +expectAssignable>( + jest.spyOn(someObject, 'SomeClass'), +); + +expectAssignable>( + jest.spyOn(someObject, 'methodB'), +); + +expectAssignable>( + jest.spyOn(someObject, 'propertyC', 'get'), +); + +expectAssignable>( + jest.spyOn(someObject, 'propertyC', 'set'), +); + // Mock Timers expectType(jest.advanceTimersByTime(6000)); diff --git a/packages/jest-util/src/__tests__/convertDescriptorToString.test.ts b/packages/jest-util/src/__tests__/convertDescriptorToString.test.ts index 9c846fd60eca..f064d02e3cb0 100644 --- a/packages/jest-util/src/__tests__/convertDescriptorToString.test.ts +++ b/packages/jest-util/src/__tests__/convertDescriptorToString.test.ts @@ -27,10 +27,12 @@ describe(convertDescriptorToString, () => { ['anonymous class expression', class {}], ])('%s', (_, input) => { expect(() => { - // @ts-expect-error + // @ts-expect-error: Testing runtime error return convertDescriptorToString(input); }).toThrow( - `Invalid first argument, ${input}. It must be a named class, named function, number, or string.`, + `Invalid first argument, ${String( + input, + )}. It must be a named class, named function, number, or string.`, ); }); }); diff --git a/packages/jest-util/src/__tests__/createProcessObject.test.ts b/packages/jest-util/src/__tests__/createProcessObject.test.ts index c521ea25263c..6689b49eb316 100644 --- a/packages/jest-util/src/__tests__/createProcessObject.test.ts +++ b/packages/jest-util/src/__tests__/createProcessObject.test.ts @@ -7,11 +7,13 @@ import {EventEmitter} from 'events'; -let createProcessObject; +let createProcessObject: typeof import('../createProcessObject').default; function requireCreateProcessObject() { jest.isolateModules(() => { - createProcessObject = require('../createProcessObject').default; + createProcessObject = ( + require('../createProcessObject') as typeof import('../createProcessObject') + ).default; }); } @@ -22,13 +24,13 @@ it('creates a process object that looks like the original one', () => { // "process" inherits from EventEmitter through the prototype chain. expect(fakeProcess instanceof EventEmitter).toBe(true); - // They look the same, but they are NOT the same (deep copied object). The - // "_events" property is checked to ensure event emitter properties are + // They look the same, but they are NOT the same (deep copied object). + // The `_events` property is checked to ensure event emitter properties are // properly copied. - ['argv', 'env', '_events'].forEach(key => { - // @ts-expect-error + (['argv', 'env', '_events'] as const).forEach(key => { + // @ts-expect-error: Testing internal `_events` property expect(fakeProcess[key]).toEqual(process[key]); - // @ts-expect-error + // @ts-expect-error: Testing internal `_events` property expect(fakeProcess[key]).not.toBe(process[key]); }); @@ -47,7 +49,7 @@ it('checks that process.env works as expected on Linux platforms', () => { // Existing properties inside process.env are copied to the fake environment. process.env.PROP_STRING = 'foo'; - // @ts-expect-error + // @ts-expect-error: Type 'number' is not assignable to type 'string'. process.env.PROP_NUMBER = 3; process.env.PROP_UNDEFINED = undefined; @@ -102,6 +104,6 @@ it('checks that process.env works as expected in Windows platforms', () => { // You can delete through case-insensitiveness too. delete fake.prop_string; - expect(Object.prototype.hasOwnProperty.call('PROP_string')).toBe(false); - expect(Object.prototype.hasOwnProperty.call('PROP_string')).toBe(false); + expect(Object.prototype.hasOwnProperty.call(fake, 'PROP_string')).toBe(false); + expect(Object.prototype.hasOwnProperty.call(fake, 'PROP_string')).toBe(false); }); diff --git a/packages/jest-util/src/__tests__/deepCyclicCopy.test.ts b/packages/jest-util/src/__tests__/deepCyclicCopy.test.ts index 1f81c3202eae..0a5048b0063c 100644 --- a/packages/jest-util/src/__tests__/deepCyclicCopy.test.ts +++ b/packages/jest-util/src/__tests__/deepCyclicCopy.test.ts @@ -23,9 +23,9 @@ it('returns the same value for primitive or function values', () => { it('does not execute getters/setters, but copies them', () => { const fn = jest.fn(); const obj = { - // @ts-expect-error get foo() { fn(); + return; }, }; const copy = deepCyclicCopy(obj); @@ -49,8 +49,11 @@ it('copies arrays as array objects', () => { }); it('handles cyclic dependencies', () => { - const cyclic: any = {a: 42, subcycle: {}}; + type Cyclic = {[key: string]: unknown | Cyclic} & {subcycle?: Cyclic}; + const cyclic: Cyclic = {a: 42}; + + cyclic.subcycle = {}; cyclic.subcycle.baz = cyclic; cyclic.bar = cyclic; @@ -60,7 +63,7 @@ it('handles cyclic dependencies', () => { expect(copy.a).toBe(42); expect(copy.bar).toEqual(copy); - expect(copy.subcycle.baz).toEqual(copy); + expect(copy.subcycle?.baz).toEqual(copy); }); it('uses the blacklist to avoid copying properties on the first level', () => { @@ -84,17 +87,17 @@ it('uses the blacklist to avoid copying properties on the first level', () => { }); it('does not keep the prototype by default when top level is object', () => { - // @ts-expect-error + // @ts-expect-error: Testing purpose const sourceObject = new (function () {})(); - // @ts-expect-error + // @ts-expect-error: Testing purpose sourceObject.nestedObject = new (function () {})(); - // @ts-expect-error + // @ts-expect-error: Testing purpose sourceObject.nestedArray = new (function () { - // @ts-expect-error + // @ts-expect-error: Testing purpose this.length = 0; })(); - const spy = jest + const spyArray = jest .spyOn(Array, 'isArray') .mockImplementation(object => object === sourceObject.nestedArray); @@ -118,15 +121,15 @@ it('does not keep the prototype by default when top level is object', () => { Object.getPrototypeOf([]), ); - spy.mockRestore(); + spyArray.mockRestore(); }); it('does not keep the prototype by default when top level is array', () => { - const spy = jest.spyOn(Array, 'isArray').mockImplementation(() => true); + const spyArray = jest.spyOn(Array, 'isArray').mockImplementation(() => true); - // @ts-expect-error + // @ts-expect-error: Testing purpose const sourceArray = new (function () { - // @ts-expect-error + // @ts-expect-error: Testing purpose this.length = 0; })(); @@ -136,15 +139,15 @@ it('does not keep the prototype by default when top level is array', () => { ); expect(Object.getPrototypeOf(copy)).toBe(Object.getPrototypeOf([])); - spy.mockRestore(); + spyArray.mockRestore(); }); it('does not keep the prototype of arrays when keepPrototype = false', () => { - const spy = jest.spyOn(Array, 'isArray').mockImplementation(() => true); + const spyArray = jest.spyOn(Array, 'isArray').mockImplementation(() => true); - // @ts-expect-error + // @ts-expect-error: Testing purpose const sourceArray = new (function () { - // @ts-expect-error + // @ts-expect-error: Testing purpose this.length = 0; })(); @@ -154,49 +157,49 @@ it('does not keep the prototype of arrays when keepPrototype = false', () => { ); expect(Object.getPrototypeOf(copy)).toBe(Object.getPrototypeOf([])); - spy.mockRestore(); + spyArray.mockRestore(); }); it('keeps the prototype of arrays when keepPrototype = true', () => { - const spy = jest.spyOn(Array, 'isArray').mockImplementation(() => true); + const spyArray = jest.spyOn(Array, 'isArray').mockImplementation(() => true); - // @ts-expect-error + // @ts-expect-error: Testing purpose const sourceArray = new (function () { - // @ts-expect-error + // @ts-expect-error: Testing purpose this.length = 0; })(); const copy = deepCyclicCopy(sourceArray, {keepPrototype: true}); expect(Object.getPrototypeOf(copy)).toBe(Object.getPrototypeOf(sourceArray)); - spy.mockRestore(); + spyArray.mockRestore(); }); it('does not keep the prototype for objects when keepPrototype = false', () => { + // @ts-expect-error: Testing purpose + const sourceObject = new (function () {})(); // @ts-expect-error - const sourceobject = new (function () {})(); - // @ts-expect-error - sourceobject.nestedObject = new (function () {})(); - // @ts-expect-error - sourceobject.nestedArray = new (function () { - // @ts-expect-error + sourceObject.nestedObject = new (function () {})(); + // @ts-expect-error: Testing purpose + sourceObject.nestedArray = new (function () { + // @ts-expect-error: Testing purpose this.length = 0; })(); - const spy = jest + const spyArray = jest .spyOn(Array, 'isArray') - .mockImplementation(object => object === sourceobject.nestedArray); + .mockImplementation(object => object === sourceObject.nestedArray); - const copy = deepCyclicCopy(sourceobject, {keepPrototype: false}); + const copy = deepCyclicCopy(sourceObject, {keepPrototype: false}); expect(Object.getPrototypeOf(copy)).not.toBe( - Object.getPrototypeOf(sourceobject), + Object.getPrototypeOf(sourceObject), ); expect(Object.getPrototypeOf(copy.nestedObject)).not.toBe( - Object.getPrototypeOf(sourceobject.nestedObject), + Object.getPrototypeOf(sourceObject.nestedObject), ); expect(Object.getPrototypeOf(copy.nestedArray)).not.toBe( - Object.getPrototypeOf(sourceobject.nestedArray), + Object.getPrototypeOf(sourceObject.nestedArray), ); expect(Object.getPrototypeOf(copy)).toBe(Object.getPrototypeOf({})); expect(Object.getPrototypeOf(copy.nestedObject)).toBe( @@ -206,21 +209,21 @@ it('does not keep the prototype for objects when keepPrototype = false', () => { Object.getPrototypeOf([]), ); - spy.mockRestore(); + spyArray.mockRestore(); }); it('keeps the prototype for objects when keepPrototype = true', () => { - // @ts-expect-error + // @ts-expect-error: Testing purpose const sourceObject = new (function () {})(); - // @ts-expect-error + // @ts-expect-error: Testing purpose sourceObject.nestedObject = new (function () {})(); - // @ts-expect-error + // @ts-expect-error: Testing purpose sourceObject.nestedArray = new (function () { - // @ts-expect-error + // @ts-expect-error: Testing purpose this.length = 0; })(); - const spy = jest + const spyArray = jest .spyOn(Array, 'isArray') .mockImplementation(object => object === sourceObject.nestedArray); @@ -233,5 +236,5 @@ it('keeps the prototype for objects when keepPrototype = true', () => { expect(Object.getPrototypeOf(copy.nestedArray)).toBe( Object.getPrototypeOf(sourceObject.nestedArray), ); - spy.mockRestore(); + spyArray.mockRestore(); }); diff --git a/packages/jest-util/src/__tests__/globsToMatcher.test.ts b/packages/jest-util/src/__tests__/globsToMatcher.test.ts index 272996da2d31..207c0d0cf27a 100644 --- a/packages/jest-util/src/__tests__/globsToMatcher.test.ts +++ b/packages/jest-util/src/__tests__/globsToMatcher.test.ts @@ -59,7 +59,7 @@ it('works like micromatch with only negative globs', () => { }); it('works like micromatch with empty globs', () => { - const globs = []; + const globs: Array = []; const matcher = globsToMatcher(globs); expect(matcher('some-module.js')).toBe( diff --git a/packages/jest-util/src/__tests__/installCommonGlobals.test.ts b/packages/jest-util/src/__tests__/installCommonGlobals.test.ts index 0157e578c700..40f787feb905 100644 --- a/packages/jest-util/src/__tests__/installCommonGlobals.test.ts +++ b/packages/jest-util/src/__tests__/installCommonGlobals.test.ts @@ -17,11 +17,13 @@ globalThis.DTRACE_NET_SERVER_CONNECTION = fake; let installCommonGlobals: typeof import('../installCommonGlobals').default; function getGlobal(): typeof globalThis { - return runInContext('this', createContext()); + return runInContext('this', createContext()) as typeof globalThis; } beforeEach(() => { - installCommonGlobals = require('../installCommonGlobals').default; + installCommonGlobals = ( + require('../installCommonGlobals') as typeof import('../installCommonGlobals') + ).default; }); afterEach(() => { diff --git a/packages/jest-util/src/__tests__/isInteractive.test.ts b/packages/jest-util/src/__tests__/isInteractive.test.ts index ae3dd4e82276..beb88641aece 100644 --- a/packages/jest-util/src/__tests__/isInteractive.test.ts +++ b/packages/jest-util/src/__tests__/isInteractive.test.ts @@ -5,13 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -let oldIsTTY: typeof process.stdout.isTTY; -let oldTERM: string | undefined; +import * as process from 'process'; -beforeEach(() => { - oldIsTTY = process.stdout.isTTY; - oldTERM = process.env.TERM; -}); +const oldIsTTY = process.stdout.isTTY; +const oldTERM = process.env.TERM; afterEach(() => { process.stdout.isTTY = oldIsTTY; @@ -19,47 +16,34 @@ afterEach(() => { jest.resetModules(); }); -it('Returns true when running on interactive environment', () => { +it('Returns true when running in an interactive environment', () => { jest.doMock('ci-info', () => ({isCI: false})); process.stdout.isTTY = true; process.env.TERM = 'xterm-256color'; - const isInteractive = require('../isInteractive').default; + const isInteractive = ( + require('../isInteractive') as typeof import('../isInteractive') + ).default; + expect(isInteractive).toBe(true); }); -it('Returns false when running on a non-interactive environment', () => { - let isInteractive; - const expectedResult = false; - - // Test with isCI being true and isTTY false - jest.doMock('ci-info', () => ({isCI: true})); - process.stdout.isTTY = undefined; - process.env.TERM = 'xterm-256color'; - isInteractive = require('../isInteractive').default; - expect(isInteractive).toBe(expectedResult); - - // Test with isCI being false and isTTY false - jest.resetModules(); - jest.doMock('ci-info', () => ({isCI: false})); - process.stdout.isTTY = undefined; - process.env.TERM = 'xterm-256color'; - isInteractive = require('../isInteractive').default; - expect(isInteractive).toBe(expectedResult); - - // Test with isCI being true and isTTY true - jest.resetModules(); - jest.doMock('ci-info', () => ({isCI: true})); - process.stdout.isTTY = true; - process.env.TERM = 'xterm-256color'; - isInteractive = require('../isInteractive').default; - expect(isInteractive).toBe(expectedResult); - - // Test with dumb terminal - jest.resetModules(); - jest.doMock('ci-info', () => ({isCI: false})); - process.stdout.isTTY = undefined; - process.env.TERM = 'dumb'; - isInteractive = require('../isInteractive').default; - expect(isInteractive).toBe(expectedResult); -}); +it.each([ + {isCI: false, isTTY: false, term: 'xterm-256color'}, + {isCI: false, isTTY: false, term: 'xterm-256color'}, + {isCI: true, isTTY: true, term: 'xterm-256color'}, + {isCI: false, isTTY: false, term: 'dumb'}, +])( + 'Returns false when running in a non-interactive environment', + ({isCI, isTTY, term}) => { + jest.doMock('ci-info', () => ({isCI})); + process.stdout.isTTY = isTTY; + process.env.TERM = term; + + const isInteractive = ( + require('../isInteractive') as typeof import('../isInteractive') + ).default; + + expect(isInteractive).toBe(false); + }, +); diff --git a/packages/jest-validate/src/__tests__/fixtures/jestConfig.ts b/packages/jest-validate/src/__tests__/__fixtures__/jestConfig.ts similarity index 92% rename from packages/jest-validate/src/__tests__/fixtures/jestConfig.ts rename to packages/jest-validate/src/__tests__/__fixtures__/jestConfig.ts index 6cddb9fbf0f0..262dc9ef6efa 100644 --- a/packages/jest-validate/src/__tests__/fixtures/jestConfig.ts +++ b/packages/jest-validate/src/__tests__/__fixtures__/jestConfig.ts @@ -19,7 +19,7 @@ const replacePathSepForRegex = (string: string) => { const NODE_MODULES_REGEXP = replacePathSepForRegex(NODE_MODULES); -const defaultConfig = { +export const defaultConfig = { automock: false, bail: 0, cacheDirectory: path.join(tmpdir(), 'jest'), @@ -55,7 +55,7 @@ const defaultConfig = { watchPathIgnorePatterns: [], }; -const validConfig = { +export const validConfig = { automock: false, bail: 0, cache: true, @@ -121,10 +121,12 @@ const validConfig = { }; const format = (value: string) => - require('pretty-format').format(value, {min: true}); + (require('pretty-format') as typeof import('pretty-format')).format(value, { + min: true, + }); -const deprecatedConfig = { - preprocessorIgnorePatterns: (config: Record) => +export const deprecatedConfig = { + preprocessorIgnorePatterns: (config: {preprocessorIgnorePatterns: string}) => ` Option ${chalk.bold( 'preprocessorIgnorePatterns', )} was replaced by ${chalk.bold( @@ -140,7 +142,7 @@ const deprecatedConfig = { Please update your configuration.`, - scriptPreprocessor: (config: Record) => + scriptPreprocessor: (config: {scriptPreprocessor: string}) => ` Option ${chalk.bold('scriptPreprocessor')} was replaced by ${chalk.bold( 'transform', )}, which support multiple preprocessors. @@ -154,9 +156,3 @@ const deprecatedConfig = { Please update your configuration.`, }; - -module.exports = { - defaultConfig, - deprecatedConfig, - validConfig, -}; diff --git a/packages/jest-validate/src/__tests__/validate.test.ts b/packages/jest-validate/src/__tests__/validate.test.ts index 72daeea9f53d..86f89d9e2512 100644 --- a/packages/jest-validate/src/__tests__/validate.test.ts +++ b/packages/jest-validate/src/__tests__/validate.test.ts @@ -10,11 +10,11 @@ import {multipleValidOptions} from '../condition'; import jestValidateDefaultConfig from '../defaultConfig'; import jestValidateExampleConfig from '../exampleConfig'; import validate from '../validate'; -const { +import { defaultConfig, - validConfig, deprecatedConfig, -} = require('./fixtures/jestConfig'); + validConfig, +} from './__fixtures__/jestConfig'; test('recursively validates default Jest config', () => { expect( diff --git a/packages/jest-validate/src/validateCLIOptions.ts b/packages/jest-validate/src/validateCLIOptions.ts index c0f979bba9b7..4987f25d3692 100644 --- a/packages/jest-validate/src/validateCLIOptions.ts +++ b/packages/jest-validate/src/validateCLIOptions.ts @@ -63,14 +63,13 @@ const logDeprecatedOptions = ( export default function validateCLIOptions( argv: Config.Argv, - options: { - deprecationEntries: DeprecatedOptions; - [s: string]: Options; - }, + options: Record & { + deprecationEntries?: DeprecatedOptions; + } = {}, rawArgv: Array = [], ): boolean { const yargsSpecialOptions = ['$0', '_', 'help', 'h']; - const deprecationEntries = options.deprecationEntries || {}; + const deprecationEntries = options.deprecationEntries ?? {}; const allowedOptions = Object.keys(options).reduce( (acc, option) => acc.add(option).add((options[option].alias as string) || option), diff --git a/packages/jest-watcher/src/lib/Prompt.ts b/packages/jest-watcher/src/lib/Prompt.ts index ad23881af6ad..00dfeafee707 100644 --- a/packages/jest-watcher/src/lib/Prompt.ts +++ b/packages/jest-watcher/src/lib/Prompt.ts @@ -72,7 +72,7 @@ export default class Prompt { switch (key) { case KEYS.ENTER: this._entering = false; - this._onSuccess(this._selection || this._value); + this._onSuccess(this._selection ?? this._value); this.abort(); break; case KEYS.ESCAPE: diff --git a/packages/jest-watcher/src/lib/formatTestNameByPattern.ts b/packages/jest-watcher/src/lib/formatTestNameByPattern.ts index 7ee3334bc548..9c99bfe54e6d 100644 --- a/packages/jest-watcher/src/lib/formatTestNameByPattern.ts +++ b/packages/jest-watcher/src/lib/formatTestNameByPattern.ts @@ -32,7 +32,7 @@ export default function formatTestNameByPattern( return chalk.dim(inlineTestName); } - const startPatternIndex = Math.max(match.index || 0, 0); + const startPatternIndex = Math.max(match.index ?? 0, 0); const endPatternIndex = startPatternIndex + match[0].length; if (inlineTestName.length <= width) { diff --git a/packages/jest-worker/src/__tests__/index.test.ts b/packages/jest-worker/src/__tests__/index.test.ts index c7b1d3d97d0c..08bbfd39d6b6 100644 --- a/packages/jest-worker/src/__tests__/index.test.ts +++ b/packages/jest-worker/src/__tests__/index.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {JestWorkerFarm, Worker, WorkerFarmOptions} from '..'; +import type {JestWorkerFarm, Worker, WorkerFarmOptions} from '../'; import type FarmClass from '../Farm'; import type WorkerPoolClass from '../WorkerPool'; @@ -54,7 +54,7 @@ beforeEach(() => { virtual: true, }); - WorkerFarm = (require('../') as typeof import('..')).Worker; + WorkerFarm = (require('../') as typeof import('../')).Worker; Farm = (require('../Farm') as typeof import('../Farm')).default; WorkerPool = (require('../WorkerPool') as typeof import('../WorkerPool')) .default; @@ -85,19 +85,17 @@ it('exposes the right API using passed worker', () => { const WorkerPool = jest.fn(() => ({ createWorker: jest.fn(), end: jest.fn(), - getStderr: () => jest.fn(a => a), - getStdout: () => jest.fn(a => a), + getStderr: jest.fn(), + getStdout: jest.fn(), + getWorkers: jest.fn(), send: jest.fn(), - })) as unknown as typeof WorkerPoolClass; + })); const farm = new WorkerFarm('/tmp/baz.js', { WorkerPool, exposedMethods: ['foo', 'bar'], numWorkers: 4, - } as WorkerFarmOptions) as JestWorkerFarm<{ - foo(): void; - bar(): void; - }>; + } as WorkerFarmOptions) as JestWorkerFarm<{foo(): void; bar(): void}>; expect(typeof farm.foo).toBe('function'); expect(typeof farm.bar).toBe('function'); diff --git a/packages/jest-worker/src/__tests__/process-integration.test.js b/packages/jest-worker/src/__tests__/process-integration.test.ts similarity index 75% rename from packages/jest-worker/src/__tests__/process-integration.test.js rename to packages/jest-worker/src/__tests__/process-integration.test.ts index 2674ab21c27f..9d47cb7901d0 100644 --- a/packages/jest-worker/src/__tests__/process-integration.test.js +++ b/packages/jest-worker/src/__tests__/process-integration.test.ts @@ -5,38 +5,39 @@ * LICENSE file in the root directory of this source tree. */ -import EventEmitter from 'events'; +import {EventEmitter} from 'events'; +import type {JestWorkerFarm, Worker} from '../'; import { CHILD_MESSAGE_CALL, CHILD_MESSAGE_MEM_USAGE, PARENT_MESSAGE_OK, - WorkerFarmOptions, } from '../types'; -let Farm; -let mockForkedProcesses; - -function mockBuildForkedProcess() { - const mockChild = new EventEmitter(); - - mockChild.send = jest.fn(); - mockChild.connected = true; - - return mockChild; +class MockChildProcess extends EventEmitter { + connected = true; + send = jest.fn<(message: unknown) => boolean>(); } -function replySuccess(i, result) { +let WorkerFarm: typeof Worker; +let mockForkedProcesses: Array; + +function replySuccess(i: number, result: unknown) { mockForkedProcesses[i].emit('message', [PARENT_MESSAGE_OK, result]); } -function assertCallsToChild(childNum, ...calls) { +function assertCallsToChild( + childNum: number, + ...calls: Array<[unknown, ...[unknown]]> +) { expect(mockForkedProcesses[childNum].send).toHaveBeenCalledTimes( calls.length + 1, ); calls.forEach(([methodName, ...args], numCall) => { expect( - mockForkedProcesses[childNum].send.mock.calls[numCall + 1][0], + jest.mocked(mockForkedProcesses[childNum].send).mock.calls[ + numCall + 1 + ][0], ).toEqual([CHILD_MESSAGE_CALL, true, methodName, args]); }); } @@ -47,15 +48,14 @@ describe('Jest Worker Integration', () => { jest.mock('child_process', () => ({ fork() { - const forkedProcess = mockBuildForkedProcess(); - + const forkedProcess = new MockChildProcess(); mockForkedProcesses.push(forkedProcess); return forkedProcess; }, })); - Farm = require('../index').Worker; + WorkerFarm = (require('../') as typeof import('../')).Worker; }); afterEach(() => { @@ -63,10 +63,10 @@ describe('Jest Worker Integration', () => { }); it('calls a single method from the worker', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(): void}>; const promise = farm.foo(); @@ -76,10 +76,10 @@ describe('Jest Worker Integration', () => { }); it('distributes sequential calls across child processes', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // The first call will go to the first child process. const promise0 = farm.foo('param-0'); @@ -95,11 +95,11 @@ describe('Jest Worker Integration', () => { }); it('schedules the task on the first available child processes if the scheduling policy is in-order', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { exposedMethods: ['foo', 'bar'], numWorkers: 4, workerSchedulingPolicy: 'in-order', - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // The first call will go to the first child process. const promise0 = farm.foo('param-0'); @@ -108,16 +108,16 @@ describe('Jest Worker Integration', () => { // The second call will go to the second child process. const promise1 = farm.foo(1); - // The first task on worker 0 completes + // The first task on worker 0 completes. replySuccess(0, 'worker-0'); expect(await promise0).toBe('worker-0'); - // The second task on worker 1 completes + // The second task on worker 1 completes. assertCallsToChild(1, ['foo', 1]); replySuccess(1, 'worker-1'); expect(await promise1).toBe('worker-1'); - // The third call will go to the first child process + // The third call will go to the first child process. const promise2 = farm.foo('param-2'); assertCallsToChild(0, ['foo', 'param-0'], ['foo', 'param-2']); replySuccess(0, 'worker-0'); @@ -125,10 +125,10 @@ describe('Jest Worker Integration', () => { }); it('distributes concurrent calls across child processes', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // Do 3 calls to the farm in parallel. const promise0 = farm.foo('param-0'); @@ -152,11 +152,11 @@ describe('Jest Worker Integration', () => { }); it('sticks parallel calls to children', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { computeWorkerKey: () => '1234567890abcdef', exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // Do 3 calls to the farm in parallel. const promise0 = farm.foo('param-0'); @@ -168,7 +168,7 @@ describe('Jest Worker Integration', () => { replySuccess(0, 'worker-1'); replySuccess(0, 'worker-2'); - // Check that all the calls have been received by the same child). + // Check that all the calls have been received by the same child. assertCallsToChild( 0, ['foo', 'param-0'], @@ -183,33 +183,29 @@ describe('Jest Worker Integration', () => { }); it('should check for memory limits', async () => { - /** @type WorkerFarmOptions */ - const options = { - computeWorkerKey: () => '1234567890abcdef', + const farm = new WorkerFarm('/tmp/baz.js', { exposedMethods: ['foo', 'bar'], idleMemoryLimit: 0.4, numWorkers: 2, - }; - - const farm = new Farm('/tmp/baz.js', options); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // Send a call to the farm - const promise0 = farm.foo('param-0'); + farm.foo('param-0'); // Send different responses for each call (from the same child). replySuccess(0, 'worker-0'); - // Check that all the calls have been received by the same child). + // Check that all the calls have been received by the same child. // We're not using the assertCallsToChild helper because we need to check // for other send types. expect(mockForkedProcesses[0].send).toHaveBeenCalledTimes(3); - expect(mockForkedProcesses[0].send.mock.calls[1][0]).toEqual([ + expect(jest.mocked(mockForkedProcesses[0].send).mock.calls[1][0]).toEqual([ CHILD_MESSAGE_CALL, true, 'foo', ['param-0'], ]); - expect(mockForkedProcesses[0].send.mock.calls[2][0]).toEqual([ + expect(jest.mocked(mockForkedProcesses[0].send).mock.calls[2][0]).toEqual([ CHILD_MESSAGE_MEM_USAGE, ]); }); diff --git a/packages/jest-worker/src/__tests__/thread-integration.test.js b/packages/jest-worker/src/__tests__/thread-integration.test.ts similarity index 77% rename from packages/jest-worker/src/__tests__/thread-integration.test.js rename to packages/jest-worker/src/__tests__/thread-integration.test.ts index 537dcb2d1762..432c2936d54f 100644 --- a/packages/jest-worker/src/__tests__/thread-integration.test.js +++ b/packages/jest-worker/src/__tests__/thread-integration.test.ts @@ -5,34 +5,31 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -import EventEmitter from 'events'; +import {EventEmitter} from 'events'; +import type {Worker as ThreadWorker} from 'worker_threads'; +import type {JestWorkerFarm, Worker} from '../'; import {CHILD_MESSAGE_CALL, PARENT_MESSAGE_OK} from '../types'; -let Farm; -let mockForkedProcesses; - -function mockBuildForkedProcess() { - const mockChild = new EventEmitter(); - - mockChild.postMessage = jest.fn(); +let WorkerFarm: typeof Worker; +let mockForkedProcesses: Array; - return mockChild; -} - -function replySuccess(i, result) { +function replySuccess(i: number, result: unknown) { mockForkedProcesses[i].emit('message', [PARENT_MESSAGE_OK, result]); } -function assertCallsToChild(childNum, ...calls) { +function assertCallsToChild( + childNum: number, + ...calls: Array<[unknown, ...[unknown]]> +) { expect(mockForkedProcesses[childNum].postMessage).toHaveBeenCalledTimes( calls.length + 1, ); calls.forEach(([methodName, ...args], numCall) => { expect( - mockForkedProcesses[childNum].postMessage.mock.calls[numCall + 1][0], + jest.mocked(mockForkedProcesses[childNum].postMessage).mock.calls[ + numCall + 1 + ][0], ).toEqual([CHILD_MESSAGE_CALL, true, methodName, args]); }); } @@ -41,9 +38,14 @@ describe('Jest Worker Process Integration', () => { beforeEach(() => { mockForkedProcesses = []; + class MockForkedProcess extends EventEmitter { + postMessage = jest.fn(); + } + jest.mock('worker_threads', () => { const fakeClass = jest.fn(() => { - const forkedProcess = mockBuildForkedProcess(); + const forkedProcess = + new MockForkedProcess() as unknown as ThreadWorker; mockForkedProcesses.push(forkedProcess); @@ -56,7 +58,7 @@ describe('Jest Worker Process Integration', () => { }; }); - Farm = require('../index').Worker; + WorkerFarm = (require('../') as typeof import('../')).Worker; }); afterEach(() => { @@ -64,11 +66,11 @@ describe('Jest Worker Process Integration', () => { }); it('calls a single method from the worker', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { enableWorkerThreads: true, exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(): void}>; const promise = farm.foo(); @@ -78,11 +80,11 @@ describe('Jest Worker Process Integration', () => { }); it('distributes sequential calls across child processes', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { enableWorkerThreads: true, exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // The first call will go to the first child process. const promise0 = farm.foo('param-0'); @@ -98,12 +100,12 @@ describe('Jest Worker Process Integration', () => { }); it('schedules the task on the first available child processes if the scheduling policy is in-order', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { enableWorkerThreads: true, exposedMethods: ['foo', 'bar'], numWorkers: 4, workerSchedulingPolicy: 'in-order', - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // The first call will go to the first child process. const promise0 = farm.foo('param-0'); @@ -112,16 +114,16 @@ describe('Jest Worker Process Integration', () => { // The second call will go to the second child process. const promise1 = farm.foo(1); - // The first task on worker 0 completes + // The first task on worker 0 completes. replySuccess(0, 'worker-0'); expect(await promise0).toBe('worker-0'); - // The second task on worker 1 completes + // The second task on worker 1 completes. assertCallsToChild(1, ['foo', 1]); replySuccess(1, 'worker-1'); expect(await promise1).toBe('worker-1'); - // The third call will go to the first child process + // The third call will go to the first child process. const promise2 = farm.foo('param-2'); assertCallsToChild(0, ['foo', 'param-0'], ['foo', 'param-2']); replySuccess(0, 'worker-0'); @@ -129,11 +131,11 @@ describe('Jest Worker Process Integration', () => { }); it('schedules the task on the first available child processes', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { enableWorkerThreads: true, exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // The first call will go to the first child process. const promise0 = farm.foo('param-0'); @@ -149,11 +151,11 @@ describe('Jest Worker Process Integration', () => { }); it('distributes concurrent calls across child processes', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { enableWorkerThreads: true, exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // Do 3 calls to the farm in parallel. const promise0 = farm.foo('param-0'); @@ -177,12 +179,12 @@ describe('Jest Worker Process Integration', () => { }); it('sticks parallel calls to children', async () => { - const farm = new Farm('/tmp/baz.js', { + const farm = new WorkerFarm('/tmp/baz.js', { computeWorkerKey: () => '1234567890abcdef', enableWorkerThreads: true, exposedMethods: ['foo', 'bar'], numWorkers: 4, - }); + }) as JestWorkerFarm<{foo(a: unknown): void}>; // Do 3 calls to the farm in parallel. const promise0 = farm.foo('param-0'); @@ -194,7 +196,7 @@ describe('Jest Worker Process Integration', () => { replySuccess(0, 'worker-1'); replySuccess(0, 'worker-2'); - // Check that all the calls have been received by the same child). + // Check that all the calls have been received by the same child. assertCallsToChild( 0, ['foo', 'param-0'], diff --git a/packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.js b/packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.ts similarity index 75% rename from packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.js rename to packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.ts index 4159587e037e..9e6b513cb22f 100644 --- a/packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.js +++ b/packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.ts @@ -5,14 +5,16 @@ * LICENSE file in the root directory of this source tree. */ -import EventEmitter from 'events'; +import {EventEmitter} from 'events'; import {PassThrough} from 'stream'; -import getStream from 'get-stream'; -import supportsColor from 'supports-color'; +import getStream = require('get-stream'); +import * as supportsColor from 'supports-color'; import { CHILD_MESSAGE_CALL, CHILD_MESSAGE_INITIALIZE, CHILD_MESSAGE_MEM_USAGE, + ChildMessage, + ChildMessageCall, PARENT_MESSAGE_CLIENT_ERROR, PARENT_MESSAGE_CUSTOM, PARENT_MESSAGE_MEM_USAGE, @@ -22,38 +24,40 @@ import { jest.useFakeTimers(); -let Worker; -let forkInterface; -let childProcess; -let originalExecArgv; -let totalmem; +jest.mock('child_process'); -beforeAll(() => { - const os = require('os'); - totalmem = jest.spyOn(os, 'totalmem'); -}); +let Worker: typeof import('../ChildProcessWorker').default; +let childProcess: typeof import('child_process'); +let forkInterface: ReturnType; +let originalExecArgv: typeof process.execArgv; -beforeEach(() => { - jest.mock('child_process'); +const totalmem = jest.spyOn(require('os') as typeof import('os'), 'totalmem'); +class MockedForkInterface extends EventEmitter { + connected = true; + kill = jest.fn(); + send = jest.fn(); + stderr = new PassThrough(); + stdout = new PassThrough(); +} + +beforeEach(() => { originalExecArgv = process.execArgv; - childProcess = require('child_process'); - childProcess.fork.mockImplementation(() => { - forkInterface = Object.assign(new EventEmitter(), { - connected: true, - kill: jest.fn(), - send: jest.fn(), - stderr: new PassThrough(), - stdout: new PassThrough(), - }); + childProcess = require('child_process') as typeof import('child_process'); + jest.mocked(childProcess.fork).mockImplementation(() => { + forkInterface = new MockedForkInterface() as unknown as ReturnType< + typeof childProcess.fork + >; return forkInterface; }); totalmem.mockReset(); - Worker = require('../ChildProcessWorker').default; + Worker = ( + require('../ChildProcessWorker') as typeof import('../ChildProcessWorker') + ).default; }); afterEach(() => { @@ -73,12 +77,12 @@ it('passes fork options down to child_process.fork, adding the defaults', () => execPath: 'hello', }, maxRetries: 3, - workerId: process.env.JEST_WORKER_ID - 1, + workerId: Number(process.env.JEST_WORKER_ID) - 1, workerPath: '/tmp/foo/bar/baz.js', - }); + } as WorkerOptions); - expect(childProcess.fork.mock.calls[0][0]).toBe(child); - expect(childProcess.fork.mock.calls[0][2]).toEqual({ + expect(jest.mocked(childProcess.fork).mock.calls[0][0]).toBe(child); + expect(jest.mocked(childProcess.fork).mock.calls[0][2]).toEqual({ cwd: '/tmp', // Overridden default option. env: {...process.env, FORCE_COLOR: supportsColor.stdout ? '1' : undefined}, // Default option. execArgv: ['-p'], // Filtered option. @@ -95,9 +99,11 @@ it('passes workerId to the child process and assign it to 1-indexed env.JEST_WOR maxRetries: 3, workerId: 2, workerPath: '/tmp/foo', - }); + } as WorkerOptions); - expect(childProcess.fork.mock.calls[0][2].env.JEST_WORKER_ID).toBe('3'); + expect(jest.mocked(childProcess.fork).mock.calls[0][2]).toMatchObject({ + env: {JEST_WORKER_ID: '3'}, + }); }); it('initializes the child process with the given workerPath', () => { @@ -107,9 +113,9 @@ it('initializes the child process with the given workerPath', () => { maxRetries: 3, setupArgs: ['foo', 'bar'], workerPath: '/tmp/foo/bar/baz.js', - }); + } as WorkerOptions); - expect(forkInterface.send.mock.calls[0][0]).toEqual([ + expect(jest.mocked(forkInterface.send).mock.calls[0][0]).toEqual([ CHILD_MESSAGE_INITIALIZE, false, '/tmp/foo/bar/baz.js', @@ -122,13 +128,13 @@ it('stops initializing the worker after the amount of retries is exceeded', () = forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo/bar/baz.js', - }); + } as WorkerOptions); - const request = [CHILD_MESSAGE_CALL, false, 'foo', []]; + const request: ChildMessageCall = [CHILD_MESSAGE_CALL, false, 'foo', []]; const onProcessStart = jest.fn(); const onProcessEnd = jest.fn(); - worker.send(request, onProcessStart, onProcessEnd); + worker.send(request, onProcessStart, onProcessEnd, () => {}); // We fail four times (initial + three retries). forkInterface.emit('exit', 1); @@ -140,7 +146,9 @@ it('stops initializing the worker after the amount of retries is exceeded', () = expect(onProcessStart).toHaveBeenCalledWith(worker); expect(onProcessEnd).toHaveBeenCalledTimes(1); expect(onProcessEnd.mock.calls[0][0]).toBeInstanceOf(Error); - expect(onProcessEnd.mock.calls[0][0].type).toBe('WorkerError'); + expect(onProcessEnd.mock.calls[0][0]).toMatchObject({ + type: 'WorkerError', + }); expect(onProcessEnd.mock.calls[0][1]).toBeNull(); }); @@ -149,16 +157,16 @@ it('provides stdout and stderr from the child processes', async () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); - const stdout = worker.getStdout(); - const stderr = worker.getStderr(); + const stdout = worker.getStdout() as NodeJS.ReadableStream; + const stderr = worker.getStderr() as NodeJS.ReadableStream; - forkInterface.stdout.end('Hello ', 'utf8'); - forkInterface.stderr.end('Jest ', 'utf8'); + (forkInterface.stdout as PassThrough).end('Hello ', 'utf8'); + (forkInterface.stderr as PassThrough).end('Jest ', 'utf8'); forkInterface.emit('exit', 1); - forkInterface.stdout.end('World!', 'utf8'); - forkInterface.stderr.end('Workers!', 'utf8'); + (forkInterface.stdout as PassThrough).end('World!', 'utf8'); + (forkInterface.stderr as PassThrough).end('Workers!', 'utf8'); forkInterface.emit('exit', 0); await expect(getStream(stdout)).resolves.toBe('Hello World!'); @@ -171,18 +179,19 @@ it('sends the task to the child process', () => { maxRetries: 3, setupArgs: [], workerPath: '/tmp/foo', - }); + } as unknown as WorkerOptions); - const request = [CHILD_MESSAGE_CALL, false, 'foo', []]; + const request: ChildMessage = [CHILD_MESSAGE_CALL, false, 'foo', []]; worker.send( request, () => {}, () => {}, + () => {}, ); // Skipping call "0" because it corresponds to the "initialize" one. - expect(forkInterface.send.mock.calls[1][0]).toEqual(request); + expect(jest.mocked(forkInterface.send).mock.calls[1][0]).toEqual(request); }); it('resends the task to the child process after a retry', () => { @@ -190,18 +199,19 @@ it('resends the task to the child process after a retry', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo/bar/baz.js', - }); + } as WorkerOptions); - const request = [CHILD_MESSAGE_CALL, false, 'foo', []]; + const request: ChildMessage = [CHILD_MESSAGE_CALL, false, 'foo', []]; worker.send( request, () => {}, () => {}, + () => {}, ); // Skipping call "0" because it corresponds to the "initialize" one. - expect(forkInterface.send.mock.calls[1][0]).toEqual(request); + expect(jest.mocked(forkInterface.send).mock.calls[1][0]).toEqual(request); const previousForkInterface = forkInterface; forkInterface.emit('exit', 1); @@ -209,7 +219,7 @@ it('resends the task to the child process after a retry', () => { expect(forkInterface).not.toBe(previousForkInterface); // Skipping call "0" because it corresponds to the "initialize" one. - expect(forkInterface.send.mock.calls[1][0]).toEqual(request); + expect(jest.mocked(forkInterface.send).mock.calls[1][0]).toEqual(request); }); it('calls the onProcessStart method synchronously if the queue is empty', () => { @@ -217,7 +227,7 @@ it('calls the onProcessStart method synchronously if the queue is empty', () => forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); const onProcessStart = jest.fn(); const onProcessEnd = jest.fn(); @@ -226,6 +236,7 @@ it('calls the onProcessStart method synchronously if the queue is empty', () => [CHILD_MESSAGE_CALL, false, 'foo', []], onProcessStart, onProcessEnd, + () => {}, ); // Only onProcessStart has been called @@ -243,7 +254,7 @@ it('can send multiple messages to parent', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); const onProcessStart = jest.fn(); const onProcessEnd = jest.fn(); @@ -280,14 +291,19 @@ it('creates error instances for known errors', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); const callback1 = jest.fn(); const callback2 = jest.fn(); const callback3 = jest.fn(); // Testing a generic ECMAScript error. - worker.send([CHILD_MESSAGE_CALL, false, 'method', []], () => {}, callback1); + worker.send( + [CHILD_MESSAGE_CALL, false, 'method', []], + () => {}, + callback1, + () => {}, + ); forkInterface.emit('message', [ PARENT_MESSAGE_CLIENT_ERROR, @@ -298,12 +314,19 @@ it('creates error instances for known errors', () => { ]); expect(callback1.mock.calls[0][0]).toBeInstanceOf(TypeError); - expect(callback1.mock.calls[0][0].message).toBe('bar'); - expect(callback1.mock.calls[0][0].type).toBe('TypeError'); - expect(callback1.mock.calls[0][0].stack).toBe('TypeError: bar'); + expect(callback1.mock.calls[0][0]).toMatchObject({ + message: 'bar', + stack: 'TypeError: bar', + type: 'TypeError', + }); // Testing a custom error. - worker.send([CHILD_MESSAGE_CALL, false, 'method', []], () => {}, callback2); + worker.send( + [CHILD_MESSAGE_CALL, false, 'method', []], + () => {}, + callback2, + () => {}, + ); forkInterface.emit('message', [ PARENT_MESSAGE_CLIENT_ERROR, @@ -314,13 +337,20 @@ it('creates error instances for known errors', () => { ]); expect(callback2.mock.calls[0][0]).toBeInstanceOf(Error); - expect(callback2.mock.calls[0][0].message).toBe('bar'); - expect(callback2.mock.calls[0][0].type).toBe('RandomCustomError'); - expect(callback2.mock.calls[0][0].stack).toBe('RandomCustomError: bar'); - expect(callback2.mock.calls[0][0].qux).toBe('extra property'); + expect(callback2.mock.calls[0][0]).toMatchObject({ + message: 'bar', + qux: 'extra property', + stack: 'RandomCustomError: bar', + type: 'RandomCustomError', + }); // Testing a non-object throw. - worker.send([CHILD_MESSAGE_CALL, false, 'method', []], () => {}, callback3); + worker.send( + [CHILD_MESSAGE_CALL, false, 'method', []], + () => {}, + callback3, + () => {}, + ); forkInterface.emit('message', [ PARENT_MESSAGE_CLIENT_ERROR, @@ -338,12 +368,13 @@ it('throws when the child process returns a strange message', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); worker.send( [CHILD_MESSAGE_CALL, false, 'method', []], () => {}, () => {}, + () => {}, ); // Type 27 does not exist. @@ -358,7 +389,7 @@ it('does not restart the child if it cleanly exited', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); expect(childProcess.fork).toHaveBeenCalledTimes(1); forkInterface.emit('exit', 0); @@ -370,7 +401,7 @@ it('resolves waitForExit() after the child process cleanly exited', async () => forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); expect(childProcess.fork).toHaveBeenCalledTimes(1); forkInterface.emit('exit', 0); @@ -381,7 +412,7 @@ it('restarts the child when the child process dies', () => { // eslint-disable-next-line no-new new Worker({ workerPath: '/tmp/foo', - }); + } as WorkerOptions); expect(childProcess.fork).toHaveBeenCalledTimes(1); forkInterface.emit('exit', 1); @@ -391,7 +422,7 @@ it('restarts the child when the child process dies', () => { it('when out of memory occurs the worker is killed and exits', async () => { const worker = new Worker({ workerPath: '/tmp/foo', - }); + } as WorkerOptions); expect(childProcess.fork).toHaveBeenCalledTimes(1); @@ -412,7 +443,7 @@ it('when out of memory occurs the worker is killed and exits', async () => { expect(onCustomMessage).not.toHaveBeenCalled(); // Splitting the emit into 2 to check concat is happening. - forkInterface.stderr.emit( + forkInterface.stderr!.emit( 'data', `<--- Last few GCs ---> @@ -424,13 +455,13 @@ it('when out of memory occurs the worker is killed and exits', async () => { FATAL ERROR: Reached heap limit Allocation failed - JavaScript he`, ); - forkInterface.stderr.emit( + forkInterface.stderr!.emit( 'data', `ap out of memory 1: 0x10da153a5 node::Abort() (.cold.1) [/Users/paul/.nvm/versions/node/v16.10.0/bin/node] 2: 0x10c6f09b9 node::Abort() [/Users/paul/.nvm/versions/node/v16.10.0/bin/node]`, ); - forkInterface.stderr.emit('end'); + forkInterface.stderr!.emit('end'); forkInterface.emit('exit', null, 'SIGABRT'); @@ -451,10 +482,10 @@ it('sends SIGTERM when forceExit() is called', async () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); worker.forceExit(); - expect(forkInterface.kill.mock.calls).toEqual([['SIGTERM']]); + expect(jest.mocked(forkInterface.kill).mock.calls).toEqual([['SIGTERM']]); }); it('sends SIGKILL some time after SIGTERM', async () => { @@ -462,11 +493,14 @@ it('sends SIGKILL some time after SIGTERM', async () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); worker.forceExit(); jest.runAllTimers(); - expect(forkInterface.kill.mock.calls).toEqual([['SIGTERM'], ['SIGKILL']]); + expect(jest.mocked(forkInterface.kill).mock.calls).toEqual([ + ['SIGTERM'], + ['SIGKILL'], + ]); }); it('does not send SIGKILL if SIGTERM exited the process', async () => { @@ -474,14 +508,14 @@ it('does not send SIGKILL if SIGTERM exited the process', async () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); worker.forceExit(); forkInterface.emit('exit', 143 /* SIGTERM exit code */); await Promise.resolve(); jest.runAllTimers(); - expect(forkInterface.kill.mock.calls).toEqual([['SIGTERM']]); + expect(jest.mocked(forkInterface.kill).mock.calls).toEqual([['SIGTERM']]); }); it('should check for memory limits and not restart if under percentage limit', async () => { @@ -491,14 +525,12 @@ it('should check for memory limits and not restart if under percentage limit', a totalMem: 16000, }; - /** @type WorkerOptions */ - const options = { + const worker = new Worker({ forkOptions: {}, idleMemoryLimit: memoryConfig.limit, maxRetries: 3, workerPath: '/tmp/foo', - }; - const worker = new Worker(options); + } as WorkerOptions); const onProcessStart = jest.fn(); const onProcessEnd = jest.fn(); @@ -521,8 +553,8 @@ it('should check for memory limits and not restart if under percentage limit', a expect(onProcessEnd).toHaveBeenCalledTimes(1); - // This is the initalization call. - expect(forkInterface.send.mock.calls[0][0]).toEqual([ + // This is the initialization call. + expect(jest.mocked(forkInterface.send).mock.calls[0][0]).toEqual([ CHILD_MESSAGE_INITIALIZE, false, '/tmp/foo', @@ -530,7 +562,7 @@ it('should check for memory limits and not restart if under percentage limit', a ]); // This is the child message - expect(forkInterface.send.mock.calls[1][0]).toEqual([ + expect(jest.mocked(forkInterface.send).mock.calls[1][0]).toEqual([ CHILD_MESSAGE_CALL, false, 'foo', @@ -538,7 +570,7 @@ it('should check for memory limits and not restart if under percentage limit', a ]); // This is the subsequent call to get memory usage - expect(forkInterface.send.mock.calls[2][0]).toEqual([ + expect(jest.mocked(forkInterface.send).mock.calls[2][0]).toEqual([ CHILD_MESSAGE_MEM_USAGE, ]); @@ -560,14 +592,12 @@ it('should check for memory limits and not restart if under absolute limit', asy totalMem: 16000, }; - /** @type WorkerOptions */ - const options = { + const worker = new Worker({ forkOptions: {}, idleMemoryLimit: memoryConfig.limit, maxRetries: 3, workerPath: '/tmp/foo', - }; - const worker = new Worker(options); + } as WorkerOptions); worker.checkMemoryUsage(); @@ -589,14 +619,12 @@ it('should check for memory limits and restart if above percentage limit', async totalMem: 16000, }; - /** @type WorkerOptions */ - const options = { + const worker = new Worker({ forkOptions: {}, idleMemoryLimit: memoryConfig.limit, maxRetries: 3, workerPath: '/tmp/foo', - }; - const worker = new Worker(options); + } as WorkerOptions); worker.checkMemoryUsage(); @@ -618,14 +646,12 @@ it('should check for memory limits and restart if above absolute limit', async ( totalMem: 16000, }; - /** @type WorkerOptions */ - const options = { + const worker = new Worker({ forkOptions: {}, idleMemoryLimit: memoryConfig.limit, maxRetries: 3, workerPath: '/tmp/foo', - }; - const worker = new Worker(options); + } as WorkerOptions); worker.checkMemoryUsage(); diff --git a/packages/jest-worker/src/workers/__tests__/NodeThreadsWorker.test.js b/packages/jest-worker/src/workers/__tests__/NodeThreadsWorker.test.ts similarity index 65% rename from packages/jest-worker/src/workers/__tests__/NodeThreadsWorker.test.js rename to packages/jest-worker/src/workers/__tests__/NodeThreadsWorker.test.ts index 5ae5a814c2bd..8d633af24e97 100644 --- a/packages/jest-worker/src/workers/__tests__/NodeThreadsWorker.test.js +++ b/packages/jest-worker/src/workers/__tests__/NodeThreadsWorker.test.ts @@ -5,45 +5,44 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; - -import getStream from 'get-stream'; +import {EventEmitter} from 'events'; +import {PassThrough} from 'stream'; +import getStream = require('get-stream'); import { CHILD_MESSAGE_CALL, CHILD_MESSAGE_INITIALIZE, + ChildMessageCall, PARENT_MESSAGE_CLIENT_ERROR, PARENT_MESSAGE_CUSTOM, PARENT_MESSAGE_OK, + WorkerOptions, } from '../../types'; -let Worker; -let workerThreads; -let originalExecArgv; +let Worker: typeof import('../NodeThreadsWorker').default; +let workerThreads: typeof import('worker_threads').Worker; +let originalExecArgv: typeof process.execArgv; + +class MockedWorker extends EventEmitter { + postMessage = jest.fn(); + terminate = jest.fn(); + stdout = new PassThrough(); + stderr = new PassThrough(); +} beforeEach(() => { jest.mock('worker_threads', () => { - const fakeClass = jest.fn(() => { - const EventEmitter = require('events'); - const {PassThrough} = require('stream'); - - const thread = new EventEmitter(); - thread.postMessage = jest.fn(); - thread.terminate = jest.fn(); - thread.stdout = new PassThrough(); - thread.stderr = new PassThrough(); - return thread; - }); - return { - Worker: fakeClass, + Worker: jest.fn(() => new MockedWorker()), }; }); originalExecArgv = process.execArgv; - workerThreads = require('worker_threads').Worker; - workerThreads.postMessage = jest.fn(); + workerThreads = (require('worker_threads') as typeof import('worker_threads')) + .Worker; - Worker = require('../NodeThreadsWorker').default; + Worker = ( + require('../NodeThreadsWorker') as typeof import('../NodeThreadsWorker') + ).default; }); afterEach(() => { @@ -64,12 +63,12 @@ it('passes fork options down to worker_threads.Worker, adding the defaults', () workerData: { foo: 'bar', }, - workerId: process.env.JEST_WORKER_ID - 1, + workerId: Number(process.env.JEST_WORKER_ID) - 1, workerPath: '/tmp/foo/bar/baz.js', - }); + } as WorkerOptions); - expect(workerThreads.mock.calls[0][0]).toBe(thread); - expect(workerThreads.mock.calls[0][1]).toEqual({ + expect(jest.mocked(workerThreads).mock.calls[0][0]).toBe(thread); + expect(jest.mocked(workerThreads).mock.calls[0][1]).toEqual({ eval: false, execArgv: ['--inspect', '-p'], execPath: 'hello', // Added option. @@ -90,9 +89,10 @@ it('initializes the thread with the given workerPath and workerId', () => { setupArgs: ['foo', 'bar'], workerId: 2, workerPath: '/tmp/foo/bar/baz.js', - }); + } as WorkerOptions); - expect(worker._worker.postMessage.mock.calls[0][0]).toEqual([ + // @ts-expect-error: Testing internal method + expect(jest.mocked(worker._worker.postMessage).mock.calls[0][0]).toEqual([ CHILD_MESSAGE_INITIALIZE, false, '/tmp/foo/bar/baz.js', @@ -106,25 +106,29 @@ it('stops initializing the worker after the amount of retries is exceeded', () = forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo/bar/baz.js', - }); + } as WorkerOptions); - const request = [CHILD_MESSAGE_CALL, false, 'foo', []]; + const request = [CHILD_MESSAGE_CALL, false, 'foo', []] as ChildMessageCall; const onProcessStart = jest.fn(); const onProcessEnd = jest.fn(); - worker.send(request, onProcessStart, onProcessEnd); + worker.send(request, onProcessStart, onProcessEnd, () => {}); // We fail four times (initial + three retries). + // @ts-expect-error: Testing internal method worker._worker.emit('exit'); + // @ts-expect-error: Testing internal method worker._worker.emit('exit'); + // @ts-expect-error: Testing internal method worker._worker.emit('exit'); + // @ts-expect-error: Testing internal method worker._worker.emit('exit'); expect(workerThreads).toHaveBeenCalledTimes(5); expect(onProcessStart).toHaveBeenCalledWith(worker); expect(onProcessEnd).toHaveBeenCalledTimes(1); expect(onProcessEnd.mock.calls[0][0]).toBeInstanceOf(Error); - expect(onProcessEnd.mock.calls[0][0].type).toBe('WorkerError'); + expect(onProcessEnd.mock.calls[0][0]).toMatchObject({type: 'WorkerError'}); expect(onProcessEnd.mock.calls[0][1]).toBeNull(); }); @@ -133,20 +137,26 @@ it('provides stdout and stderr from the threads', async () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); const stdout = worker.getStdout(); const stderr = worker.getStderr(); + // @ts-expect-error: Testing internal method worker._worker.stdout.end('Hello ', 'utf8'); + // @ts-expect-error: Testing internal method worker._worker.stderr.end('Jest ', 'utf8'); + // @ts-expect-error: Testing internal method worker._worker.emit('exit'); + // @ts-expect-error: Testing internal method worker._worker.stdout.end('World!', 'utf8'); + // @ts-expect-error: Testing internal method worker._worker.stderr.end('Workers!', 'utf8'); + // @ts-expect-error: Testing internal method worker._worker.emit('exit', 0); - await expect(getStream(stdout)).resolves.toBe('Hello World!'); - await expect(getStream(stderr)).resolves.toBe('Jest Workers!'); + await expect(getStream(stdout!)).resolves.toBe('Hello World!'); + await expect(getStream(stderr!)).resolves.toBe('Jest Workers!'); }); it('sends the task to the thread', () => { @@ -154,18 +164,22 @@ it('sends the task to the thread', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); - const request = [CHILD_MESSAGE_CALL, false, 'foo', []]; + const request = [CHILD_MESSAGE_CALL, false, 'foo', []] as ChildMessageCall; worker.send( request, () => {}, () => {}, + () => {}, ); // Skipping call "0" because it corresponds to the "initialize" one. - expect(worker._worker.postMessage.mock.calls[1][0]).toEqual(request); + // @ts-expect-error: Testing internal method + expect(jest.mocked(worker._worker.postMessage).mock.calls[1][0]).toEqual( + request, + ); }); it('resends the task to the thread after a retry', () => { @@ -173,26 +187,36 @@ it('resends the task to the thread after a retry', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo/bar/baz.js', - }); + } as WorkerOptions); - const request = [CHILD_MESSAGE_CALL, false, 'foo', []]; + const request = [CHILD_MESSAGE_CALL, false, 'foo', []] as ChildMessageCall; worker.send( request, () => {}, () => {}, + () => {}, ); // Skipping call "0" because it corresponds to the "initialize" one. - expect(worker._worker.postMessage.mock.calls[1][0]).toEqual(request); + // @ts-expect-error: Testing internal method + expect(jest.mocked(worker._worker.postMessage).mock.calls[1][0]).toEqual( + request, + ); + // @ts-expect-error: Testing internal method const previousWorker = worker._worker; + // @ts-expect-error: Testing internal method worker._worker.emit('exit'); + // @ts-expect-error: Testing internal method expect(worker._worker).not.toBe(previousWorker); // Skipping call "0" because it corresponds to the "initialize" one. - expect(worker._worker.postMessage.mock.calls[1][0]).toEqual(request); + // @ts-expect-error: Testing internal method + expect(jest.mocked(worker._worker.postMessage).mock.calls[1][0]).toEqual( + request, + ); }); it('calls the onProcessStart method synchronously if the queue is empty', () => { @@ -200,7 +224,7 @@ it('calls the onProcessStart method synchronously if the queue is empty', () => forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); const onProcessStart = jest.fn(); const onProcessEnd = jest.fn(); @@ -209,6 +233,7 @@ it('calls the onProcessStart method synchronously if the queue is empty', () => [CHILD_MESSAGE_CALL, false, 'foo', []], onProcessStart, onProcessEnd, + () => {}, ); // Only onProcessStart has been called @@ -216,6 +241,7 @@ it('calls the onProcessStart method synchronously if the queue is empty', () => expect(onProcessEnd).not.toHaveBeenCalled(); // then first call replies... + // @ts-expect-error: Testing internal method worker._worker.emit('message', [PARENT_MESSAGE_OK]); expect(onProcessEnd).toHaveBeenCalledTimes(1); @@ -226,7 +252,7 @@ it('can send multiple messages to parent', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); const onProcessStart = jest.fn(); const onProcessEnd = jest.fn(); @@ -245,6 +271,7 @@ it('can send multiple messages to parent', () => { expect(onCustomMessage).not.toHaveBeenCalled(); // then first call replies... + // @ts-expect-error: Testing internal method worker._worker.emit('message', [ PARENT_MESSAGE_CUSTOM, {message: 'foo bar', otherKey: 1}, @@ -263,15 +290,21 @@ it('creates error instances for known errors', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); const callback1 = jest.fn(); const callback2 = jest.fn(); const callback3 = jest.fn(); // Testing a generic ECMAScript error. - worker.send([CHILD_MESSAGE_CALL, false, 'method', []], () => {}, callback1); + worker.send( + [CHILD_MESSAGE_CALL, false, 'method', []], + () => {}, + callback1, + () => {}, + ); + // @ts-expect-error: Testing internal method worker._worker.emit('message', [ PARENT_MESSAGE_CLIENT_ERROR, 'TypeError', @@ -281,13 +314,21 @@ it('creates error instances for known errors', () => { ]); expect(callback1.mock.calls[0][0]).toBeInstanceOf(TypeError); - expect(callback1.mock.calls[0][0].message).toBe('bar'); - expect(callback1.mock.calls[0][0].type).toBe('TypeError'); - expect(callback1.mock.calls[0][0].stack).toBe('TypeError: bar'); + expect(callback1.mock.calls[0][0]).toMatchObject({ + message: 'bar', + stack: 'TypeError: bar', + type: 'TypeError', + }); // Testing a custom error. - worker.send([CHILD_MESSAGE_CALL, false, 'method', []], () => {}, callback2); + worker.send( + [CHILD_MESSAGE_CALL, false, 'method', []], + () => {}, + callback2, + () => {}, + ); + // @ts-expect-error: Testing internal method worker._worker.emit('message', [ PARENT_MESSAGE_CLIENT_ERROR, 'RandomCustomError', @@ -297,14 +338,22 @@ it('creates error instances for known errors', () => { ]); expect(callback2.mock.calls[0][0]).toBeInstanceOf(Error); - expect(callback2.mock.calls[0][0].message).toBe('bar'); - expect(callback2.mock.calls[0][0].type).toBe('RandomCustomError'); - expect(callback2.mock.calls[0][0].stack).toBe('RandomCustomError: bar'); - expect(callback2.mock.calls[0][0].qux).toBe('extra property'); + expect(callback2.mock.calls[0][0]).toMatchObject({ + message: 'bar', + qux: 'extra property', + stack: 'RandomCustomError: bar', + type: 'RandomCustomError', + }); // Testing a non-object throw. - worker.send([CHILD_MESSAGE_CALL, false, 'method', []], () => {}, callback3); + worker.send( + [CHILD_MESSAGE_CALL, false, 'method', []], + () => {}, + callback3, + () => {}, + ); + // @ts-expect-error: Testing internal method worker._worker.emit('message', [ PARENT_MESSAGE_CLIENT_ERROR, 'Number', @@ -321,16 +370,18 @@ it('throws when the thread returns a strange message', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); worker.send( [CHILD_MESSAGE_CALL, false, 'method', []], () => {}, () => {}, + () => {}, ); // Type 27 does not exist. expect(() => { + // @ts-expect-error: Testing internal method worker._worker.emit('message', [27]); }).toThrow(TypeError); }); @@ -340,9 +391,10 @@ it('does not restart the thread if it cleanly exited', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); expect(workerThreads).toHaveBeenCalledTimes(1); + // @ts-expect-error: Testing internal method worker._worker.emit('exit', 0); expect(workerThreads).toHaveBeenCalledTimes(1); }); @@ -352,9 +404,10 @@ it('resolves waitForExit() after the thread cleanly exited', async () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); expect(workerThreads).toHaveBeenCalledTimes(1); + // @ts-expect-error: Testing internal method worker._worker.emit('exit', 0); await worker.waitForExit(); // should not timeout }); @@ -362,9 +415,10 @@ it('resolves waitForExit() after the thread cleanly exited', async () => { it('restarts the thread when the thread dies', () => { const worker = new Worker({ workerPath: '/tmp/foo', - }); + } as WorkerOptions); expect(workerThreads).toHaveBeenCalledTimes(1); + // @ts-expect-error: Testing internal method worker._worker.emit('exit', 1); expect(workerThreads).toHaveBeenCalledTimes(2); }); @@ -374,8 +428,9 @@ it('terminates the thread when forceExit() is called', () => { forkOptions: {}, maxRetries: 3, workerPath: '/tmp/foo', - }); + } as WorkerOptions); worker.forceExit(); + // @ts-expect-error: Testing internal method expect(worker._worker.terminate).toHaveBeenCalled(); }); diff --git a/packages/jest-worker/src/workers/__tests__/WorkerEdgeCases.test.js b/packages/jest-worker/src/workers/__tests__/WorkerEdgeCases.test.ts similarity index 86% rename from packages/jest-worker/src/workers/__tests__/WorkerEdgeCases.test.js rename to packages/jest-worker/src/workers/__tests__/WorkerEdgeCases.test.ts index 903b15d7f3a1..e056531854ae 100644 --- a/packages/jest-worker/src/workers/__tests__/WorkerEdgeCases.test.js +++ b/packages/jest-worker/src/workers/__tests__/WorkerEdgeCases.test.ts @@ -10,9 +10,7 @@ import {dirname, join} from 'path'; import {transformFileAsync} from '@babel/core'; import { CHILD_MESSAGE_CALL, - CHILD_MESSAGE_MEM_USAGE, WorkerEvents, - WorkerInterface, WorkerOptions, WorkerStates, } from '../../types'; @@ -41,7 +39,7 @@ beforeAll(async () => { const result = await transformFileAsync(sourcePath); - await writeFile(writePath, result.code, { + await writeFile(writePath, result!.code!, { encoding: 'utf-8', }); } @@ -51,13 +49,16 @@ afterAll(async () => { await rm(writeDestination, {force: true, recursive: true}); }); -test.each(filesToBuild)('%s.js should exist', async file => { +test.each(filesToBuild)('%s.js should exist', file => { const path = join(writeDestination, `${file}.js`); - await expect(async () => await access(path)).not.toThrow(); + expect(async () => await access(path)).not.toThrow(); }); -async function closeWorkerAfter(worker, testBody) { +async function closeWorkerAfter( + worker: ChildProcessWorker | ThreadsWorker, + testBody: (worker: ChildProcessWorker | ThreadsWorker) => Promise, +) { try { await testBody(worker); } finally { @@ -78,14 +79,14 @@ describe.each([ workerPath: threadChildWorkerPath, }, ])('$name', ({workerClass, workerPath}) => { - let int; + let int: NodeJS.Timeout; afterEach(async () => { clearInterval(int); }); - function waitForChange(fn) { - const inital = fn(); + function waitForChange(fn: () => unknown) { + const initial = fn(); return new Promise((resolve, reject) => { let count = 0; @@ -93,7 +94,7 @@ describe.each([ int = setInterval(() => { const updated = fn(); - if (inital !== updated) { + if (initial !== updated) { resolve(updated); clearInterval(int); } @@ -113,8 +114,8 @@ describe.each([ childWorkerPath: workerPath, maxRetries: 0, workerPath: join(__dirname, '__fixtures__', 'EdgeCasesWorker'), - }), - async worker => { + } as WorkerOptions), + async (worker: ChildProcessWorker | ThreadsWorker) => { const memoryUsagePromise = worker.getMemoryUsage(); expect(memoryUsagePromise).toBeInstanceOf(Promise); @@ -132,8 +133,8 @@ describe.each([ idleMemoryLimit: 1000, maxRetries: 0, workerPath: join(__dirname, '__fixtures__', 'EdgeCasesWorker'), - }), - async worker => { + } as WorkerOptions), + async (worker: ChildProcessWorker | ThreadsWorker) => { const startSystemId = worker.getWorkerSystemId(); expect(startSystemId).toBeGreaterThanOrEqual(0); @@ -155,9 +156,9 @@ describe.each([ }); describe('should automatically recycle on idle limit breach', () => { - let startPid; - let worker; - const orderOfEvents = []; + let startPid: number; + let worker: ChildProcessWorker | ThreadsWorker; + const orderOfEvents: Array = []; beforeAll(() => { worker = new workerClass({ @@ -167,13 +168,13 @@ describe.each([ idleMemoryLimit: 1000, maxRetries: 0, on: { - [WorkerEvents.STATE_CHANGE]: state => { + [WorkerEvents.STATE_CHANGE]: (state: WorkerStates) => { orderOfEvents.push(state); }, }, silent: true, workerPath: join(__dirname, '__fixtures__', 'EdgeCasesWorker'), - }); + } as unknown as WorkerOptions); }); afterAll(async () => { @@ -188,7 +189,7 @@ describe.each([ expect(startPid).toBeGreaterThanOrEqual(0); expect(worker.state).toEqual(WorkerStates.OK); - expect(orderOfEvents).toMatchObject(['ok']); + expect(orderOfEvents).toEqual(['ok']); }); test('new worker starts', async () => { @@ -226,36 +227,30 @@ describe.each([ ); test('expected state order', () => { - expect(orderOfEvents).toMatchObject([ - 'ok', - 'restarting', - 'starting', - 'ok', - ]); + expect(orderOfEvents).toEqual(['ok', 'restarting', 'starting', 'ok']); }); }); describe('should cleanly exit on out of memory crash', () => { const workerHeapLimit = 50; - let worker; - let orderOfEvents = []; + let worker: ChildProcessWorker | ThreadsWorker; + let orderOfEvents: Array = []; beforeAll(() => { orderOfEvents = []; - /** @type WorkerOptions */ const options = { childWorkerPath: workerPath, maxRetries: 0, on: { - [WorkerEvents.STATE_CHANGE]: state => { + [WorkerEvents.STATE_CHANGE]: (state: WorkerStates) => { orderOfEvents.push(state); }, }, silent: true, workerPath: join(__dirname, '__fixtures__', 'EdgeCasesWorker'), - }; + } as unknown as WorkerOptions; if (workerClass === ThreadsWorker) { options.resourceLimits = { @@ -275,7 +270,7 @@ describe.each([ }); afterAll(async () => { - await new Promise(resolve => { + await new Promise(resolve => { setTimeout(async () => { if (worker) { worker.forceExit(); @@ -322,16 +317,16 @@ describe.each([ }); test('expected state order', () => { - expect(orderOfEvents).toMatchObject([ + expect(orderOfEvents).toEqual([ WorkerStates.OK, WorkerStates.OUT_OF_MEMORY, WorkerStates.SHUT_DOWN, ]); }); - }, 15000); + }); describe('should handle regular fatal crashes', () => { - let worker; + let worker: ChildProcessWorker | ThreadsWorker; let startedWorkers = 0; beforeAll(() => { @@ -339,14 +334,14 @@ describe.each([ childWorkerPath: workerPath, maxRetries: 4, on: { - [WorkerEvents.STATE_CHANGE]: state => { + [WorkerEvents.STATE_CHANGE]: (state: WorkerStates) => { if (state === WorkerStates.OK) { startedWorkers++; } }, }, workerPath: join(__dirname, '__fixtures__', 'EdgeCasesWorker'), - }); + } as unknown as WorkerOptions); }); afterAll(async () => { diff --git a/packages/jest-worker/src/workers/__tests__/processChild.test.js b/packages/jest-worker/src/workers/__tests__/processChild.test.js deleted file mode 100644 index 209987616ed0..000000000000 --- a/packages/jest-worker/src/workers/__tests__/processChild.test.js +++ /dev/null @@ -1,389 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -const mockError = new TypeError('Booo'); -const mockExtendedError = new ReferenceError('Booo extended'); -const processExit = process.exit; -const processSend = process.send; -const uninitializedParam = {}; -const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); - -import { - CHILD_MESSAGE_CALL, - CHILD_MESSAGE_END, - CHILD_MESSAGE_INITIALIZE, - CHILD_MESSAGE_MEM_USAGE, - PARENT_MESSAGE_CLIENT_ERROR, - PARENT_MESSAGE_MEM_USAGE, - PARENT_MESSAGE_OK, -} from '../../types'; - -let ended; -let mockCount; -let initializeParm = uninitializedParam; - -beforeEach(() => { - mockCount = 0; - ended = false; - - jest.mock( - '../my-fancy-worker', - () => { - mockCount++; - - return { - fooPromiseThrows() { - return new Promise((resolve, reject) => { - setTimeout(() => reject(mockError), 5); - }); - }, - - fooPromiseWorks() { - return new Promise(resolve => { - setTimeout(() => resolve(1989), 5); - }); - }, - - fooThrows() { - throw mockError; - }, - - fooThrowsANumber() { - // eslint-disable-next-line no-throw-literal - throw 412; - }, - - fooThrowsAnErrorWithExtraProperties() { - mockExtendedError.baz = 123; - mockExtendedError.qux = 456; - - throw mockExtendedError; - }, - - fooThrowsNull() { - // eslint-disable-next-line no-throw-literal - throw null; - }, - - fooWorks() { - return 1989; - }, - - setup(param) { - initializeParm = param; - }, - - teardown() { - ended = true; - }, - }; - }, - {virtual: true}, - ); - - jest.mock( - '../my-fancy-standalone-worker', - () => jest.fn().mockImplementation(() => 12345), - {virtual: true}, - ); - - // This mock emulates a transpiled Babel module that carries a default export - // that corresponds to a method. - jest.mock( - '../my-fancy-babel-worker', - () => ({ - __esModule: true, - default: jest.fn().mockImplementation(() => 67890), - }), - {virtual: true}, - ); - - process.exit = jest.fn(); - process.send = jest.fn(); - - // Require the child! - require('../processChild'); -}); - -afterEach(() => { - jest.resetModules(); - - process.removeAllListeners('message'); - - process.exit = processExit; - process.send = processSend; -}); - -it('lazily requires the file', () => { - expect(mockCount).toBe(0); - - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-worker', - ]); - - expect(mockCount).toBe(0); - expect(initializeParm).toBe(uninitializedParam); // Not called yet. - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooWorks', - [], - ]); - - expect(mockCount).toBe(1); - expect(initializeParm).toBeUndefined(); -}); - -it('should return memory usage', () => { - process.send = jest.fn(); - - expect(mockCount).toBe(0); - - process.emit('message', [CHILD_MESSAGE_MEM_USAGE]); - - expect(process.send.mock.calls[0][0]).toEqual([ - PARENT_MESSAGE_MEM_USAGE, - expect.any(Number), - ]); -}); - -it('calls initialize with the correct arguments', () => { - expect(mockCount).toBe(0); - - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-worker', - ['foo'], // Pass empty initialize params so the initialize method is called. - ]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooWorks', - [], - ]); - - expect(initializeParm).toBe('foo'); -}); - -it('returns results immediately when function is synchronous', () => { - process.send = jest.fn(); - - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-worker', - ]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooWorks', - [], - ]); - - expect(process.send.mock.calls[0][0]).toEqual([PARENT_MESSAGE_OK, 1989]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooThrows', - [], - ]); - - expect(process.send.mock.calls[1][0]).toEqual([ - PARENT_MESSAGE_CLIENT_ERROR, - 'TypeError', - 'Booo', - mockError.stack, - {}, - ]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooThrowsANumber', - [], - ]); - - expect(process.send.mock.calls[2][0]).toEqual([ - PARENT_MESSAGE_CLIENT_ERROR, - 'Number', - void 0, - void 0, - 412, - ]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooThrowsAnErrorWithExtraProperties', - [], - ]); - - expect(process.send.mock.calls[3][0]).toEqual([ - PARENT_MESSAGE_CLIENT_ERROR, - 'ReferenceError', - 'Booo extended', - mockExtendedError.stack, - {baz: 123, qux: 456}, - ]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooThrowsNull', - [], - ]); - - expect(process.send.mock.calls[4][0][0]).toBe(PARENT_MESSAGE_CLIENT_ERROR); - expect(process.send.mock.calls[4][0][1]).toBe('Error'); - expect(process.send.mock.calls[4][0][2]).toBe('"null" or "undefined" thrown'); - - expect(process.send).toHaveBeenCalledTimes(5); -}); - -it('returns results when it gets resolved if function is asynchronous', async () => { - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-worker', - ]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooPromiseWorks', - [], - ]); - - await sleep(10); - - expect(process.send.mock.calls[0][0]).toEqual([PARENT_MESSAGE_OK, 1989]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooPromiseThrows', - [], - ]); - - await sleep(10); - - expect(process.send.mock.calls[1][0]).toEqual([ - PARENT_MESSAGE_CLIENT_ERROR, - 'TypeError', - 'Booo', - mockError.stack, - {}, - ]); - - expect(process.send).toHaveBeenCalledTimes(2); -}); - -it('calls the main module if the method call is "default"', () => { - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-standalone-worker', - ]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'default', - [], - ]); - - expect(process.send.mock.calls[0][0]).toEqual([PARENT_MESSAGE_OK, 12345]); -}); - -it('calls the main export if the method call is "default" and it is a Babel transpiled one', () => { - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-babel-worker', - ]); - - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'default', - [], - ]); - - expect(process.send.mock.calls[0][0]).toEqual([PARENT_MESSAGE_OK, 67890]); -}); - -it('removes the message listener on END message', () => { - // So that there are no more open handles preventing Node from exiting - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-worker', - ]); - - process.emit('message', [CHILD_MESSAGE_END]); - - expect(process.listenerCount('message')).toBe(0); -}); - -it('calls the teardown method ', () => { - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-worker', - ]); - - process.emit('message', [ - CHILD_MESSAGE_END, - true, // Not really used here, but for flow type purity. - ]); - - expect(ended).toBe(true); -}); - -it('throws if an invalid message is detected', () => { - // Type 27 does not exist. - expect(() => { - process.emit('message', [27]); - }).toThrow(TypeError); -}); - -it('throws if child is not forked', () => { - delete process.send; - - process.emit('message', [ - CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. - './my-fancy-worker', - ]); - - expect(() => { - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooWorks', - [], - ]); - }).toThrow('Child can only be used on a forked process'); - - expect(() => { - process.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooThrows', - [], - ]); - }).toThrow('Child can only be used on a forked process'); -}); diff --git a/packages/jest-worker/src/workers/__tests__/processChild.test.ts b/packages/jest-worker/src/workers/__tests__/processChild.test.ts new file mode 100644 index 000000000000..1665351dc573 --- /dev/null +++ b/packages/jest-worker/src/workers/__tests__/processChild.test.ts @@ -0,0 +1,476 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + CHILD_MESSAGE_CALL, + CHILD_MESSAGE_END, + CHILD_MESSAGE_INITIALIZE, + CHILD_MESSAGE_MEM_USAGE, + PARENT_MESSAGE_CLIENT_ERROR, + PARENT_MESSAGE_MEM_USAGE, + PARENT_MESSAGE_OK, +} from '../../types'; + +const spyProcessSend = jest.spyOn(process, 'send'); + +class MockExtendedError extends ReferenceError { + baz = 123; + qux = 456; +} + +const mockError = new TypeError('Boo'); +const mockExtendedError = new MockExtendedError('Boo extended'); +const uninitializedParam = {}; +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +let ended: boolean; +let mockCount: number; +let initializeParm = uninitializedParam; + +beforeEach(() => { + mockCount = 0; + ended = false; + + jest.mock( + '../my-fancy-worker', + () => { + mockCount++; + + return { + fooPromiseThrows() { + return new Promise((_resolve, reject) => { + setTimeout(() => reject(mockError), 5); + }); + }, + + fooPromiseWorks() { + return new Promise(resolve => { + setTimeout(() => resolve(1989), 5); + }); + }, + + fooThrows() { + throw mockError; + }, + + fooThrowsANumber() { + // eslint-disable-next-line no-throw-literal + throw 412; + }, + + fooThrowsAnErrorWithExtraProperties() { + mockExtendedError.baz = 123; + mockExtendedError.qux = 456; + + throw mockExtendedError; + }, + + fooThrowsNull() { + // eslint-disable-next-line no-throw-literal + throw null; + }, + + fooWorks() { + return 1989; + }, + + setup(param: Record) { + initializeParm = param; + }, + + teardown() { + ended = true; + }, + }; + }, + {virtual: true}, + ); + + jest.mock( + '../my-fancy-standalone-worker', + () => jest.fn().mockImplementation(() => 12345), + {virtual: true}, + ); + + // This mock emulates a transpiled Babel module that carries a default export + // that corresponds to a method. + jest.mock( + '../my-fancy-babel-worker', + () => ({ + __esModule: true, + default: jest.fn().mockImplementation(() => 67890), + }), + {virtual: true}, + ); + + // Require the child! + require('../processChild'); +}); + +afterEach(() => { + jest.clearAllMocks().resetModules(); + + process.removeAllListeners('message'); +}); + +it('lazily requires the file', () => { + expect(mockCount).toBe(0); + + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-worker', + ], + null, + ); + + expect(mockCount).toBe(0); + expect(initializeParm).toBe(uninitializedParam); // Not called yet. + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooWorks', + [], + ], + null, + ); + + expect(mockCount).toBe(1); + expect(initializeParm).toBeUndefined(); +}); + +it('should return memory usage', () => { + expect(mockCount).toBe(0); + + process.emit('message', [CHILD_MESSAGE_MEM_USAGE], null); + + expect(spyProcessSend.mock.calls[0][0]).toEqual([ + PARENT_MESSAGE_MEM_USAGE, + expect.any(Number), + ]); +}); + +it('calls initialize with the correct arguments', () => { + expect(mockCount).toBe(0); + + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-worker', + ['foo'], // Pass empty initialize params so the initialize method is called. + ], + null, + ); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooWorks', + [], + ], + null, + ); + + expect(initializeParm).toBe('foo'); +}); + +it('returns results immediately when function is synchronous', () => { + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-worker', + ], + null, + ); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooWorks', + [], + ], + null, + ); + + expect(spyProcessSend.mock.calls[0][0]).toEqual([PARENT_MESSAGE_OK, 1989]); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooThrows', + [], + ], + null, + ); + + expect(spyProcessSend.mock.calls[1][0]).toEqual([ + PARENT_MESSAGE_CLIENT_ERROR, + 'TypeError', + 'Boo', + mockError.stack, + {}, + ]); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooThrowsANumber', + [], + ], + null, + ); + + expect(spyProcessSend.mock.calls[2][0]).toEqual([ + PARENT_MESSAGE_CLIENT_ERROR, + 'Number', + void 0, + void 0, + 412, + ]); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooThrowsAnErrorWithExtraProperties', + [], + ], + null, + ); + + expect(spyProcessSend.mock.calls[3][0]).toEqual([ + PARENT_MESSAGE_CLIENT_ERROR, + 'MockExtendedError', + 'Boo extended', + mockExtendedError.stack, + {baz: 123, qux: 456}, + ]); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooThrowsNull', + [], + ], + null, + ); + + expect(spyProcessSend.mock.calls[4][0][0]).toBe(PARENT_MESSAGE_CLIENT_ERROR); + expect(spyProcessSend.mock.calls[4][0][1]).toBe('Error'); + expect(spyProcessSend.mock.calls[4][0][2]).toBe( + '"null" or "undefined" thrown', + ); + + expect(spyProcessSend).toHaveBeenCalledTimes(5); +}); + +it('returns results when it gets resolved if function is asynchronous', async () => { + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-worker', + ], + null, + ); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooPromiseWorks', + [], + ], + null, + ); + + await sleep(10); + + expect(spyProcessSend.mock.calls[0][0]).toEqual([PARENT_MESSAGE_OK, 1989]); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooPromiseThrows', + [], + ], + null, + ); + + await sleep(10); + + expect(spyProcessSend.mock.calls[1][0]).toEqual([ + PARENT_MESSAGE_CLIENT_ERROR, + 'TypeError', + 'Boo', + mockError.stack, + {}, + ]); + + expect(spyProcessSend).toHaveBeenCalledTimes(2); +}); + +it('calls the main module if the method call is "default"', () => { + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-standalone-worker', + ], + null, + ); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'default', + [], + ], + null, + ); + + expect(spyProcessSend.mock.calls[0][0]).toEqual([PARENT_MESSAGE_OK, 12345]); +}); + +it('calls the main export if the method call is "default" and it is a Babel transpiled one', () => { + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-babel-worker', + ], + null, + ); + + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'default', + [], + ], + null, + ); + + expect(spyProcessSend.mock.calls[0][0]).toEqual([PARENT_MESSAGE_OK, 67890]); +}); + +it('removes the message listener on END message', () => { + // So that there are no more open handles preventing Node from exiting + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-worker', + ], + null, + ); + + process.emit('message', [CHILD_MESSAGE_END], null); + + expect(process.listenerCount('message')).toBe(0); +}); + +it('calls the teardown method ', () => { + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-worker', + ], + null, + ); + + process.emit( + 'message', + [ + CHILD_MESSAGE_END, + true, // Not really used here, but for type purity. + ], + null, + ); + + expect(ended).toBe(true); +}); + +it('throws if an invalid message is detected', () => { + // Type 27 does not exist. + expect(() => { + process.emit('message', [27], null); + }).toThrow(TypeError); +}); + +it('throws if child is not forked', () => { + delete process.send; + + process.emit( + 'message', + [ + CHILD_MESSAGE_INITIALIZE, + true, // Not really used here, but for type purity. + './my-fancy-worker', + ], + null, + ); + + expect(() => { + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooWorks', + [], + ], + null, + ); + }).toThrow('Child can only be used on a forked process'); + + expect(() => { + process.emit( + 'message', + [ + CHILD_MESSAGE_CALL, + true, // Not really used here, but for type purity. + 'fooThrows', + [], + ], + null, + ); + }).toThrow('Child can only be used on a forked process'); +}); diff --git a/packages/jest-worker/src/workers/__tests__/threadChild.test.js b/packages/jest-worker/src/workers/__tests__/threadChild.test.ts similarity index 51% rename from packages/jest-worker/src/workers/__tests__/threadChild.test.js rename to packages/jest-worker/src/workers/__tests__/threadChild.test.ts index 456e7c5a8b2c..281365950f05 100644 --- a/packages/jest-worker/src/workers/__tests__/threadChild.test.js +++ b/packages/jest-worker/src/workers/__tests__/threadChild.test.ts @@ -5,37 +5,43 @@ * LICENSE file in the root directory of this source tree. */ -'use strict'; +import {EventEmitter} from 'events'; +import type {MessagePort} from 'worker_threads'; +import { + CHILD_MESSAGE_CALL, + CHILD_MESSAGE_END, + CHILD_MESSAGE_INITIALIZE, + PARENT_MESSAGE_CLIENT_ERROR, + PARENT_MESSAGE_OK, +} from '../../types'; -jest.mock('worker_threads', () => { - const EventEmitter = require('events'); - const thread = new EventEmitter(); - thread.postMessage = jest.fn(); +class MockedParentPort extends EventEmitter { + postMessage = jest.fn(); +} +jest.mock('worker_threads', () => { return { isMainThread: false, - parentPort: thread, + parentPort: new MockedParentPort(), }; }); -let thread; -const mockError = new TypeError('Booo'); -const mockExtendedError = new ReferenceError('Booo extended'); -const uninitializedParam = {}; -const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); +class MockExtendedError extends ReferenceError { + baz = 123; + qux = 456; +} -import { - CHILD_MESSAGE_CALL, - CHILD_MESSAGE_END, - CHILD_MESSAGE_INITIALIZE, - PARENT_MESSAGE_CLIENT_ERROR, - PARENT_MESSAGE_OK, -} from '../../types'; +const mockError = new TypeError('Boo'); +const mockExtendedError = new MockExtendedError('Boo extended'); +const uninitializedParam = {}; +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); -let ended; -let mockCount; +let ended: boolean; +let mockCount: number; let initializeParm = uninitializedParam; +let messagePort: MessagePort; + beforeEach(() => { mockCount = 0; ended = false; @@ -47,7 +53,7 @@ beforeEach(() => { return { fooPromiseThrows() { - return new Promise((resolve, reject) => { + return new Promise((_resolve, reject) => { setTimeout(() => reject(mockError), 5); }); }, @@ -68,9 +74,6 @@ beforeEach(() => { }, fooThrowsAnErrorWithExtraProperties() { - mockExtendedError.baz = 123; - mockExtendedError.qux = 456; - throw mockExtendedError; }, @@ -83,7 +86,7 @@ beforeEach(() => { return 1989; }, - setup(param) { + setup(param: Record) { initializeParm = param; }, @@ -95,11 +98,9 @@ beforeEach(() => { {virtual: true}, ); - jest.mock( - '../my-fancy-standalone-worker', - () => jest.fn().mockImplementation(() => 12345), - {virtual: true}, - ); + jest.mock('../my-fancy-standalone-worker', () => jest.fn(() => 12345), { + virtual: true, + }); // This mock emulates a transpiled Babel module that carries a default export // that corresponds to a method. @@ -107,31 +108,32 @@ beforeEach(() => { '../my-fancy-babel-worker', () => ({ __esModule: true, - default: jest.fn().mockImplementation(() => 67890), + default: jest.fn(() => 67890), }), {virtual: true}, ); - thread = require('worker_threads').parentPort; + messagePort = (require('worker_threads') as typeof import('worker_threads')) + .parentPort as MessagePort; // Require the child! require('../threadChild'); }); beforeEach(() => { - thread.postMessage.mockClear(); + jest.mocked(messagePort.postMessage).mockClear(); }); afterEach(() => { jest.resetModules(); - thread.removeAllListeners('message'); + messagePort.removeAllListeners('message'); }); it('sets env.JEST_WORKER_ID', () => { - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-worker', [], '3', @@ -143,21 +145,16 @@ it('sets env.JEST_WORKER_ID', () => { it('lazily requires the file', () => { expect(mockCount).toBe(0); - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-worker', ]); expect(mockCount).toBe(0); expect(initializeParm).toBe(uninitializedParam); // Not called yet. - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooWorks', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'fooWorks', []]); expect(mockCount).toBe(1); expect(initializeParm).toBeUndefined(); @@ -166,67 +163,50 @@ it('lazily requires the file', () => { it('calls initialize with the correct arguments', () => { expect(mockCount).toBe(0); - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-worker', ['foo'], // Pass empty initialize params so the initialize method is called. ]); - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooWorks', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'fooWorks', []]); expect(initializeParm).toBe('foo'); }); it('returns results immediately when function is synchronous', () => { - thread.send = jest.fn(); - - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-worker', ]); - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooWorks', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'fooWorks', []]); - expect(thread.postMessage.mock.calls[0][0]).toEqual([ + expect(jest.mocked(messagePort.postMessage).mock.calls[0][0]).toEqual([ PARENT_MESSAGE_OK, 1989, ]); - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooThrows', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'fooThrows', []]); - expect(thread.postMessage.mock.calls[1][0]).toEqual([ + expect(jest.mocked(messagePort.postMessage).mock.calls[1][0]).toEqual([ PARENT_MESSAGE_CLIENT_ERROR, 'TypeError', - 'Booo', + 'Boo', mockError.stack, {}, ]); - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. + true, 'fooThrowsANumber', [], ]); - expect(thread.postMessage.mock.calls[2][0]).toEqual([ + expect(jest.mocked(messagePort.postMessage).mock.calls[2][0]).toEqual([ PARENT_MESSAGE_CLIENT_ERROR, 'Number', void 0, @@ -234,115 +214,102 @@ it('returns results immediately when function is synchronous', () => { 412, ]); - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. + true, 'fooThrowsAnErrorWithExtraProperties', [], ]); - expect(thread.postMessage.mock.calls[3][0]).toEqual([ + expect(jest.mocked(messagePort.postMessage).mock.calls[3][0]).toEqual([ PARENT_MESSAGE_CLIENT_ERROR, - 'ReferenceError', - 'Booo extended', + 'MockExtendedError', + 'Boo extended', mockExtendedError.stack, {baz: 123, qux: 456}, ]); - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooThrowsNull', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'fooThrowsNull', []]); - expect(thread.postMessage.mock.calls[4][0][0]).toBe( + expect(jest.mocked(messagePort.postMessage).mock.calls[4][0][0]).toBe( PARENT_MESSAGE_CLIENT_ERROR, ); - expect(thread.postMessage.mock.calls[4][0][1]).toBe('Error'); - expect(thread.postMessage.mock.calls[4][0][2]).toBe( + expect(jest.mocked(messagePort.postMessage).mock.calls[4][0][1]).toBe( + 'Error', + ); + expect(jest.mocked(messagePort.postMessage).mock.calls[4][0][2]).toBe( '"null" or "undefined" thrown', ); - expect(thread.postMessage).toHaveBeenCalledTimes(5); + expect(messagePort.postMessage).toHaveBeenCalledTimes(5); }); it('returns results when it gets resolved if function is asynchronous', async () => { - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-worker', ]); - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. + true, 'fooPromiseWorks', [], ]); await sleep(10); - expect(thread.postMessage.mock.calls[0][0]).toEqual([ + expect(jest.mocked(messagePort.postMessage).mock.calls[0][0]).toEqual([ PARENT_MESSAGE_OK, 1989, ]); - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. + true, 'fooPromiseThrows', [], ]); await sleep(10); - expect(thread.postMessage.mock.calls[1][0]).toEqual([ + expect(jest.mocked(messagePort.postMessage).mock.calls[1][0]).toEqual([ PARENT_MESSAGE_CLIENT_ERROR, 'TypeError', - 'Booo', + 'Boo', mockError.stack, {}, ]); - expect(thread.postMessage).toHaveBeenCalledTimes(2); + expect(messagePort.postMessage).toHaveBeenCalledTimes(2); }); it('calls the main module if the method call is "default"', () => { - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-standalone-worker', ]); - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'default', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'default', []]); - expect(thread.postMessage.mock.calls[0][0]).toEqual([ + expect(jest.mocked(messagePort.postMessage).mock.calls[0][0]).toEqual([ PARENT_MESSAGE_OK, 12345, ]); }); it('calls the main export if the method call is "default" and it is a Babel transpiled one', () => { - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-babel-worker', ]); - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'default', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'default', []]); - expect(thread.postMessage.mock.calls[0][0]).toEqual([ + expect(jest.mocked(messagePort.postMessage).mock.calls[0][0]).toEqual([ PARENT_MESSAGE_OK, 67890, ]); @@ -350,31 +317,25 @@ it('calls the main export if the method call is "default" and it is a Babel tran it('removes the message listener on END message', () => { // So that there are no more open handles preventing Node from exiting - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-worker', ]); - thread.emit('message', [ - CHILD_MESSAGE_END, - true, // Not really used here, but for flow type purity. - ]); + messagePort.emit('message', [CHILD_MESSAGE_END, true]); - expect(thread.listenerCount('message')).toBe(0); + expect(messagePort.listenerCount('message')).toBe(0); }); it('calls the teardown method ', () => { - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-worker', ]); - thread.emit('message', [ - CHILD_MESSAGE_END, - true, // Not really used here, but for flow type purity. - ]); + messagePort.emit('message', [CHILD_MESSAGE_END, true]); expect(ended).toBe(true); }); @@ -382,34 +343,25 @@ it('calls the teardown method ', () => { it('throws if an invalid message is detected', () => { // Type 27 does not exist. expect(() => { - thread.emit('message', [27]); + messagePort.emit('message', [27]); }).toThrow(TypeError); }); it('throws if child is not forked', () => { - delete thread.postMessage; + // @ts-expect-error: Testing purpose + delete messagePort.postMessage; - thread.emit('message', [ + messagePort.emit('message', [ CHILD_MESSAGE_INITIALIZE, - true, // Not really used here, but for flow type purity. + true, './my-fancy-worker', ]); expect(() => { - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooWorks', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'fooWorks', []]); }).toThrow('_worker_threads.parentPort.postMessage is not a function'); expect(() => { - thread.emit('message', [ - CHILD_MESSAGE_CALL, - true, // Not really used here, but for flow type purity. - 'fooThrows', - [], - ]); + messagePort.emit('message', [CHILD_MESSAGE_CALL, true, 'fooThrows', []]); }).toThrow('_worker_threads.parentPort.postMessage is not a function'); }); diff --git a/packages/test-globals/src/index.ts b/packages/test-globals/src/index.ts index 94eedd3e3520..ddeaf6e1b7a0 100644 --- a/packages/test-globals/src/index.ts +++ b/packages/test-globals/src/index.ts @@ -16,6 +16,11 @@ import type { MockedClass as JestMockedClass, MockedFunction as JestMockedFunction, MockedObject as JestMockedObject, + Spied as JestSpied, + SpiedClass as JestSpiedClass, + SpiedFunction as JestSpiedFunction, + SpiedGetter as JestSpiedGetter, + SpiedSetter as JestSpiedSetter, UnknownFunction, } from 'jest-mock'; @@ -63,5 +68,25 @@ declare global { * Wraps an object type with Jest mock type definitions. */ export type MockedObject = JestMockedObject; + /** + * Constructs the type of a spied class or function. + */ + export type Spied = JestSpied; + /** + * Constructs the type of a spied class. + */ + export type SpiedClass = JestSpiedClass; + /** + * Constructs the type of a spied function. + */ + export type SpiedFunction = JestSpiedFunction; + /** + * Constructs the type of a spied getter. + */ + export type SpiedGetter = JestSpiedGetter; + /** + * Constructs the type of a spied setter. + */ + export type SpiedSetter = JestSpiedSetter; } } diff --git a/scripts/lintTs.mjs b/scripts/lintTs.mjs index 760bfb7abdd2..60ef1e7a8f23 100644 --- a/scripts/lintTs.mjs +++ b/scripts/lintTs.mjs @@ -26,6 +26,7 @@ const packagesToTest = [ 'babel-jest', 'babel-plugin-jest-hoist', 'diff-sequences', + 'jest', 'jest-changed-files', 'jest-console', 'jest-docblock', @@ -38,6 +39,7 @@ const packagesToTest = [ 'jest-test-sequencer', 'jest-transform', 'jest-types', + 'jest-watcher', 'test-globals', 'test-utils', ]; @@ -64,6 +66,16 @@ try { extends: [ 'plugin:@typescript-eslint/recommended-requiring-type-checking', ], + overrides: [ + { + files: ['**/__tests__/**'], + plugins: ['jest'], + rules: { + '@typescript-eslint/unbound-method': 'off', + 'jest/unbound-method': 'error', + }, + }, + ], parser: '@typescript-eslint/parser', parserOptions: { project: ['./tsconfig.json', `${packageDir}/tsconfig.json`], diff --git a/website/static/img/blog/15-console.png b/website/static/img/blog/15-console.png index f065fe541871..8ffa08604349 100644 Binary files a/website/static/img/blog/15-console.png and b/website/static/img/blog/15-console.png differ diff --git a/website/static/img/blog/15-failure1.png b/website/static/img/blog/15-failure1.png index 04e7203447f7..895705655391 100644 Binary files a/website/static/img/blog/15-failure1.png and b/website/static/img/blog/15-failure1.png differ diff --git a/website/static/img/blog/15-failure2.png b/website/static/img/blog/15-failure2.png index 9513bcfeb12d..a8c8a5b8329e 100644 Binary files a/website/static/img/blog/15-failure2.png and b/website/static/img/blog/15-failure2.png differ diff --git a/website/static/img/blog/16-snapshots.png b/website/static/img/blog/16-snapshots.png index ee798f4871fc..5a367cb9c1aa 100644 Binary files a/website/static/img/blog/16-snapshots.png and b/website/static/img/blog/16-snapshots.png differ diff --git a/website/static/img/blog/19-asymmetric-matchers.png b/website/static/img/blog/19-asymmetric-matchers.png index b2055de0a772..2565d5295fc1 100644 Binary files a/website/static/img/blog/19-asymmetric-matchers.png and b/website/static/img/blog/19-asymmetric-matchers.png differ diff --git a/website/static/img/blog/19-cli-error.png b/website/static/img/blog/19-cli-error.png index 49c9af72cb72..b0e1a48c5e88 100644 Binary files a/website/static/img/blog/19-cli-error.png and b/website/static/img/blog/19-cli-error.png differ diff --git a/website/static/img/blog/19-skipped-tests.png b/website/static/img/blog/19-skipped-tests.png index ef593c4f7b83..8124fc1e8871 100644 Binary files a/website/static/img/blog/19-skipped-tests.png and b/website/static/img/blog/19-skipped-tests.png differ diff --git a/website/static/img/blog/19-snapshot-version.png b/website/static/img/blog/19-snapshot-version.png index 603fe08d1cc3..01dddbe47c48 100644 Binary files a/website/static/img/blog/19-snapshot-version.png and b/website/static/img/blog/19-snapshot-version.png differ diff --git a/website/static/img/blog/19-validate.png b/website/static/img/blog/19-validate.png index cc9d835cd3df..860abd451db0 100644 Binary files a/website/static/img/blog/19-validate.png and b/website/static/img/blog/19-validate.png differ diff --git a/website/static/img/blog/20-testing-apis.png b/website/static/img/blog/20-testing-apis.png index f494146196d8..5830aca91f0c 100644 Binary files a/website/static/img/blog/20-testing-apis.png and b/website/static/img/blog/20-testing-apis.png differ diff --git a/website/static/img/blog/22-community.png b/website/static/img/blog/22-community.png index cf12cbbcd01e..67d5fd46392e 100644 Binary files a/website/static/img/blog/22-community.png and b/website/static/img/blog/22-community.png differ diff --git a/website/static/img/blog/22-failure-21.png b/website/static/img/blog/22-failure-21.png index 4737096ee845..ecdb93693c7b 100644 Binary files a/website/static/img/blog/22-failure-21.png and b/website/static/img/blog/22-failure-21.png differ diff --git a/website/static/img/blog/22-failure-22.png b/website/static/img/blog/22-failure-22.png index a6571f2d8443..16a7ec0be50c 100644 Binary files a/website/static/img/blog/22-failure-22.png and b/website/static/img/blog/22-failure-22.png differ diff --git a/website/static/img/blog/23-asymmetric-matchers.png b/website/static/img/blog/23-asymmetric-matchers.png index 00c2ef1292e0..bde8d0afd759 100644 Binary files a/website/static/img/blog/23-asymmetric-matchers.png and b/website/static/img/blog/23-asymmetric-matchers.png differ diff --git a/website/static/img/blog/23-async-matchers.png b/website/static/img/blog/23-async-matchers.png index d6977e017f2c..0abf04b4c6b2 100644 Binary files a/website/static/img/blog/23-async-matchers.png and b/website/static/img/blog/23-async-matchers.png differ diff --git a/website/static/img/blog/23-hanging-after.png b/website/static/img/blog/23-hanging-after.png index 77ea401f1b5f..3646d8ac92c8 100644 Binary files a/website/static/img/blog/23-hanging-after.png and b/website/static/img/blog/23-hanging-after.png differ diff --git a/website/static/img/blog/23-hanging-before.png b/website/static/img/blog/23-hanging-before.png index 12d1a157ea73..c26831816619 100644 Binary files a/website/static/img/blog/23-hanging-before.png and b/website/static/img/blog/23-hanging-before.png differ diff --git a/website/static/img/blog/23-jest-each.png b/website/static/img/blog/23-jest-each.png index de395a47c34e..c26bdf84db84 100644 Binary files a/website/static/img/blog/23-jest-each.png and b/website/static/img/blog/23-jest-each.png differ diff --git a/website/static/img/blog/23-new-matchers.png b/website/static/img/blog/23-new-matchers.png index 9d8c6d2e2c65..d909eda3866b 100644 Binary files a/website/static/img/blog/23-new-matchers.png and b/website/static/img/blog/23-new-matchers.png differ diff --git a/website/static/img/blog/23-snapshot-matchers.png b/website/static/img/blog/23-snapshot-matchers.png index 923b2b5d87fd..d520f40e490e 100644 Binary files a/website/static/img/blog/23-snapshot-matchers.png and b/website/static/img/blog/23-snapshot-matchers.png differ diff --git a/website/static/img/blog/24-assertion-error.png b/website/static/img/blog/24-assertion-error.png index 7f4507fa12e2..6d7e18c8fda9 100644 Binary files a/website/static/img/blog/24-assertion-error.png and b/website/static/img/blog/24-assertion-error.png differ diff --git a/website/static/img/blog/24-different-types.png b/website/static/img/blog/24-different-types.png index fe34f6f10ca8..be9f999f3886 100644 Binary files a/website/static/img/blog/24-different-types.png and b/website/static/img/blog/24-different-types.png differ diff --git a/website/static/img/blog/24-mock-function.png b/website/static/img/blog/24-mock-function.png index 895b8693737a..a8837ccbad5f 100644 Binary files a/website/static/img/blog/24-mock-function.png and b/website/static/img/blog/24-mock-function.png differ diff --git a/website/static/img/blog/24-todo.png b/website/static/img/blog/24-todo.png index 8cb4217afc3e..2fcede8b6ec0 100644 Binary files a/website/static/img/blog/24-todo.png and b/website/static/img/blog/24-todo.png differ diff --git a/website/static/img/blog/25-snapshot-change-lines.png b/website/static/img/blog/25-snapshot-change-lines.png index 9c8bf45aeb4c..172392efa46c 100644 Binary files a/website/static/img/blog/25-snapshot-change-lines.png and b/website/static/img/blog/25-snapshot-change-lines.png differ diff --git a/website/static/img/blog/25-snapshot-change-substrings.png b/website/static/img/blog/25-snapshot-change-substrings.png index 32cc6cce0f86..39fa717cc1c8 100644 Binary files a/website/static/img/blog/25-snapshot-change-substrings.png and b/website/static/img/blog/25-snapshot-change-substrings.png differ diff --git a/website/static/img/blog/25-snapshot-insert-lines.png b/website/static/img/blog/25-snapshot-insert-lines.png index d9625d0f1f11..572132fb4a89 100644 Binary files a/website/static/img/blog/25-snapshot-insert-lines.png and b/website/static/img/blog/25-snapshot-insert-lines.png differ diff --git a/website/static/img/blog/27-interactive-failures.png b/website/static/img/blog/27-interactive-failures.png index e9a20506b63f..dd000aa094fb 100644 Binary files a/website/static/img/blog/27-interactive-failures.png and b/website/static/img/blog/27-interactive-failures.png differ diff --git a/website/static/img/blog/28-gh-actions-reporter.png b/website/static/img/blog/28-gh-actions-reporter.png index 94d7a3d93a4b..37911aecde24 100644 Binary files a/website/static/img/blog/28-gh-actions-reporter.png and b/website/static/img/blog/28-gh-actions-reporter.png differ diff --git a/website/static/img/blog/Scheduling1.png b/website/static/img/blog/Scheduling1.png index 71f0425efcca..f8dee07a1fc6 100644 Binary files a/website/static/img/blog/Scheduling1.png and b/website/static/img/blog/Scheduling1.png differ diff --git a/website/static/img/blog/Scheduling2.png b/website/static/img/blog/Scheduling2.png index 827b0a7216bc..d38e88717a92 100644 Binary files a/website/static/img/blog/Scheduling2.png and b/website/static/img/blog/Scheduling2.png differ diff --git a/website/static/img/blog/collective.png b/website/static/img/blog/collective.png index 54a182aec1b7..f110156cff74 100644 Binary files a/website/static/img/blog/collective.png and b/website/static/img/blog/collective.png differ diff --git a/website/static/img/blog/openjs.png b/website/static/img/blog/openjs.png index c902eab79e66..28abddbb3970 100644 Binary files a/website/static/img/blog/openjs.png and b/website/static/img/blog/openjs.png differ diff --git a/website/static/img/blog/snapshot.png b/website/static/img/blog/snapshot.png index 7e3f7be9e865..d1d4d64371a7 100644 Binary files a/website/static/img/blog/snapshot.png and b/website/static/img/blog/snapshot.png differ diff --git a/website/static/img/circus.png b/website/static/img/circus.png index 0fa1c82a51ac..e897795a9397 100644 Binary files a/website/static/img/circus.png and b/website/static/img/circus.png differ diff --git a/website/static/img/content/camera-with-flash.png b/website/static/img/content/camera-with-flash.png index e69ee7ea9b63..f25bc640f3ff 100644 Binary files a/website/static/img/content/camera-with-flash.png and b/website/static/img/content/camera-with-flash.png differ diff --git a/website/static/img/content/failedSnapshotTest.png b/website/static/img/content/failedSnapshotTest.png index f89acea8ac3d..edf5d6bebb0b 100644 Binary files a/website/static/img/content/failedSnapshotTest.png and b/website/static/img/content/failedSnapshotTest.png differ diff --git a/website/static/img/content/feature-coverage.png b/website/static/img/content/feature-coverage.png index e50e286d0178..287ce9afb145 100644 Binary files a/website/static/img/content/feature-coverage.png and b/website/static/img/content/feature-coverage.png differ diff --git a/website/static/img/content/feature-fast.png b/website/static/img/content/feature-fast.png index 3491675e264e..db1e3253860c 100644 Binary files a/website/static/img/content/feature-fast.png and b/website/static/img/content/feature-fast.png differ diff --git a/website/static/img/content/feature-mocking.png b/website/static/img/content/feature-mocking.png index b32d4dca35f7..e479e457ae73 100644 Binary files a/website/static/img/content/feature-mocking.png and b/website/static/img/content/feature-mocking.png differ diff --git a/website/static/img/content/female-technologist.png b/website/static/img/content/female-technologist.png index e17dea00b384..9e3b40822643 100644 Binary files a/website/static/img/content/female-technologist.png and b/website/static/img/content/female-technologist.png differ diff --git a/website/static/img/content/interactiveSnapshot.png b/website/static/img/content/interactiveSnapshot.png index 1155b8750c40..98e1508a577d 100644 Binary files a/website/static/img/content/interactiveSnapshot.png and b/website/static/img/content/interactiveSnapshot.png differ diff --git a/website/static/img/content/interactiveSnapshotDone.png b/website/static/img/content/interactiveSnapshotDone.png index 079bb300754f..79687ec9f772 100644 Binary files a/website/static/img/content/interactiveSnapshotDone.png and b/website/static/img/content/interactiveSnapshotDone.png differ diff --git a/website/static/img/content/joker.png b/website/static/img/content/joker.png index 9a065b6eeb74..36b8a2e5e323 100644 Binary files a/website/static/img/content/joker.png and b/website/static/img/content/joker.png differ diff --git a/website/static/img/content/matchers/toBe.png b/website/static/img/content/matchers/toBe.png index e08bface2def..8819f0ddccdd 100644 Binary files a/website/static/img/content/matchers/toBe.png and b/website/static/img/content/matchers/toBe.png differ diff --git a/website/static/img/content/matchers/toBeCloseTo.png b/website/static/img/content/matchers/toBeCloseTo.png index 93402fbbb29e..6809d4d459c4 100644 Binary files a/website/static/img/content/matchers/toBeCloseTo.png and b/website/static/img/content/matchers/toBeCloseTo.png differ diff --git a/website/static/img/content/matchers/toEqual.png b/website/static/img/content/matchers/toEqual.png index 4465fd50dd14..7626e211c878 100644 Binary files a/website/static/img/content/matchers/toEqual.png and b/website/static/img/content/matchers/toEqual.png differ diff --git a/website/static/img/content/matchers/toHaveProperty.png b/website/static/img/content/matchers/toHaveProperty.png index ed647981fa04..b01453ec1a3c 100644 Binary files a/website/static/img/content/matchers/toHaveProperty.png and b/website/static/img/content/matchers/toHaveProperty.png differ diff --git a/website/static/img/content/matchers/toMatchSnapshot.png b/website/static/img/content/matchers/toMatchSnapshot.png index 7ba4d211f450..6c062088be6e 100644 Binary files a/website/static/img/content/matchers/toMatchSnapshot.png and b/website/static/img/content/matchers/toMatchSnapshot.png differ diff --git a/website/static/img/content/matchers/toStrictEqual.png b/website/static/img/content/matchers/toStrictEqual.png index 980ce4ed06bb..11cd4abdddb3 100644 Binary files a/website/static/img/content/matchers/toStrictEqual.png and b/website/static/img/content/matchers/toStrictEqual.png differ diff --git a/website/static/img/content/matchers/toThrowError.png b/website/static/img/content/matchers/toThrowError.png index 86fdd4ac9de1..fa563c8c9778 100644 Binary files a/website/static/img/content/matchers/toThrowError.png and b/website/static/img/content/matchers/toThrowError.png differ diff --git a/website/static/img/content/runner.png b/website/static/img/content/runner.png index 3d8a78c1fcba..023b81d1c254 100644 Binary files a/website/static/img/content/runner.png and b/website/static/img/content/runner.png differ diff --git a/website/static/img/favicon/android-chrome-192x192.png b/website/static/img/favicon/android-chrome-192x192.png index 3eb022ed6c4a..8ef786e423b1 100644 Binary files a/website/static/img/favicon/android-chrome-192x192.png and b/website/static/img/favicon/android-chrome-192x192.png differ diff --git a/website/static/img/favicon/android-chrome-512x512.png b/website/static/img/favicon/android-chrome-512x512.png index ef9a736393be..713ad5eb8e21 100644 Binary files a/website/static/img/favicon/android-chrome-512x512.png and b/website/static/img/favicon/android-chrome-512x512.png differ diff --git a/website/static/img/favicon/apple-touch-icon.png b/website/static/img/favicon/apple-touch-icon.png index d6aa8a620332..6f51750ed3d5 100644 Binary files a/website/static/img/favicon/apple-touch-icon.png and b/website/static/img/favicon/apple-touch-icon.png differ diff --git a/website/static/img/favicon/favicon-16x16.png b/website/static/img/favicon/favicon-16x16.png index 64349597d76b..86f03685b78a 100644 Binary files a/website/static/img/favicon/favicon-16x16.png and b/website/static/img/favicon/favicon-16x16.png differ diff --git a/website/static/img/favicon/favicon-32x32.png b/website/static/img/favicon/favicon-32x32.png index 2d0a6d4ea8cb..374ba24284a0 100644 Binary files a/website/static/img/favicon/favicon-32x32.png and b/website/static/img/favicon/favicon-32x32.png differ diff --git a/website/static/img/favicon/mstile-144x144.png b/website/static/img/favicon/mstile-144x144.png index 2dbfdb95820b..fde953530c6f 100644 Binary files a/website/static/img/favicon/mstile-144x144.png and b/website/static/img/favicon/mstile-144x144.png differ diff --git a/website/static/img/favicon/mstile-150x150.png b/website/static/img/favicon/mstile-150x150.png index 1b59af16b428..6e03408eb17b 100644 Binary files a/website/static/img/favicon/mstile-150x150.png and b/website/static/img/favicon/mstile-150x150.png differ diff --git a/website/static/img/favicon/mstile-310x150.png b/website/static/img/favicon/mstile-310x150.png index 0f1eb0ca588d..782bb4f1b037 100644 Binary files a/website/static/img/favicon/mstile-310x150.png and b/website/static/img/favicon/mstile-310x150.png differ diff --git a/website/static/img/favicon/mstile-310x310.png b/website/static/img/favicon/mstile-310x310.png index f8b2d7ada5bd..2689ef5880eb 100644 Binary files a/website/static/img/favicon/mstile-310x310.png and b/website/static/img/favicon/mstile-310x310.png differ diff --git a/website/static/img/favicon/mstile-70x70.png b/website/static/img/favicon/mstile-70x70.png index e22a9928bedd..d023d001a334 100644 Binary files a/website/static/img/favicon/mstile-70x70.png and b/website/static/img/favicon/mstile-70x70.png differ diff --git a/website/static/img/jest-readme-headline.png b/website/static/img/jest-readme-headline.png index d3344284430a..ae51fcc3222a 100644 Binary files a/website/static/img/jest-readme-headline.png and b/website/static/img/jest-readme-headline.png differ diff --git a/website/static/img/jest.png b/website/static/img/jest.png index 2b59238d2924..4dd08447cfab 100644 Binary files a/website/static/img/jest.png and b/website/static/img/jest.png differ diff --git a/website/static/img/logos/airbnb.png b/website/static/img/logos/airbnb.png index d2ef2334597c..234e0eb66826 100644 Binary files a/website/static/img/logos/airbnb.png and b/website/static/img/logos/airbnb.png differ diff --git a/website/static/img/logos/facebook.png b/website/static/img/logos/facebook.png index 5a38e79fd825..3b8e17a58314 100644 Binary files a/website/static/img/logos/facebook.png and b/website/static/img/logos/facebook.png differ diff --git a/website/static/img/logos/instagram.png b/website/static/img/logos/instagram.png index fa9001570564..d72994acac50 100644 Binary files a/website/static/img/logos/instagram.png and b/website/static/img/logos/instagram.png differ diff --git a/website/static/img/logos/nyt.png b/website/static/img/logos/nyt.png index cc77b248a114..2cba8d58ff88 100644 Binary files a/website/static/img/logos/nyt.png and b/website/static/img/logos/nyt.png differ diff --git a/website/static/img/logos/spotify.png b/website/static/img/logos/spotify.png index 0b8a9da507a0..628dfb9e7e67 100644 Binary files a/website/static/img/logos/spotify.png and b/website/static/img/logos/spotify.png differ diff --git a/website/static/img/logos/twitter.png b/website/static/img/logos/twitter.png index f7631fef945d..5bb24f5c9387 100644 Binary files a/website/static/img/logos/twitter.png and b/website/static/img/logos/twitter.png differ diff --git a/website/static/img/opengraph.png b/website/static/img/opengraph.png index 1e78ee813049..7993842851a4 100644 Binary files a/website/static/img/opengraph.png and b/website/static/img/opengraph.png differ diff --git a/website/static/img/oss_logo.png b/website/static/img/oss_logo.png index 81923fc56250..1b34da033489 100644 Binary files a/website/static/img/oss_logo.png and b/website/static/img/oss_logo.png differ diff --git a/website/static/img/running-card-background.png b/website/static/img/running-card-background.png index 81057071794e..05bc2e9b9724 100644 Binary files a/website/static/img/running-card-background.png and b/website/static/img/running-card-background.png differ