diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 4441df1c69f9..63533e8e16dc 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2521,8 +2521,6 @@ if (window.jasmine || window.mocha) { throw new ErrorAddingDeclarationLocationStack(e, errorForStack); } throw e; - } finally { - errorForStack = null; } } } diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index ce81adf74deb..475f614e4664 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -920,50 +920,121 @@ describe('ngMock', function() { }).toThrow('test message'); })); - describe('error stack trace when called outside of spec context', function() { - // - Chrome, Firefox, Edge, Opera give us the stack trace as soon as an Error is created - // - IE10+, PhantomJS give us the stack trace only once the error is thrown - // - IE9 does not provide stack traces - var stackTraceSupported = (function() { - var error = new Error(); - if (!error.stack) { - try { - throw error; - } catch (e) {} + // - Chrome, Firefox, Edge, Opera give us the stack trace as soon as an Error is created + // - IE10+, PhantomJS give us the stack trace only once the error is thrown + // - IE9 does not provide stack traces + var stackTraceSupported = (function() { + var error = new Error(); + if (!error.stack) { + try { + throw error; + } catch (e) {} + } + + return !!error.stack; + })(); + + // function returned by inject(), when called outside of test spec + // context, may have stored state so do not reuse the result from this + // call in multiple test specs + function testInjectCaller(injectionFunctionCount) { + var shouldThrow = []; + // using an extra named function wrapper around the Error throw avoids + // stack trace constructed by some browsers (e.g. FireFox) from + // containing the name of the external caller function + function injectionFunction(index) { + return function() { + if (shouldThrow[index]) + throw new Error(); + }; + } + var injectionFunctions = []; + for (var i = 0; i < (injectionFunctionCount || 1); ++i) { + injectionFunctions.push(injectionFunction(i)); + } + var injectingCall = inject.apply(window, injectionFunctions); + injectingCall.setThrow = function(index, value) { + if (!isDefined(value)) { + value = index; + index = 0; } + shouldThrow[index] = value; + }; + return injectingCall; + } - return !!error.stack; - })(); + if (!stackTraceSupported) { + describe('on browsers not supporting stack traces', function() { + describe('when called outside of test spec context', function() { + var injectingCall = testInjectCaller(); - function testCaller() { - return inject(function() { - throw new Error(); + it('should not add stack trace information to thrown injection Error', function() { + injectingCall.setThrow(true); + try { + injectingCall(); + } catch (e) { + expect(e.stack).toBeUndefined(); + } + }); }); - } - var throwErrorFromInjectCallback = testCaller(); + }); + } - if (stackTraceSupported) { - describe('on browsers supporting stack traces', function() { + if (stackTraceSupported) { + describe('on browsers supporting stack traces', function() { + describe('when called outside of test spec context and initial inject callback invocation fails', function() { + var throwingInjectingCall = testInjectCaller(); + throwingInjectingCall.setThrow(true); + + // regression test for issue #13591 when run on IE10+ or PhantomJS it('should update thrown Error stack trace with inject call location', function() { try { - throwErrorFromInjectCallback(); + throwingInjectingCall(); } catch (e) { - expect(e.stack).toMatch('testCaller'); + expect(e.stack).toMatch('testInjectCaller'); } }); }); - } else { - describe('on browsers not supporting stack traces', function() { - it('should not add stack trace information to thrown Error', function() { + + describe('when called outside of test spec context', function() { + var injectingCall = testInjectCaller(); + + // regression test for issue #13594 + // regression test for issue #13591 when run on IE10+ or PhantomJS + it('should update thrown Error stack when repeated inject callback invocations fail', function() { + injectingCall.setThrow(false); + injectingCall(); // initial call that will not throw + injectingCall.setThrow(true); try { - throwErrorFromInjectCallback(); + injectingCall(); // non-initial call, but first failing one } catch (e) { - expect(e.stack).toBeUndefined(); + expect(e.stack).toMatch('testInjectCaller'); + } + try { + injectingCall(); // repeated failing call + } catch (e) { + expect(e.stack).toMatch('testInjectCaller'); } }); }); - } - }); + + describe('when called outside of test spec context with multiple injected functions', function() { + var injectingCall = testInjectCaller(2); + + // regression test for issue #13594 + // regression test for issue #13591 when run on IE10+ or PhantomJS + it('should update thrown Error stack when second injected function fails', function() { + injectingCall.setThrow(0, false); + injectingCall.setThrow(1, true); + try { + injectingCall(); + } catch (e) { + expect(e.stack).toMatch('testInjectCaller'); + } + }); + }); + }); + } }); });