Add optional param to spyOnAllFunctions to include non-enumerable properties
This commit is contained in:
@@ -407,6 +407,123 @@ describe('SpyRegistry', function() {
|
||||
expect(subject.toString).not.toBe('I am a spy');
|
||||
expect(subject.hasOwnProperty).not.toBe('I am a spy');
|
||||
});
|
||||
describe('when includeNonEnumerable is true', function() {
|
||||
it('does not override Object.prototype methods', function() {
|
||||
var spyRegistry = new jasmineUnderTest.SpyRegistry({
|
||||
createSpy: function() {
|
||||
return 'I am a spy';
|
||||
}
|
||||
});
|
||||
var subject = {
|
||||
spied1: function() {}
|
||||
};
|
||||
|
||||
spyRegistry.spyOnAllFunctions(subject, true);
|
||||
|
||||
expect(subject.spied1).toBe('I am a spy');
|
||||
expect(subject.toString).not.toBe('I am a spy');
|
||||
expect(subject.hasOwnProperty).not.toBe('I am a spy');
|
||||
});
|
||||
|
||||
it('overrides non-enumerable properties', function() {
|
||||
var spyRegistry = new jasmineUnderTest.SpyRegistry({
|
||||
createSpy: function() {
|
||||
return 'I am a spy';
|
||||
}
|
||||
});
|
||||
var subject = {
|
||||
spied1: function() {},
|
||||
spied2: function() {}
|
||||
};
|
||||
|
||||
Object.defineProperty(subject, 'spied2', {
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
spyRegistry.spyOnAllFunctions(subject, true);
|
||||
|
||||
expect(subject.spied1).toBe('I am a spy');
|
||||
expect(subject.spied2).toBe('I am a spy');
|
||||
});
|
||||
|
||||
it('should not spy on non-enumerable functions named constructor', function() {
|
||||
var spyRegistry = new jasmineUnderTest.SpyRegistry({
|
||||
createSpy: function() {
|
||||
return 'I am a spy';
|
||||
}
|
||||
});
|
||||
var subject = {
|
||||
constructor: function() {}
|
||||
};
|
||||
|
||||
Object.defineProperty(subject, 'constructor', {
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
spyRegistry.spyOnAllFunctions(subject, true);
|
||||
|
||||
expect(subject.constructor).not.toBe('I am a spy');
|
||||
});
|
||||
|
||||
it('should spy on enumerable functions named constructor', function() {
|
||||
var spyRegistry = new jasmineUnderTest.SpyRegistry({
|
||||
createSpy: function() {
|
||||
return 'I am a spy';
|
||||
}
|
||||
});
|
||||
var subject = {
|
||||
constructor: function() {}
|
||||
};
|
||||
|
||||
spyRegistry.spyOnAllFunctions(subject, true);
|
||||
|
||||
expect(subject.constructor).toBe('I am a spy');
|
||||
});
|
||||
|
||||
it('should not throw an exception if we try and access strict mode restricted properties', function() {
|
||||
var spyRegistry = new jasmineUnderTest.SpyRegistry({
|
||||
createSpy: function() {
|
||||
return 'I am a spy';
|
||||
}
|
||||
});
|
||||
var subject = function() {};
|
||||
var fn = function() {
|
||||
spyRegistry.spyOnAllFunctions(subject, true);
|
||||
};
|
||||
|
||||
expect(fn).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not spy on properties which are more permissable further up the prototype chain', function() {
|
||||
var spyRegistry = new jasmineUnderTest.SpyRegistry({
|
||||
createSpy: function() {
|
||||
return 'I am a spy';
|
||||
}
|
||||
});
|
||||
var subjectParent = Object.defineProperty({}, 'sharedProp', {
|
||||
value: function() {},
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
var subject = Object.create(subjectParent);
|
||||
|
||||
Object.defineProperty(subject, 'sharedProp', {
|
||||
value: function() {}
|
||||
});
|
||||
|
||||
var fn = function() {
|
||||
spyRegistry.spyOnAllFunctions(subject, true);
|
||||
};
|
||||
|
||||
expect(fn).not.toThrow();
|
||||
expect(subject).not.toBe('I am a spy');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#clearSpies', function() {
|
||||
|
||||
@@ -163,7 +163,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
|
||||
return spy;
|
||||
};
|
||||
|
||||
this.spyOnAllFunctions = function(obj) {
|
||||
this.spyOnAllFunctions = function(obj, includeNonEnumerable) {
|
||||
if (j$.util.isUndefined(obj)) {
|
||||
throw new Error(
|
||||
'spyOnAllFunctions could not find an object to spy upon'
|
||||
@@ -171,30 +171,27 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
|
||||
}
|
||||
|
||||
var pointer = obj,
|
||||
props = [],
|
||||
prop,
|
||||
descriptor;
|
||||
propsToSpyOn = [],
|
||||
properties,
|
||||
propertiesToSkip = [];
|
||||
|
||||
while (pointer) {
|
||||
for (prop in pointer) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(pointer, prop) &&
|
||||
pointer[prop] instanceof Function
|
||||
) {
|
||||
descriptor = Object.getOwnPropertyDescriptor(pointer, prop);
|
||||
if (
|
||||
(descriptor.writable || descriptor.set) &&
|
||||
descriptor.configurable
|
||||
) {
|
||||
props.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (var i = 0; i < props.length; i++) {
|
||||
this.spyOn(obj, props[i]);
|
||||
for (var i = 0; i < propsToSpyOn.length; i++) {
|
||||
this.spyOn(obj, propsToSpyOn[i]);
|
||||
}
|
||||
|
||||
return obj;
|
||||
@@ -209,5 +206,49 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
|
||||
};
|
||||
}
|
||||
|
||||
function getProps(obj, includeNonEnumerable) {
|
||||
var 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) {
|
||||
var props = [],
|
||||
prop;
|
||||
for (var i = 0; i < propertiesToCheck.length; i++) {
|
||||
prop = propertiesToCheck[i];
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(obj, prop) &&
|
||||
isSpyableProp(obj, prop)
|
||||
) {
|
||||
props.push(prop);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
function isSpyableProp(obj, prop) {
|
||||
var value, descriptor;
|
||||
try {
|
||||
value = obj[prop];
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof Function) {
|
||||
descriptor = Object.getOwnPropertyDescriptor(obj, prop);
|
||||
return (descriptor.writable || descriptor.set) && descriptor.configurable;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return SpyRegistry;
|
||||
};
|
||||
|
||||
@@ -285,10 +285,11 @@ getJasmineRequireObj().interface = function(jasmine, env) {
|
||||
* @function
|
||||
* @global
|
||||
* @param {Object} obj - The object upon which to install the {@link Spy}s
|
||||
* @param {boolean} includeNonEnumerable - Whether or not to add spies to non-enumerable properties
|
||||
* @returns {Object} the spied object
|
||||
*/
|
||||
spyOnAllFunctions: function(obj) {
|
||||
return env.spyOnAllFunctions(obj);
|
||||
spyOnAllFunctions: function(obj, includeNonEnumerable) {
|
||||
return env.spyOnAllFunctions(obj, includeNonEnumerable);
|
||||
},
|
||||
|
||||
jsApiReporter: new jasmine.JsApiReporter({
|
||||
|
||||
Reference in New Issue
Block a user