Skip to content

Commit

Permalink
chore: extend and improve type tests for jest object (#12442)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrazauskas authored Feb 21, 2022
1 parent 18bc882 commit 7a05a6c
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 72 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- `[jest-environment-node]` [**BREAKING**] Add default `node` and `node-addon` conditions to `exportConditions` for `node` environment ([#11924](https://github.com/facebook/jest/pull/11924))
- `[@jest/expect]` New module which extends `expect` with `jest-snapshot` matchers ([#12404](https://github.com/facebook/jest/pull/12404), [#12410](https://github.com/facebook/jest/pull/12410), [#12418](https://github.com/facebook/jest/pull/12418))
- `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323))
- `[jest-mock]` Improve `isMockFunction` to infer types of passed function ([#12442](https://github.com/facebook/jest/pull/12442))
- `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373))
- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392))
- `[@jest/schemas]` New module for JSON schemas for Jest's config ([#12384](https://github.com/facebook/jest/pull/12384))
Expand All @@ -27,6 +28,7 @@
- `[jest-haste-map]` Don't use partial results if file crawl errors ([#12420](https://github.com/facebook/jest/pull/12420))
- `[jest-jasmine2, jest-types]` [**BREAKING**] Move all `jasmine` specific types from `@jest/types` to its own package ([#12125](https://github.com/facebook/jest/pull/12125))
- `[jest-matcher-utils]` Pass maxWidth to `pretty-format` to avoid printing every element in arrays by default ([#12402](https://github.com/facebook/jest/pull/12402))
- `[jest-mock]` Fix function overloads for `spyOn` to allow more correct type inference in complex object ([#12442](https://github.com/facebook/jest/pull/12442))

### Chore & Maintenance

Expand Down
17 changes: 5 additions & 12 deletions packages/jest-environment/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@
import type {Context} from 'vm';
import type {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
import type {Circus, Config, Global} from '@jest/types';
import type {
fn as JestMockFn,
mocked as JestMockMocked,
spyOn as JestMockSpyOn,
ModuleMocker,
} from 'jest-mock';
import type {ModuleMocker} from 'jest-mock';

export type EnvironmentContext = {
console: Console;
Expand Down Expand Up @@ -110,7 +105,7 @@ export interface Jest {
/**
* Creates a mock function. Optionally takes a mock implementation.
*/
fn: typeof JestMockFn;
fn: ModuleMocker['fn'];
/**
* Given the name of a module, use the automatic mocking system to generate a
* mocked version of the module for you.
Expand All @@ -132,9 +127,7 @@ export interface Jest {
/**
* Determines if the given function is a mocked function.
*/
isMockFunction(
fn: (...args: Array<any>) => unknown,
): fn is ReturnType<typeof JestMockFn>;
isMockFunction: ModuleMocker['isMockFunction'];
/**
* Mocks a module with an auto-mocked version when it is being required.
*/
Expand Down Expand Up @@ -196,7 +189,7 @@ export interface Jest {
* jest.spyOn; other mocks will require you to manually restore them.
*/
restoreAllMocks(): Jest;
mocked: typeof JestMockMocked;
mocked: ModuleMocker['mocked'];
/**
* Runs failed tests n-times until they pass or until the max number of
* retries is exhausted. This only works with `jest-circus`!
Expand Down Expand Up @@ -259,7 +252,7 @@ export interface Jest {
* Note: By default, jest.spyOn also calls the spied method. This is
* different behavior from most other test libraries.
*/
spyOn: typeof JestMockSpyOn;
spyOn: ModuleMocker['spyOn'];
/**
* Indicates that the module system should never return a mocked version of
* the specified module from require() (e.g. that it should always return the
Expand Down
32 changes: 23 additions & 9 deletions packages/jest-mock/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: Array<any>) => any ? K : never;
}[keyof T] &
string;
type ConstructorPropertyNames<T> = {
[K in keyof T]: T[K] extends new (...args: Array<any>) => any ? K : never;
}[keyof T] &
string;

const MOCK_CONSTRUCTOR_NAME = 'mockConstructor';

Expand Down Expand Up @@ -988,6 +992,10 @@ export class ModuleMocker {
return metadata;
}

isMockFunction<T, Y extends Array<unknown> = Array<unknown>>(
fn: (...args: Y) => T,
): fn is Mock<T, Y>;
isMockFunction(fn: unknown): fn is Mock<unknown>;
isMockFunction<T>(fn: unknown): fn is Mock<T> {
return !!fn && (fn as any)._isMockFunction === true;
}
Expand All @@ -1003,27 +1011,34 @@ export class ModuleMocker {
return fn;
}

spyOn<T extends {}, M extends NonFunctionPropertyNames<T>>(
spyOn<T extends object, M extends NonFunctionPropertyNames<T>>(
object: T,
methodName: M,
accessType: 'get',
): SpyInstance<T[M], []>;

spyOn<T extends {}, M extends NonFunctionPropertyNames<T>>(
spyOn<T extends object, M extends NonFunctionPropertyNames<T>>(
object: T,
methodName: M,
accessType: 'set',
): SpyInstance<void, [T[M]]>;

spyOn<T extends {}, M extends FunctionPropertyNames<T>>(
spyOn<T extends object, M extends ConstructorPropertyNames<Required<T>>>(
object: T,
methodName: M,
): T[M] extends new (...args: Array<any>) => any
? SpyInstance<InstanceType<T[M]>, ConstructorParameters<T[M]>>
: never;

spyOn<T extends object, M extends FunctionPropertyNames<T>>(
object: T,
methodName: M,
): T[M] extends (...args: Array<any>) => any
? SpyInstance<ReturnType<T[M]>, Parameters<T[M]>>
: never;

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
spyOn<T extends {}, M extends NonFunctionPropertyNames<T>>(
spyOn<T extends object, M extends NonFunctionPropertyNames<T>>(
object: T,
methodName: M,
accessType?: 'get' | 'set',
Expand Down Expand Up @@ -1094,11 +1109,10 @@ export class ModuleMocker {
return object[methodName];
}

private _spyOnProperty<T extends {}, M extends NonFunctionPropertyNames<T>>(
obj: T,
propertyName: M,
accessType: 'get' | 'set' = 'get',
): Mock<T> {
private _spyOnProperty<
T extends object,
M extends NonFunctionPropertyNames<T>,
>(obj: T, propertyName: M, accessType: 'get' | 'set' = 'get'): Mock<T> {
if (typeof obj !== 'object' && typeof obj !== 'function') {
throw new Error(
'Cannot spyOn on a primitive value; ' + this._typeOf(obj) + ' given',
Expand Down
Loading

0 comments on commit 7a05a6c

Please sign in to comment.