diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index bd4a5c64..40713ef6 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1233,7 +1233,33 @@ getJasmineRequireObj().Env = function(j$) { } }; - var asyncExpectationFactory = function(actual, spec) { + function recordLateExpectation(runable, runableType, result) { + var delayedExpectationResult = {}; + Object.keys(result).forEach(function(k) { + delayedExpectationResult[k] = result[k]; + }); + delayedExpectationResult.passed = false; + delayedExpectationResult.globalErrorType = 'lateExpectation'; + delayedExpectationResult.message = + runableType + + ' "' + + runable.getFullName() + + '" ran a "' + + result.matcherName + + '" expectation after it finished.\n'; + + if (result.message) { + delayedExpectationResult.message += + 'Message: "' + result.message + '"\n'; + } + + delayedExpectationResult.message += + 'Did you forget to return or await the result of expectAsync?'; + + topSuite.result.failedExpectations.push(delayedExpectationResult); + } + + var asyncExpectationFactory = function(actual, spec, runableType) { return j$.Expectation.asyncFactory({ util: j$.matchersUtil, customEqualityTesters: runnableResources[spec.id].customEqualityTesters, @@ -1243,9 +1269,19 @@ getJasmineRequireObj().Env = function(j$) { }); function addExpectationResult(passed, result) { + if (currentRunnable() !== spec) { + recordLateExpectation(spec, runableType, result); + } return spec.addExpectationResult(passed, result); } }; + var suiteAsyncExpectationFactory = function(actual, suite) { + return asyncExpectationFactory(actual, suite, 'Suite'); + }; + + var specAsyncExpectationFactory = function(actual, suite) { + return asyncExpectationFactory(actual, suite, 'Spec'); + }; var defaultResourcesForRunnable = function(id, parentRunnableId) { var resources = { @@ -1463,7 +1499,7 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', expectationFactory: expectationFactory, - asyncExpectationFactory: asyncExpectationFactory, + asyncExpectationFactory: suiteAsyncExpectationFactory, expectationResultFactory: expectationResultFactory }); defaultResourcesForRunnable(topSuite.id); @@ -1796,7 +1832,7 @@ getJasmineRequireObj().Env = function(j$) { description: description, parentSuite: currentDeclarationSuite, expectationFactory: expectationFactory, - asyncExpectationFactory: asyncExpectationFactory, + asyncExpectationFactory: suiteAsyncExpectationFactory, expectationResultFactory: expectationResultFactory, throwOnExpectationFailure: config.oneFailurePerSpec }); @@ -1890,7 +1926,7 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSpecId(), beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: expectationFactory, - asyncExpectationFactory: asyncExpectationFactory, + asyncExpectationFactory: specAsyncExpectationFactory, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index e31f7d9d..eef02e6c 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2149,7 +2149,7 @@ describe("Env integration", function() { env.it('is a spec without any expectations', function() { // does nothing, just a mock spec without expectations }); - + }); it('should report "failed" status if "failSpecWithNoExpectations" is enabled', function(done) { @@ -2556,4 +2556,93 @@ describe("Env integration", function() { env.execute(); }); + + it('reports an error when an async expectation occurs after the spec finishes', function(done) { + jasmine.getEnv().requirePromises(); + + var env = new jasmineUnderTest.Env(), + resolve, + promise = new Promise(function(res) { resolve = res; }); + + env.describe('a suite', function() { + env.it('does not wait', function() { + // Note: we intentionally don't return the result of each expectAsync. + // This causes the spec to finish before the expectations are evaluated. + env.expectAsync(promise).toBeResolved(); + env.expectAsync(promise).toBeResolvedTo('something else'); + }); + }); + + env.addReporter({ + specDone: function() { + resolve(); + }, + jasmineDone: function (result) { + expect(result.failedExpectations).toEqual([ + jasmine.objectContaining({ + passed: false, + globalErrorType: 'lateExpectation', + message: 'Spec "a suite does not wait" ran a "toBeResolved" expectation ' + + 'after it finished.\n' + + 'Did you forget to return or await the result of expectAsync?', + matcherName: 'toBeResolved' + }), + jasmine.objectContaining({ + passed: false, + globalErrorType: 'lateExpectation', + message: 'Spec "a suite does not wait" ran a "toBeResolvedTo" expectation ' + + 'after it finished.\n' + + 'Message: "Expected a promise to be resolved to \'something else\' ' + + 'but it was resolved to undefined."\n' + + 'Did you forget to return or await the result of expectAsync?', + matcherName: 'toBeResolvedTo' + }) + ]); + + done(); + } + }); + + env.execute(); + }); + + it('reports an error when an async expectation occurs after the suite finishes', function(done) { + jasmine.getEnv().requirePromises(); + + var env = new jasmineUnderTest.Env(), + resolve, + promise = new Promise(function(res) { resolve = res; }); + + env.describe('a suite', function() { + env.afterAll(function() { + // Note: we intentionally don't return the result of expectAsync. + // This causes the suite to finish before the expectations are evaluated. + env.expectAsync(promise).toBeResolved(); + }); + + env.it('is a spec', function() {}); + }); + + env.addReporter({ + suiteDone: function() { + resolve(); + }, + jasmineDone: function (result) { + expect(result.failedExpectations).toEqual([ + jasmine.objectContaining({ + passed: false, + globalErrorType: 'lateExpectation', + message: 'Suite "a suite" ran a "toBeResolved" expectation ' + + 'after it finished.\n' + + 'Did you forget to return or await the result of expectAsync?', + matcherName: 'toBeResolved' + }) + ]); + + done(); + } + }); + + env.execute(); + }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 7d6a1210..dc652cd5 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -321,7 +321,33 @@ getJasmineRequireObj().Env = function(j$) { } }; - var asyncExpectationFactory = function(actual, spec) { + function recordLateExpectation(runable, runableType, result) { + var delayedExpectationResult = {}; + Object.keys(result).forEach(function(k) { + delayedExpectationResult[k] = result[k]; + }); + delayedExpectationResult.passed = false; + delayedExpectationResult.globalErrorType = 'lateExpectation'; + delayedExpectationResult.message = + runableType + + ' "' + + runable.getFullName() + + '" ran a "' + + result.matcherName + + '" expectation after it finished.\n'; + + if (result.message) { + delayedExpectationResult.message += + 'Message: "' + result.message + '"\n'; + } + + delayedExpectationResult.message += + 'Did you forget to return or await the result of expectAsync?'; + + topSuite.result.failedExpectations.push(delayedExpectationResult); + } + + var asyncExpectationFactory = function(actual, spec, runableType) { return j$.Expectation.asyncFactory({ util: j$.matchersUtil, customEqualityTesters: runnableResources[spec.id].customEqualityTesters, @@ -331,9 +357,19 @@ getJasmineRequireObj().Env = function(j$) { }); function addExpectationResult(passed, result) { + if (currentRunnable() !== spec) { + recordLateExpectation(spec, runableType, result); + } return spec.addExpectationResult(passed, result); } }; + var suiteAsyncExpectationFactory = function(actual, suite) { + return asyncExpectationFactory(actual, suite, 'Suite'); + }; + + var specAsyncExpectationFactory = function(actual, suite) { + return asyncExpectationFactory(actual, suite, 'Spec'); + }; var defaultResourcesForRunnable = function(id, parentRunnableId) { var resources = { @@ -551,7 +587,7 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', expectationFactory: expectationFactory, - asyncExpectationFactory: asyncExpectationFactory, + asyncExpectationFactory: suiteAsyncExpectationFactory, expectationResultFactory: expectationResultFactory }); defaultResourcesForRunnable(topSuite.id); @@ -884,7 +920,7 @@ getJasmineRequireObj().Env = function(j$) { description: description, parentSuite: currentDeclarationSuite, expectationFactory: expectationFactory, - asyncExpectationFactory: asyncExpectationFactory, + asyncExpectationFactory: suiteAsyncExpectationFactory, expectationResultFactory: expectationResultFactory, throwOnExpectationFailure: config.oneFailurePerSpec }); @@ -978,7 +1014,7 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSpecId(), beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: expectationFactory, - asyncExpectationFactory: asyncExpectationFactory, + asyncExpectationFactory: specAsyncExpectationFactory, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite);