in packages/jest-jasmine2/src/jasmine/spyRegistry.ts [67:233]
private _spyOnProperty: (
obj: Record<string, Spy>,
propertyName: string,
accessType: keyof PropertyDescriptor,
) => Spy;
constructor({
currentSpies = () => [],
}: {
currentSpies?: () => Array<Spy>;
} = {}) {
this.allowRespy = function (allow) {
this.respy = allow;
};
this.spyOn = (obj, methodName, accessType) => {
if (accessType) {
return this._spyOnProperty(obj, methodName, accessType);
}
if (obj === void 0) {
throw new Error(
getErrorMsg(
`could not find an object to spy upon for ${methodName}()`,
),
);
}
if (methodName === void 0) {
throw new Error(getErrorMsg('No method name supplied'));
}
if (obj[methodName] === void 0) {
throw new Error(getErrorMsg(`${methodName}() method does not exist`));
}
if (obj[methodName] && isSpy(obj[methodName])) {
if (this.respy) {
return obj[methodName];
} else {
throw new Error(
getErrorMsg(`${methodName} has already been spied upon`),
);
}
}
let descriptor;
try {
descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
} catch {
// IE 8 doesn't support `definePropery` on non-DOM nodes
}
if (descriptor && !(descriptor.writable || descriptor.set)) {
throw new Error(
getErrorMsg(
`${methodName} is not declared writable or has no setter`,
),
);
}
const originalMethod = obj[methodName];
const spiedMethod = createSpy(methodName, originalMethod);
let restoreStrategy;
if (Object.prototype.hasOwnProperty.call(obj, methodName)) {
restoreStrategy = function () {
obj[methodName] = originalMethod;
};
} else {
restoreStrategy = function () {
if (!delete obj[methodName]) {
obj[methodName] = originalMethod;
}
};
}
currentSpies().push({
restoreObjectToOriginalState: restoreStrategy,
} as Spy);
obj[methodName] = spiedMethod;
return spiedMethod;
};
this._spyOnProperty = function (obj, propertyName, accessType = 'get') {
if (!obj) {
throw new Error(
getErrorMsg(
`could not find an object to spy upon for ${propertyName}`,
),
);
}
if (!propertyName) {
throw new Error(getErrorMsg('No property name supplied'));
}
let descriptor: PropertyDescriptor | undefined;
try {
descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
} catch {
// IE 8 doesn't support `definePropery` on non-DOM nodes
}
if (!descriptor) {
throw new Error(getErrorMsg(`${propertyName} property does not exist`));
}
if (!descriptor.configurable) {
throw new Error(
getErrorMsg(`${propertyName} is not declared configurable`),
);
}
if (!descriptor[accessType]) {
throw new Error(
getErrorMsg(
`Property ${propertyName} does not have access type ${accessType}`,
),
);
}
if (obj[propertyName] && isSpy(obj[propertyName])) {
if (this.respy) {
return obj[propertyName];
} else {
throw new Error(
getErrorMsg(`${propertyName} has already been spied upon`),
);
}
}
const originalDescriptor = descriptor;
const spiedProperty = createSpy(propertyName, descriptor[accessType]);
let restoreStrategy;
if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {
restoreStrategy = function () {
Object.defineProperty(obj, propertyName, originalDescriptor);
};
} else {
restoreStrategy = function () {
delete obj[propertyName];
};
}
currentSpies().push({
restoreObjectToOriginalState: restoreStrategy,
} as Spy);
const spiedDescriptor = {...descriptor, [accessType]: spiedProperty};
Object.defineProperty(obj, propertyName, spiedDescriptor);
return spiedProperty;
};
this.clearSpies = function () {
const spies = currentSpies();
for (let i = spies.length - 1; i >= 0; i--) {
const spyEntry = spies[i];
spyEntry.restoreObjectToOriginalState!();
}
};
}