diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 1a00063f..0d42c0b1 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -106,6 +106,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toContain', 'toEqual', 'toHaveBeenCalled', + 'toHaveBeenCalledBefore', 'toHaveBeenCalledWith', 'toHaveBeenCalledTimes', 'toMatch', @@ -2707,6 +2708,59 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { return toHaveBeenCalled; }; +getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { + + var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledBefore()'); + + function toHaveBeenCalledBefore() { + return { + compare: function(firstSpy, latterSpy) { + if (!j$.isSpy(firstSpy)) { + throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(firstSpy) + '.')); + } + if (!j$.isSpy(latterSpy)) { + throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(latterSpy) + '.')); + } + + var result = { pass: false }; + + if (!firstSpy.calls.count()) { + result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called.'; + return result; + } + if (!latterSpy.calls.count()) { + result.message = 'Expected spy ' + latterSpy.and.identity() + ' to have been called.'; + return result; + } + + var latest1stSpyCall = firstSpy.calls.mostRecent().invocationOrder; + var first2ndSpyCall = latterSpy.calls.first().invocationOrder; + + result.pass = latest1stSpyCall < first2ndSpyCall; + + if (result.pass) { + result.message = 'Expected spy ' + firstSpy.and.identity() + ' to not have been called before spy ' + latterSpy.and.identity() + ', but it was'; + } else { + var first1stSpyCall = firstSpy.calls.first().invocationOrder; + var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder; + + if(first1stSpyCall < first2ndSpyCall) { + result.message = 'Expected latest call to spy ' + firstSpy.and.identity() + ' to have been called before first call to spy ' + latterSpy.and.identity() + ' (no interleaved calls)'; + } else if (latest2ndSpyCall > latest1stSpyCall) { + result.message = 'Expected first call to spy ' + latterSpy.and.identity() + ' to have been called after latest call to spy ' + firstSpy.and.identity() + ' (no interleaved calls)'; + } else { + result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called before spy ' + latterSpy.and.identity(); + } + } + + return result; + } + }; + } + + return toHaveBeenCalledBefore; +}; + getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledTimes()'); @@ -3511,6 +3565,14 @@ getJasmineRequireObj().interface = function(jasmine, env) { getJasmineRequireObj().Spy = function (j$) { + var nextOrder = (function() { + var order = 0; + + return function() { + return order++; + }; + })(); + function Spy(name, originalFn) { var args = buildArgs(), /*`eval` is the only option to preserve both this and context: @@ -3532,6 +3594,7 @@ getJasmineRequireObj().Spy = function (j$) { spy = function () { var callData = { object: this, + invocationOrder: nextOrder(), args: Array.prototype.slice.apply(arguments) }; diff --git a/spec/core/matchers/toHaveBeenCalledBeforeSpec.js b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js new file mode 100644 index 00000000..256ed1d2 --- /dev/null +++ b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js @@ -0,0 +1,99 @@ +describe("toHaveBeenCalledBefore", function() { + it("throws an exception when the actual is not a spy", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + fn = function() {}, + secondSpy = jasmineUnderTest.createSpy('second spy'); + + expect(function() { matcher.compare(fn, secondSpy) }).toThrowError(Error, /Expected a spy, but got Function./); + }); + + it("throws an exception when the expected is not a spy", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + firstSpy = jasmineUnderTest.createSpy('first spy'), + fn = function() {}; + + expect(function() { matcher.compare(firstSpy, fn) }).toThrowError(Error, /Expected a spy, but got Function./); + }); + + it("fails when the actual was not called", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + firstSpy = jasmineUnderTest.createSpy('first spy'), + secondSpy = jasmineUnderTest.createSpy('second spy'); + + secondSpy(); + + result = matcher.compare(firstSpy, secondSpy); + expect(result.pass).toBe(false); + expect(result.message).toMatch(/Expected spy first spy to have been called./); + }); + + it("fails when the expected was not called", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + firstSpy = jasmineUnderTest.createSpy('first spy'), + secondSpy = jasmineUnderTest.createSpy('second spy'); + + firstSpy(); + + result = matcher.compare(firstSpy, secondSpy); + expect(result.pass).toBe(false); + expect(result.message).toMatch(/Expected spy second spy to have been called./); + }); + + it("fails when the actual is called after the expected", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + firstSpy = jasmineUnderTest.createSpy('first spy'), + secondSpy = jasmineUnderTest.createSpy('second spy'), + result; + + secondSpy(); + firstSpy(); + + result = matcher.compare(firstSpy, secondSpy); + expect(result.pass).toBe(false); + expect(result.message).toEqual('Expected spy first spy to have been called before spy second spy'); + }); + + it("fails when the actual is called before and after the expected", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + firstSpy = jasmineUnderTest.createSpy('first spy'), + secondSpy = jasmineUnderTest.createSpy('second spy'), + result; + + firstSpy(); + secondSpy(); + firstSpy(); + + result = matcher.compare(firstSpy, secondSpy); + expect(result.pass).toBe(false); + expect(result.message).toEqual('Expected latest call to spy first spy to have been called before first call to spy second spy (no interleaved calls)'); + }); + + it("fails when the expected is called before and after the actual", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + firstSpy = jasmineUnderTest.createSpy('first spy'), + secondSpy = jasmineUnderTest.createSpy('second spy'), + result; + + secondSpy(); + firstSpy(); + secondSpy(); + + result = matcher.compare(firstSpy, secondSpy); + expect(result.pass).toBe(false); + expect(result.message).toEqual('Expected first call to spy second spy to have been called after latest call to spy first spy (no interleaved calls)'); + }); + + it("passes when the actual is called before the expected", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + firstSpy = jasmineUnderTest.createSpy('first spy'), + secondSpy = jasmineUnderTest.createSpy('second spy'), + result; + + firstSpy(); + secondSpy(); + + result = matcher.compare(firstSpy, secondSpy); + expect(result.pass).toBe(true); + expect(result.message).toEqual('Expected spy first spy to not have been called before spy second spy, but it was'); + }); +}); diff --git a/src/core/Spy.js b/src/core/Spy.js index bd5ae01b..de4ed399 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -1,5 +1,13 @@ getJasmineRequireObj().Spy = function (j$) { + var nextOrder = (function() { + var order = 0; + + return function() { + return order++; + }; + })(); + function Spy(name, originalFn) { var args = buildArgs(), /*`eval` is the only option to preserve both this and context: @@ -21,6 +29,7 @@ getJasmineRequireObj().Spy = function (j$) { spy = function () { var callData = { object: this, + invocationOrder: nextOrder(), args: Array.prototype.slice.apply(arguments) }; diff --git a/src/core/matchers/requireMatchers.js b/src/core/matchers/requireMatchers.js index 7fb9f4ea..0054706f 100644 --- a/src/core/matchers/requireMatchers.js +++ b/src/core/matchers/requireMatchers.js @@ -15,6 +15,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toContain', 'toEqual', 'toHaveBeenCalled', + 'toHaveBeenCalledBefore', 'toHaveBeenCalledWith', 'toHaveBeenCalledTimes', 'toMatch', diff --git a/src/core/matchers/toHaveBeenCalledBefore.js b/src/core/matchers/toHaveBeenCalledBefore.js new file mode 100644 index 00000000..b54973f1 --- /dev/null +++ b/src/core/matchers/toHaveBeenCalledBefore.js @@ -0,0 +1,52 @@ +getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { + + var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledBefore()'); + + function toHaveBeenCalledBefore() { + return { + compare: function(firstSpy, latterSpy) { + if (!j$.isSpy(firstSpy)) { + throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(firstSpy) + '.')); + } + if (!j$.isSpy(latterSpy)) { + throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(latterSpy) + '.')); + } + + var result = { pass: false }; + + if (!firstSpy.calls.count()) { + result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called.'; + return result; + } + if (!latterSpy.calls.count()) { + result.message = 'Expected spy ' + latterSpy.and.identity() + ' to have been called.'; + return result; + } + + var latest1stSpyCall = firstSpy.calls.mostRecent().invocationOrder; + var first2ndSpyCall = latterSpy.calls.first().invocationOrder; + + result.pass = latest1stSpyCall < first2ndSpyCall; + + if (result.pass) { + result.message = 'Expected spy ' + firstSpy.and.identity() + ' to not have been called before spy ' + latterSpy.and.identity() + ', but it was'; + } else { + var first1stSpyCall = firstSpy.calls.first().invocationOrder; + var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder; + + if(first1stSpyCall < first2ndSpyCall) { + result.message = 'Expected latest call to spy ' + firstSpy.and.identity() + ' to have been called before first call to spy ' + latterSpy.and.identity() + ' (no interleaved calls)'; + } else if (latest2ndSpyCall > latest1stSpyCall) { + result.message = 'Expected first call to spy ' + latterSpy.and.identity() + ' to have been called after latest call to spy ' + firstSpy.and.identity() + ' (no interleaved calls)'; + } else { + result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called before spy ' + latterSpy.and.identity(); + } + } + + return result; + } + }; + } + + return toHaveBeenCalledBefore; +};