Merge branch 'Eradev-issue-1991'

* Merges #2051 from @Eradev
* Fixes #1991
This commit is contained in:
Steve Gravrock
2025-01-20 11:30:12 -08:00
17 changed files with 513 additions and 24 deletions

View File

@@ -162,6 +162,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
'toHaveClass', 'toHaveClass',
'toHaveClasses', 'toHaveClasses',
'toHaveSpyInteractions', 'toHaveSpyInteractions',
'toHaveNoOtherSpyInteractions',
'toMatch', 'toMatch',
'toThrow', 'toThrow',
'toThrowError', 'toThrowError',
@@ -2898,6 +2899,10 @@ getJasmineRequireObj().CallTracker = function(j$) {
this.saveArgumentsByValue = function() { this.saveArgumentsByValue = function() {
opts.cloneArgs = true; opts.cloneArgs = true;
}; };
this.unverifiedCount = function() {
return calls.reduce((count, call) => count + (call.verified ? 0 : 1), 0);
};
} }
return CallTracker; return CallTracker;
@@ -6231,6 +6236,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
result.pass = actual.calls.any(); result.pass = actual.calls.any();
actual.calls.all().forEach(call => (call.verified = true));
result.message = result.pass result.message = result.pass
? 'Expected spy ' + actual.and.identity + ' not to have been called.' ? 'Expected spy ' + actual.and.identity + ' not to have been called.'
: 'Expected spy ' + actual.and.identity + ' to have been called.'; : 'Expected spy ' + actual.and.identity + ' to have been called.';
@@ -6295,6 +6302,9 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) {
result.pass = latest1stSpyCall < first2ndSpyCall; result.pass = latest1stSpyCall < first2ndSpyCall;
if (result.pass) { if (result.pass) {
firstSpy.calls.mostRecent().verified = true;
latterSpy.calls.first().verified = true;
result.message = result.message =
'Expected spy ' + 'Expected spy ' +
firstSpy.and.identity + firstSpy.and.identity +
@@ -6351,7 +6361,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
* @example * @example
* expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2); * expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2);
*/ */
function toHaveBeenCalledOnceWith(util) { function toHaveBeenCalledOnceWith(matchersUtil) {
return { return {
compare: function() { compare: function() {
const args = Array.prototype.slice.call(arguments, 0), const args = Array.prototype.slice.call(arguments, 0),
@@ -6360,20 +6370,29 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
if (!j$.isSpy(actual)) { if (!j$.isSpy(actual)) {
throw new Error( throw new Error(
getErrorMsg('Expected a spy, but got ' + util.pp(actual) + '.') getErrorMsg(
'Expected a spy, but got ' + matchersUtil.pp(actual) + '.'
)
); );
} }
const prettyPrintedCalls = actual.calls const prettyPrintedCalls = actual.calls
.allArgs() .allArgs()
.map(function(argsForCall) { .map(function(argsForCall) {
return ' ' + util.pp(argsForCall); return ' ' + matchersUtil.pp(argsForCall);
}); });
if ( if (
actual.calls.count() === 1 && actual.calls.count() === 1 &&
util.contains(actual.calls.allArgs(), expectedArgs) matchersUtil.contains(actual.calls.allArgs(), expectedArgs)
) { ) {
const firstIndex = actual.calls
.all()
.findIndex(call => matchersUtil.equals(call.args, expectedArgs));
if (firstIndex > -1) {
actual.calls.all()[firstIndex].verified = true;
}
return { return {
pass: true, pass: true,
message: message:
@@ -6381,7 +6400,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
actual.and.identity + actual.and.identity +
' to have been called 0 times, multiple times, or once, but with arguments different from:\n' + ' to have been called 0 times, multiple times, or once, but with arguments different from:\n' +
' ' + ' ' +
util.pp(expectedArgs) + matchersUtil.pp(expectedArgs) +
'\n' + '\n' +
'But the actual call was:\n' + 'But the actual call was:\n' +
prettyPrintedCalls.join(',\n') + prettyPrintedCalls.join(',\n') +
@@ -6392,7 +6411,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
function getDiffs() { function getDiffs() {
return actual.calls.allArgs().map(function(argsForCall, callIx) { return actual.calls.allArgs().map(function(argsForCall, callIx) {
const diffBuilder = new j$.DiffBuilder(); const diffBuilder = new j$.DiffBuilder();
util.equals(argsForCall, expectedArgs, diffBuilder); matchersUtil.equals(argsForCall, expectedArgs, diffBuilder);
return diffBuilder.getMessage(); return diffBuilder.getMessage();
}); });
} }
@@ -6425,7 +6444,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
actual.and.identity + actual.and.identity +
' to have been called only once, and with given args:\n' + ' to have been called only once, and with given args:\n' +
' ' + ' ' +
util.pp(expectedArgs) + matchersUtil.pp(expectedArgs) +
'\n' + '\n' +
butString() butString()
}; };
@@ -6474,23 +6493,35 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
} }
actual = args[0]; actual = args[0];
const calls = actual.calls.count();
const callsCount = actual.calls.count();
const timesMessage = expected === 1 ? 'once' : expected + ' times'; const timesMessage = expected === 1 ? 'once' : expected + ' times';
result.pass = calls === expected;
result.pass = callsCount === expected;
if (result.pass) {
const allCalls = actual.calls.all();
const max = Math.min(expected, callsCount);
for (let i = 0; i < max; i++) {
allCalls[i].verified = true;
}
}
result.message = result.pass result.message = result.pass
? 'Expected spy ' + ? 'Expected spy ' +
actual.and.identity + actual.and.identity +
' not to have been called ' + ' not to have been called ' +
timesMessage + timesMessage +
'. It was called ' + '. It was called ' +
calls + callsCount +
' times.' ' times.'
: 'Expected spy ' + : 'Expected spy ' +
actual.and.identity + actual.and.identity +
' to have been called ' + ' to have been called ' +
timesMessage + timesMessage +
'. It was called ' + '. It was called ' +
calls + callsCount +
' times.'; ' times.';
return result; return result;
} }
@@ -6546,6 +6577,11 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
} }
if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) { if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) {
actual.calls
.all()
.filter(call => matchersUtil.equals(call.args, expectedArgs))
.forEach(call => (call.verified = true));
result.pass = true; result.pass = true;
result.message = function() { result.message = function() {
return ( return (
@@ -6672,6 +6708,94 @@ getJasmineRequireObj().toHaveClasses = function(j$) {
return toHaveClasses; return toHaveClasses;
}; };
getJasmineRequireObj().toHaveNoOtherSpyInteractions = function(j$) {
const getErrorMsg = j$.formatErrorMsg(
'<toHaveNoOtherSpyInteractions>',
'expect(<spyObj>).toHaveNoOtherSpyInteractions()'
);
/**
* {@link expect} the actual (a {@link SpyObj}) spies to have not been called except interactions which was already tracked with `toHaveBeenCalled`.
* @function
* @name matchers#toHaveNoOtherSpyInteractions
* @example
* expect(mySpyObj).toHaveNoOtherSpyInteractions();
* expect(mySpyObj).not.toHaveNoOtherSpyInteractions();
*/
function toHaveNoOtherSpyInteractions(matchersUtil) {
return {
compare: function(actual) {
const result = {};
if (!j$.isObject_(actual)) {
throw new Error(
getErrorMsg('Expected an object, but got ' + typeof actual + '.')
);
}
if (arguments.length > 1) {
throw new Error(getErrorMsg('Does not take arguments'));
}
result.pass = true;
let hasSpy = false;
const unexpectedCalls = [];
for (const spy of Object.values(actual)) {
if (!j$.isSpy(spy)) {
continue;
}
hasSpy = true;
const unverifiedCalls = spy.calls
.all()
.filter(call => !call.verified);
if (unverifiedCalls.length > 0) {
result.pass = false;
}
unverifiedCalls.forEach(unverifiedCall => {
unexpectedCalls.push([
spy.and.identity,
matchersUtil.pp(unverifiedCall.args)
]);
});
}
if (!hasSpy) {
throw new Error(
getErrorMsg(
'Expected an object with spies, but object has no spies.'
)
);
}
if (result.pass) {
result.message =
"Expected to have other spy interactions but it didn't.";
} else {
const ppUnexpectedCalls = unexpectedCalls
.map(
([spyName, arguments]) => ` ${spyName} called with ${arguments}`
)
.join(',\n');
result.message =
'Expected to have no other spy interactions, but it had the following calls:\n' +
ppUnexpectedCalls +
'.\n\n';
}
return result;
}
};
}
return toHaveNoOtherSpyInteractions;
};
getJasmineRequireObj().toHaveSize = function(j$) { getJasmineRequireObj().toHaveSize = function(j$) {
/** /**
* {@link expect} the actual size to be equal to the expected, using array-like length or object keys size. * {@link expect} the actual size to be equal to the expected, using array-like length or object keys size.
@@ -9250,7 +9374,8 @@ getJasmineRequireObj().Spy = function(j$) {
const callData = { const callData = {
object: context, object: context,
invocationOrder: nextOrder(), invocationOrder: nextOrder(),
args: Array.prototype.slice.apply(args) args: Array.prototype.slice.apply(args),
verified: false
}; };
callTracker.track(callData); callTracker.track(callData);

View File

@@ -675,6 +675,23 @@ describe('Matchers (Integration)', function() {
}); });
}); });
describe('toHaveNoOtherSpyInteractions', function() {
let spyObj;
beforeEach(function() {
spyObj = env.createSpyObj('NewClass', ['spyA', 'spyB']);
});
verifyPasses(function(env) {
env.expect(spyObj).toHaveNoOtherSpyInteractions();
});
verifyFails(function(env) {
spyObj.spyA();
env.expect(spyObj).toHaveNoOtherSpyInteractions();
});
});
describe('toMatch', function() { describe('toMatch', function() {
verifyPasses(function(env) { verifyPasses(function(env) {
env.expect('foo').toMatch(/oo$/); env.expect('foo').toMatch(/oo$/);

View File

@@ -112,4 +112,20 @@ describe('toHaveBeenCalledBefore', function() {
'Expected spy first spy to not have been called before spy second spy, but it was' 'Expected spy first spy to not have been called before spy second spy, but it was'
); );
}); });
it('set the correct calls as verified when passing', function() {
const matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(),
firstSpy = new jasmineUnderTest.Spy('first spy'),
secondSpy = new jasmineUnderTest.Spy('second spy');
firstSpy();
secondSpy();
matcher.compare(firstSpy, secondSpy);
expect(firstSpy.calls.count()).toBe(1);
expect(firstSpy.calls.unverifiedCount()).toBe(0);
expect(secondSpy.calls.count()).toBe(1);
expect(secondSpy.calls.unverifiedCount()).toBe(0);
});
}); });

View File

@@ -105,4 +105,18 @@ describe('toHaveBeenCalledOnceWith', function() {
matcher.compare(fn); matcher.compare(fn);
}).toThrowError(/Expected a spy, but got Function./); }).toThrowError(/Expected a spy, but got Function./);
}); });
it('set the correct calls as verified when passing', function() {
const pp = jasmineUnderTest.makePrettyPrinter(),
util = new jasmineUnderTest.MatchersUtil({ pp: pp }),
matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util),
calledSpy = new jasmineUnderTest.Spy('called-spy');
calledSpy('x');
matcher.compare(calledSpy, 'x');
expect(calledSpy.calls.count()).toBe(1);
expect(calledSpy.calls.unverifiedCount()).toBe(0);
});
}); });

View File

@@ -50,4 +50,16 @@ describe('toHaveBeenCalled', function() {
'Expected spy sample-spy to have been called.' 'Expected spy sample-spy to have been called.'
); );
}); });
it('set the correct calls as verified when passing', function() {
const matcher = jasmineUnderTest.matchers.toHaveBeenCalled(),
spy = new jasmineUnderTest.Spy('sample-spy');
spy();
matcher.compare(spy);
expect(spy.calls.count()).toBe(1);
expect(spy.calls.unverifiedCount()).toBe(0);
});
}); });

View File

@@ -87,4 +87,17 @@ describe('toHaveBeenCalledTimes', function() {
' times.' ' times.'
); );
}); });
it('set the correct calls as verified when passing', function() {
const matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(),
spy = new jasmineUnderTest.Spy('sample-spy');
spy();
spy();
matcher.compare(spy, 2);
expect(spy.calls.count()).toBe(2);
expect(spy.calls.unverifiedCount()).toBe(0);
});
}); });

View File

@@ -2,6 +2,7 @@ describe('toHaveBeenCalledWith', function() {
it('passes when the actual was called with matching parameters', function() { it('passes when the actual was called with matching parameters', function() {
const matchersUtil = { const matchersUtil = {
contains: jasmine.createSpy('delegated-contains').and.returnValue(true), contains: jasmine.createSpy('delegated-contains').and.returnValue(true),
equals: jasmine.createSpy('delegated-equals').and.returnValue(true),
pp: jasmineUnderTest.makePrettyPrinter() pp: jasmineUnderTest.makePrettyPrinter()
}, },
matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil), matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil),
@@ -92,4 +93,20 @@ describe('toHaveBeenCalledWith', function() {
matcher.compare(fn); matcher.compare(fn);
}).toThrowError(/Expected a spy, but got Function./); }).toThrowError(/Expected a spy, but got Function./);
}); });
it('set the correct calls as verified when passing', function() {
const matchersUtil = {
contains: jasmine.createSpy('delegated-contains').and.returnValue(true),
equals: jasmine.createSpy('delegated-equals').and.returnValue(true),
pp: jasmineUnderTest.makePrettyPrinter()
},
matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil),
calledSpy = new jasmineUnderTest.Spy('called-spy');
calledSpy('a', 'b');
matcher.compare(calledSpy, 'a', 'b');
expect(calledSpy.calls.count()).toBe(1);
expect(calledSpy.calls.unverifiedCount()).toBe(0);
});
}); });

View File

@@ -0,0 +1,151 @@
describe('toHaveNoOtherSpyInteractions', function() {
it('passes when there are no spy interactions', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
let result = matcher.compare(spyObj);
expect(result.pass).toBeTrue();
});
it('passes when there are multiple spy interactions where checked by toHaveBeenCalled', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
let toHaveBeenCalledMatcher = jasmineUnderTest.matchers.toHaveBeenCalled();
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.spyA();
spyObj.spyB();
spyObj.spyA();
toHaveBeenCalledMatcher.compare(spyObj.spyA);
toHaveBeenCalledMatcher.compare(spyObj.spyB);
let result = matcher.compare(spyObj);
expect(result.pass).toBeTrue();
});
it('fails when there are spy interactions', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil({
pp: jasmineUnderTest.makePrettyPrinter()
});
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
matchersUtil
);
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.spyA('x');
let result = matcher.compare(spyObj);
expect(result.pass).toBeFalse();
expect(result.message).toContain(
'Expected to have no other spy interactions, but it had the following calls:'
);
});
it('shows the right message is negated', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil({
pp: jasmineUnderTest.makePrettyPrinter()
});
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
matchersUtil
);
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.spyA();
spyObj.spyB();
let result = matcher.compare(spyObj);
expect(result.pass).toBeFalse(),
expect(result.message).toContain(
'Expected to have no other spy interactions, but it had the following calls:'
);
});
it('passes when only non-observed spy object interactions are interacted', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.otherMethod = function() {};
spyObj.otherMethod();
let result = matcher.compare(spyObj);
expect(result.pass).toBeTrue();
expect(result.message).toContain(
"Expected to have other spy interactions but it didn't"
);
});
it(`throws an error if a non-object is passed`, function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
expect(function() {
matcher.compare(true);
}).toThrowError(Error, /Expected an object, but got/);
expect(function() {
matcher.compare(123);
}).toThrowError(Error, /Expected an object, but got/);
expect(function() {
matcher.compare('string');
}).toThrowError(Error, /Expected an object, but got/);
});
it('throws an error if arguments are passed', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
expect(function() {
matcher.compare(spyObj, 'an argument');
}).toThrowError(Error, /Does not take arguments/);
});
it('throws an error if the spy object has no spies', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
const spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['notSpy']);
// Removing spy since spy objects cannot be created without spies.
spyObj.notSpy = function() {};
expect(function() {
matcher.compare(spyObj);
}).toThrowError(
Error,
/Expected an object with spies, but object has no spies/
);
});
it('handles multiple interactions with a single spy', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil({
pp: jasmineUnderTest.makePrettyPrinter()
}),
matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
matchersUtil
),
toHaveBeenCalledWithMatcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(
matchersUtil
),
spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.spyA('x');
spyObj.spyA('y');
toHaveBeenCalledWithMatcher.compare(spyObj.spyA, 'x');
let result = matcher.compare(spyObj);
expect(result.pass).toBeFalse();
});
});

View File

@@ -125,6 +125,10 @@ getJasmineRequireObj().CallTracker = function(j$) {
this.saveArgumentsByValue = function() { this.saveArgumentsByValue = function() {
opts.cloneArgs = true; opts.cloneArgs = true;
}; };
this.unverifiedCount = function() {
return calls.reduce((count, call) => count + (call.verified ? 0 : 1), 0);
};
} }
return CallTracker; return CallTracker;

View File

@@ -26,7 +26,8 @@ getJasmineRequireObj().Spy = function(j$) {
const callData = { const callData = {
object: context, object: context,
invocationOrder: nextOrder(), invocationOrder: nextOrder(),
args: Array.prototype.slice.apply(args) args: Array.prototype.slice.apply(args),
verified: false
}; };
callTracker.track(callData); callTracker.track(callData);

View File

@@ -30,6 +30,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
'toHaveClass', 'toHaveClass',
'toHaveClasses', 'toHaveClasses',
'toHaveSpyInteractions', 'toHaveSpyInteractions',
'toHaveNoOtherSpyInteractions',
'toMatch', 'toMatch',
'toThrow', 'toThrow',
'toThrowError', 'toThrowError',

View File

@@ -34,6 +34,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
result.pass = actual.calls.any(); result.pass = actual.calls.any();
actual.calls.all().forEach(call => (call.verified = true));
result.message = result.pass result.message = result.pass
? 'Expected spy ' + actual.and.identity + ' not to have been called.' ? 'Expected spy ' + actual.and.identity + ' not to have been called.'
: 'Expected spy ' + actual.and.identity + ' to have been called.'; : 'Expected spy ' + actual.and.identity + ' to have been called.';

View File

@@ -50,6 +50,9 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) {
result.pass = latest1stSpyCall < first2ndSpyCall; result.pass = latest1stSpyCall < first2ndSpyCall;
if (result.pass) { if (result.pass) {
firstSpy.calls.mostRecent().verified = true;
latterSpy.calls.first().verified = true;
result.message = result.message =
'Expected spy ' + 'Expected spy ' +
firstSpy.and.identity + firstSpy.and.identity +

View File

@@ -13,7 +13,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
* @example * @example
* expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2); * expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2);
*/ */
function toHaveBeenCalledOnceWith(util) { function toHaveBeenCalledOnceWith(matchersUtil) {
return { return {
compare: function() { compare: function() {
const args = Array.prototype.slice.call(arguments, 0), const args = Array.prototype.slice.call(arguments, 0),
@@ -22,20 +22,29 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
if (!j$.isSpy(actual)) { if (!j$.isSpy(actual)) {
throw new Error( throw new Error(
getErrorMsg('Expected a spy, but got ' + util.pp(actual) + '.') getErrorMsg(
'Expected a spy, but got ' + matchersUtil.pp(actual) + '.'
)
); );
} }
const prettyPrintedCalls = actual.calls const prettyPrintedCalls = actual.calls
.allArgs() .allArgs()
.map(function(argsForCall) { .map(function(argsForCall) {
return ' ' + util.pp(argsForCall); return ' ' + matchersUtil.pp(argsForCall);
}); });
if ( if (
actual.calls.count() === 1 && actual.calls.count() === 1 &&
util.contains(actual.calls.allArgs(), expectedArgs) matchersUtil.contains(actual.calls.allArgs(), expectedArgs)
) { ) {
const firstIndex = actual.calls
.all()
.findIndex(call => matchersUtil.equals(call.args, expectedArgs));
if (firstIndex > -1) {
actual.calls.all()[firstIndex].verified = true;
}
return { return {
pass: true, pass: true,
message: message:
@@ -43,7 +52,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
actual.and.identity + actual.and.identity +
' to have been called 0 times, multiple times, or once, but with arguments different from:\n' + ' to have been called 0 times, multiple times, or once, but with arguments different from:\n' +
' ' + ' ' +
util.pp(expectedArgs) + matchersUtil.pp(expectedArgs) +
'\n' + '\n' +
'But the actual call was:\n' + 'But the actual call was:\n' +
prettyPrintedCalls.join(',\n') + prettyPrintedCalls.join(',\n') +
@@ -54,7 +63,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
function getDiffs() { function getDiffs() {
return actual.calls.allArgs().map(function(argsForCall, callIx) { return actual.calls.allArgs().map(function(argsForCall, callIx) {
const diffBuilder = new j$.DiffBuilder(); const diffBuilder = new j$.DiffBuilder();
util.equals(argsForCall, expectedArgs, diffBuilder); matchersUtil.equals(argsForCall, expectedArgs, diffBuilder);
return diffBuilder.getMessage(); return diffBuilder.getMessage();
}); });
} }
@@ -87,7 +96,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
actual.and.identity + actual.and.identity +
' to have been called only once, and with given args:\n' + ' to have been called only once, and with given args:\n' +
' ' + ' ' +
util.pp(expectedArgs) + matchersUtil.pp(expectedArgs) +
'\n' + '\n' +
butString() butString()
}; };

View File

@@ -36,23 +36,35 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
} }
actual = args[0]; actual = args[0];
const calls = actual.calls.count();
const callsCount = actual.calls.count();
const timesMessage = expected === 1 ? 'once' : expected + ' times'; const timesMessage = expected === 1 ? 'once' : expected + ' times';
result.pass = calls === expected;
result.pass = callsCount === expected;
if (result.pass) {
const allCalls = actual.calls.all();
const max = Math.min(expected, callsCount);
for (let i = 0; i < max; i++) {
allCalls[i].verified = true;
}
}
result.message = result.pass result.message = result.pass
? 'Expected spy ' + ? 'Expected spy ' +
actual.and.identity + actual.and.identity +
' not to have been called ' + ' not to have been called ' +
timesMessage + timesMessage +
'. It was called ' + '. It was called ' +
calls + callsCount +
' times.' ' times.'
: 'Expected spy ' + : 'Expected spy ' +
actual.and.identity + actual.and.identity +
' to have been called ' + ' to have been called ' +
timesMessage + timesMessage +
'. It was called ' + '. It was called ' +
calls + callsCount +
' times.'; ' times.';
return result; return result;
} }

View File

@@ -44,6 +44,11 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
} }
if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) { if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) {
actual.calls
.all()
.filter(call => matchersUtil.equals(call.args, expectedArgs))
.forEach(call => (call.verified = true));
result.pass = true; result.pass = true;
result.message = function() { result.message = function() {
return ( return (

View File

@@ -0,0 +1,87 @@
getJasmineRequireObj().toHaveNoOtherSpyInteractions = function(j$) {
const getErrorMsg = j$.formatErrorMsg(
'<toHaveNoOtherSpyInteractions>',
'expect(<spyObj>).toHaveNoOtherSpyInteractions()'
);
/**
* {@link expect} the actual (a {@link SpyObj}) spies to have not been called except interactions which was already tracked with `toHaveBeenCalled`.
* @function
* @name matchers#toHaveNoOtherSpyInteractions
* @example
* expect(mySpyObj).toHaveNoOtherSpyInteractions();
* expect(mySpyObj).not.toHaveNoOtherSpyInteractions();
*/
function toHaveNoOtherSpyInteractions(matchersUtil) {
return {
compare: function(actual) {
const result = {};
if (!j$.isObject_(actual)) {
throw new Error(
getErrorMsg('Expected an object, but got ' + typeof actual + '.')
);
}
if (arguments.length > 1) {
throw new Error(getErrorMsg('Does not take arguments'));
}
result.pass = true;
let hasSpy = false;
const unexpectedCalls = [];
for (const spy of Object.values(actual)) {
if (!j$.isSpy(spy)) {
continue;
}
hasSpy = true;
const unverifiedCalls = spy.calls
.all()
.filter(call => !call.verified);
if (unverifiedCalls.length > 0) {
result.pass = false;
}
unverifiedCalls.forEach(unverifiedCall => {
unexpectedCalls.push([
spy.and.identity,
matchersUtil.pp(unverifiedCall.args)
]);
});
}
if (!hasSpy) {
throw new Error(
getErrorMsg(
'Expected an object with spies, but object has no spies.'
)
);
}
if (result.pass) {
result.message =
"Expected to have other spy interactions but it didn't.";
} else {
const ppUnexpectedCalls = unexpectedCalls
.map(
([spyName, arguments]) => ` ${spyName} called with ${arguments}`
)
.join(',\n');
result.message =
'Expected to have no other spy interactions, but it had the following calls:\n' +
ppUnexpectedCalls +
'.\n\n';
}
return result;
}
};
}
return toHaveNoOtherSpyInteractions;
};