getJasmineRequireObj().SpyRegistry = function(j$) { const spyOnMsg = j$.formatErrorMsg( '', 'spyOn(, )' ); const spyOnPropertyMsg = j$.formatErrorMsg( '', 'spyOnProperty(, , [accessType])' ); function SpyRegistry(options) { options = options || {}; const global = options.global || j$.getGlobal(); const createSpy = options.createSpy; const currentSpies = options.currentSpies || function() { return []; }; this.allowRespy = function(allow) { this.respy = allow; }; this.spyOn = function(obj, methodName) { const getErrorMsg = spyOnMsg; if (j$.util.isUndefined(obj) || obj === null) { throw new Error( getErrorMsg( 'could not find an object to spy upon for ' + methodName + '()' ) ); } if (j$.util.isUndefined(methodName) || methodName === null) { throw new Error(getErrorMsg('No method name supplied')); } if (j$.util.isUndefined(obj[methodName])) { throw new Error(getErrorMsg(methodName + '() method does not exist')); } if (obj[methodName] && j$.isSpy(obj[methodName])) { if (this.respy) { return obj[methodName]; } else { throw new Error( getErrorMsg(methodName + ' has already been spied upon') ); } } const descriptor = Object.getOwnPropertyDescriptor(obj, methodName); 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) || (obj === global && methodName === 'onerror') ) { restoreStrategy = function() { obj[methodName] = originalMethod; }; } else { restoreStrategy = function() { if (!delete obj[methodName]) { obj[methodName] = originalMethod; } }; } currentSpies().push({ restoreObjectToOriginalState: restoreStrategy }); obj[methodName] = spiedMethod; return spiedMethod; }; this.spyOnProperty = function(obj, propertyName, accessType) { const getErrorMsg = spyOnPropertyMsg; accessType = accessType || 'get'; if (j$.util.isUndefined(obj)) { throw new Error( getErrorMsg( 'spyOn could not find an object to spy upon for ' + propertyName + '' ) ); } if (j$.util.isUndefined(propertyName)) { throw new Error(getErrorMsg('No property name supplied')); } const descriptor = j$.util.getPropertyDescriptor(obj, propertyName); 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 (j$.isSpy(descriptor[accessType])) { if (this.respy) { return descriptor[accessType]; } else { throw new Error( getErrorMsg( propertyName + '#' + accessType + ' has already been spied upon' ) ); } } const originalDescriptor = j$.util.clone(descriptor); const spy = 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 }); descriptor[accessType] = spy; Object.defineProperty(obj, propertyName, descriptor); return spy; }; this.spyOnAllFunctions = function(obj, includeNonEnumerable) { if (j$.util.isUndefined(obj)) { throw new Error( 'spyOnAllFunctions could not find an object to spy upon' ); } let pointer = obj, propsToSpyOn = [], properties, propertiesToSkip = []; while ( pointer && (!includeNonEnumerable || pointer !== Object.prototype) ) { properties = getProps(pointer, includeNonEnumerable); properties = properties.filter(function(prop) { return propertiesToSkip.indexOf(prop) === -1; }); propertiesToSkip = propertiesToSkip.concat(properties); propsToSpyOn = propsToSpyOn.concat( getSpyableFunctionProps(pointer, properties) ); pointer = Object.getPrototypeOf(pointer); } for (const prop of propsToSpyOn) { this.spyOn(obj, prop); } return obj; }; this.clearSpies = function() { const spies = currentSpies(); for (let i = spies.length - 1; i >= 0; i--) { const spyEntry = spies[i]; spyEntry.restoreObjectToOriginalState(); } }; } function getProps(obj, includeNonEnumerable) { const enumerableProperties = Object.keys(obj); if (!includeNonEnumerable) { return enumerableProperties; } return Object.getOwnPropertyNames(obj).filter(function(prop) { return ( prop !== 'constructor' || enumerableProperties.indexOf('constructor') > -1 ); }); } function getSpyableFunctionProps(obj, propertiesToCheck) { const props = []; for (const prop of propertiesToCheck) { if ( Object.prototype.hasOwnProperty.call(obj, prop) && isSpyableProp(obj, prop) ) { props.push(prop); } } return props; } function isSpyableProp(obj, prop) { let value; try { value = obj[prop]; } catch (e) { return false; } if (value instanceof Function) { const descriptor = Object.getOwnPropertyDescriptor(obj, prop); return (descriptor.writable || descriptor.set) && descriptor.configurable; } return false; } return SpyRegistry; };