-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(jest-mock): improve spyOn
typings to handle optional properties
#13247
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1008,40 +1008,48 @@ export class ModuleMocker { | |
return fn; | ||
} | ||
|
||
spyOn<T extends object, M extends PropertyLikeKeys<T>>( | ||
spyOn< | ||
T extends object, | ||
K extends PropertyLikeKeys<Required<T>>, | ||
V extends Required<T>[K], | ||
>(object: T, methodKey: K, accessType: 'get'): SpyInstance<() => V>; | ||
|
||
spyOn< | ||
T extends object, | ||
K extends PropertyLikeKeys<Required<T>>, | ||
V extends Required<T>[K], | ||
>(object: T, methodKey: K, accessType: 'set'): SpyInstance<(arg: V) => void>; | ||
|
||
spyOn< | ||
T extends object, | ||
K extends ConstructorLikeKeys<Required<T>>, | ||
V extends Required<T>[K], | ||
>( | ||
object: T, | ||
methodName: M, | ||
accessType: 'get', | ||
): SpyInstance<() => T[M]>; | ||
|
||
spyOn<T extends object, M extends PropertyLikeKeys<T>>( | ||
object: T, | ||
methodName: M, | ||
accessType: 'set', | ||
): SpyInstance<(arg: T[M]) => void>; | ||
|
||
spyOn<T extends object, M extends ConstructorLikeKeys<T>>( | ||
object: T, | ||
methodName: M, | ||
): T[M] extends ClassLike | ||
? SpyInstance<(...args: ConstructorParameters<T[M]>) => InstanceType<T[M]>> | ||
methodKey: K, | ||
): V extends ClassLike | ||
? SpyInstance<(...args: ConstructorParameters<V>) => InstanceType<V>> | ||
: never; | ||
|
||
spyOn<T extends object, M extends MethodLikeKeys<T>>( | ||
spyOn< | ||
T extends object, | ||
K extends MethodLikeKeys<Required<T>>, | ||
V extends Required<T>[K], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lines with |
||
>( | ||
object: T, | ||
methodName: M, | ||
): T[M] extends FunctionLike | ||
? SpyInstance<(...args: Parameters<T[M]>) => ReturnType<T[M]>> | ||
methodKey: K, | ||
): V extends FunctionLike | ||
? SpyInstance<(...args: Parameters<V>) => ReturnType<V>> | ||
: never; | ||
|
||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||
spyOn<T extends object, M extends PropertyLikeKeys<T>>( | ||
spyOn<T extends object, K extends PropertyLikeKeys<T>>( | ||
object: T, | ||
methodName: M, | ||
methodKey: K, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
accessType?: 'get' | 'set', | ||
) { | ||
if (accessType) { | ||
return this._spyOnProperty(object, methodName, accessType); | ||
return this._spyOnProperty(object, methodKey, accessType); | ||
} | ||
|
||
if (typeof object !== 'object' && typeof object !== 'function') { | ||
|
@@ -1050,13 +1058,13 @@ export class ModuleMocker { | |
); | ||
} | ||
|
||
const original = object[methodName]; | ||
const original = object[methodKey]; | ||
|
||
if (!this.isMockFunction(original)) { | ||
if (typeof original !== 'function') { | ||
throw new Error( | ||
`Cannot spy the ${String( | ||
methodName, | ||
methodKey, | ||
)} property because it is not a function; ${this._typeOf( | ||
original, | ||
)} given instead`, | ||
|
@@ -1065,14 +1073,14 @@ export class ModuleMocker { | |
|
||
const isMethodOwner = Object.prototype.hasOwnProperty.call( | ||
object, | ||
methodName, | ||
methodKey, | ||
); | ||
|
||
let descriptor = Object.getOwnPropertyDescriptor(object, methodName); | ||
let descriptor = Object.getOwnPropertyDescriptor(object, methodKey); | ||
let proto = Object.getPrototypeOf(object); | ||
|
||
while (!descriptor && proto !== null) { | ||
descriptor = Object.getOwnPropertyDescriptor(proto, methodName); | ||
descriptor = Object.getOwnPropertyDescriptor(proto, methodKey); | ||
proto = Object.getPrototypeOf(proto); | ||
} | ||
|
||
|
@@ -1082,34 +1090,34 @@ export class ModuleMocker { | |
const originalGet = descriptor.get; | ||
mock = this._makeComponent({type: 'function'}, () => { | ||
descriptor!.get = originalGet; | ||
Object.defineProperty(object, methodName, descriptor!); | ||
Object.defineProperty(object, methodKey, descriptor!); | ||
}); | ||
descriptor.get = () => mock; | ||
Object.defineProperty(object, methodName, descriptor); | ||
Object.defineProperty(object, methodKey, descriptor); | ||
} else { | ||
mock = this._makeComponent({type: 'function'}, () => { | ||
if (isMethodOwner) { | ||
object[methodName] = original; | ||
object[methodKey] = original; | ||
} else { | ||
delete object[methodName]; | ||
delete object[methodKey]; | ||
} | ||
}); | ||
// @ts-expect-error overriding original method with a Mock | ||
object[methodName] = mock; | ||
object[methodKey] = mock; | ||
} | ||
|
||
mock.mockImplementation(function (this: unknown) { | ||
return original.apply(this, arguments); | ||
}); | ||
} | ||
|
||
return object[methodName]; | ||
return object[methodKey]; | ||
} | ||
|
||
private _spyOnProperty<T extends object, M extends PropertyLikeKeys<T>>( | ||
private _spyOnProperty<T extends object, K extends PropertyLikeKeys<T>>( | ||
obj: T, | ||
propertyName: M, | ||
accessType: 'get' | 'set' = 'get', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Default is unnecessary, |
||
propertyKey: K, | ||
accessType: 'get' | 'set', | ||
): Mock<() => T> { | ||
if (typeof obj !== 'object' && typeof obj !== 'function') { | ||
throw new Error( | ||
|
@@ -1119,36 +1127,34 @@ export class ModuleMocker { | |
|
||
if (!obj) { | ||
throw new Error( | ||
`spyOn could not find an object to spy upon for ${String( | ||
propertyName, | ||
)}`, | ||
`spyOn could not find an object to spy upon for ${String(propertyKey)}`, | ||
); | ||
} | ||
|
||
if (!propertyName) { | ||
if (!propertyKey) { | ||
throw new Error('No property name supplied'); | ||
} | ||
|
||
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); | ||
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyKey); | ||
let proto = Object.getPrototypeOf(obj); | ||
|
||
while (!descriptor && proto !== null) { | ||
descriptor = Object.getOwnPropertyDescriptor(proto, propertyName); | ||
descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey); | ||
proto = Object.getPrototypeOf(proto); | ||
} | ||
|
||
if (!descriptor) { | ||
throw new Error(`${String(propertyName)} property does not exist`); | ||
throw new Error(`${String(propertyKey)} property does not exist`); | ||
} | ||
|
||
if (!descriptor.configurable) { | ||
throw new Error(`${String(propertyName)} is not declared configurable`); | ||
throw new Error(`${String(propertyKey)} is not declared configurable`); | ||
} | ||
|
||
if (!descriptor[accessType]) { | ||
throw new Error( | ||
`Property ${String( | ||
propertyName, | ||
propertyKey, | ||
)} does not have access type ${accessType}`, | ||
); | ||
} | ||
|
@@ -1159,7 +1165,7 @@ export class ModuleMocker { | |
if (typeof original !== 'function') { | ||
throw new Error( | ||
`Cannot spy the ${String( | ||
propertyName, | ||
propertyKey, | ||
)} property because it is not a function; ${this._typeOf( | ||
original, | ||
)} given instead`, | ||
|
@@ -1169,7 +1175,7 @@ export class ModuleMocker { | |
descriptor[accessType] = this._makeComponent({type: 'function'}, () => { | ||
// @ts-expect-error: mock is assignable | ||
descriptor![accessType] = original; | ||
Object.defineProperty(obj, propertyName, descriptor!); | ||
Object.defineProperty(obj, propertyKey, descriptor!); | ||
}); | ||
|
||
(descriptor[accessType] as Mock<() => T>).mockImplementation(function ( | ||
|
@@ -1180,7 +1186,7 @@ export class ModuleMocker { | |
}); | ||
} | ||
|
||
Object.defineProperty(obj, propertyName, descriptor); | ||
Object.defineProperty(obj, propertyKey, descriptor); | ||
return descriptor[accessType] as Mock<() => T>; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
V
is just a helper to avoidSpyInstance<(...args: ConstructorParameters<Required<T>[K]>) => InstanceType<Required<T>[K]>>