'use strict'; describe('ngMock', function() { var noop = angular.noop; describe('TzDate', function() { function minutes(min) { return min * 60 * 1000; } it('should look like a Date', function() { var date = new angular.mock.TzDate(0,0); expect(angular.isDate(date)).toBe(true); }); it('should take millis as constructor argument', function() { expect(new angular.mock.TzDate(0, 0).getTime()).toBe(0); expect(new angular.mock.TzDate(0, 1283555108000).getTime()).toBe(1283555108000); }); it('should take dateString as constructor argument', function() { expect(new angular.mock.TzDate(0, '1970-01-01T00:00:00.000Z').getTime()).toBe(0); expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getTime()).toBe(1283555108023); }); it('should fake getLocalDateString method', function() { var millennium = new Date('2000').getTime(); // millennium in -3h var t0 = new angular.mock.TzDate(-3, millennium); expect(t0.toLocaleDateString()).toMatch('2000'); // millennium in +0h var t1 = new angular.mock.TzDate(0, millennium); expect(t1.toLocaleDateString()).toMatch('2000'); // millennium in +3h var t2 = new angular.mock.TzDate(3, millennium); expect(t2.toLocaleDateString()).toMatch('1999'); }); it('should fake toISOString method', function() { var date = new angular.mock.TzDate(-1, '2009-10-09T01:02:03.027Z'); if (new Date().toISOString) { expect(date.toISOString()).toEqual('2009-10-09T01:02:03.027Z'); } else { expect(date.toISOString).toBeUndefined(); } }); it('should fake getHours method', function() { // avoid going negative due to #5017, so use Jan 2, 1970 00:00 UTC var jan2 = 24 * 60 * 60 * 1000; //0:00 in -3h var t0 = new angular.mock.TzDate(-3, jan2); expect(t0.getHours()).toBe(3); //0:00 in +0h var t1 = new angular.mock.TzDate(0, jan2); expect(t1.getHours()).toBe(0); //0:00 in +3h var t2 = new angular.mock.TzDate(3, jan2); expect(t2.getHours()).toMatch('21'); }); it('should fake getMinutes method', function() { //0:15 in -3h var t0 = new angular.mock.TzDate(-3, minutes(15)); expect(t0.getMinutes()).toBe(15); //0:15 in -3.25h var t0a = new angular.mock.TzDate(-3.25, minutes(15)); expect(t0a.getMinutes()).toBe(30); //0 in +0h var t1 = new angular.mock.TzDate(0, minutes(0)); expect(t1.getMinutes()).toBe(0); //0:15 in +0h var t1a = new angular.mock.TzDate(0, minutes(15)); expect(t1a.getMinutes()).toBe(15); //0:15 in +3h var t2 = new angular.mock.TzDate(3, minutes(15)); expect(t2.getMinutes()).toMatch('15'); //0:15 in +3.25h var t2a = new angular.mock.TzDate(3.25, minutes(15)); expect(t2a.getMinutes()).toMatch('0'); }); it('should fake getSeconds method', function() { //0 in -3h var t0 = new angular.mock.TzDate(-3, 0); expect(t0.getSeconds()).toBe(0); //0 in +0h var t1 = new angular.mock.TzDate(0, 0); expect(t1.getSeconds()).toBe(0); //0 in +3h var t2 = new angular.mock.TzDate(3, 0); expect(t2.getSeconds()).toMatch('0'); }); it('should fake getMilliseconds method', function() { expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.003Z').getMilliseconds()).toBe(3); expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getMilliseconds()).toBe(23); expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.123Z').getMilliseconds()).toBe(123); }); it('should create a date representing new year in Bratislava', function() { var newYearInBratislava = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z'); expect(newYearInBratislava.getTimezoneOffset()).toBe(-60); expect(newYearInBratislava.getFullYear()).toBe(2010); expect(newYearInBratislava.getMonth()).toBe(0); expect(newYearInBratislava.getDate()).toBe(1); expect(newYearInBratislava.getHours()).toBe(0); expect(newYearInBratislava.getMinutes()).toBe(0); expect(newYearInBratislava.getSeconds()).toBe(0); }); it('should delegate all the UTC methods to the original UTC Date object', function() { //from when created from string var date1 = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z'); expect(date1.getUTCFullYear()).toBe(2009); expect(date1.getUTCMonth()).toBe(11); expect(date1.getUTCDate()).toBe(31); expect(date1.getUTCHours()).toBe(23); expect(date1.getUTCMinutes()).toBe(0); expect(date1.getUTCSeconds()).toBe(0); //from when created from millis var date2 = new angular.mock.TzDate(-1, date1.getTime()); expect(date2.getUTCFullYear()).toBe(2009); expect(date2.getUTCMonth()).toBe(11); expect(date2.getUTCDate()).toBe(31); expect(date2.getUTCHours()).toBe(23); expect(date2.getUTCMinutes()).toBe(0); expect(date2.getUTCSeconds()).toBe(0); }); it('should throw error when no third param but toString called', function() { expect(function() { new angular.mock.TzDate(0,0).toString(); }). toThrowError('Method \'toString\' is not implemented in the TzDate mock'); }); }); describe('$log', function() { angular.forEach([true, false], function(debugEnabled) { describe('debug ' + debugEnabled, function() { beforeEach(module(function($logProvider) { $logProvider.debugEnabled(debugEnabled); })); afterEach(inject(function($log) { $log.reset(); })); it('should skip debugging output if disabled (' + debugEnabled + ')', inject(function($log) { $log.log('fake log'); $log.info('fake log'); $log.warn('fake log'); $log.error('fake log'); $log.debug('fake log'); expect($log.log.logs).toContain(['fake log']); expect($log.info.logs).toContain(['fake log']); expect($log.warn.logs).toContain(['fake log']); expect($log.error.logs).toContain(['fake log']); if (debugEnabled) { expect($log.debug.logs).toContain(['fake log']); } else { expect($log.debug.logs).toEqual([]); } })); }); }); describe('debug enabled (default)', function() { var $log; beforeEach(inject(['$log', function(log) { $log = log; }])); afterEach(inject(function($log) { $log.reset(); })); it('should provide the log method', function() { expect(function() { $log.log(''); }).not.toThrow(); }); it('should provide the info method', function() { expect(function() { $log.info(''); }).not.toThrow(); }); it('should provide the warn method', function() { expect(function() { $log.warn(''); }).not.toThrow(); }); it('should provide the error method', function() { expect(function() { $log.error(''); }).not.toThrow(); }); it('should provide the debug method', function() { expect(function() { $log.debug(''); }).not.toThrow(); }); it('should store log messages', function() { $log.log('fake log'); expect($log.log.logs).toContain(['fake log']); }); it('should store info messages', function() { $log.info('fake log'); expect($log.info.logs).toContain(['fake log']); }); it('should store warn messages', function() { $log.warn('fake log'); expect($log.warn.logs).toContain(['fake log']); }); it('should store error messages', function() { $log.error('fake log'); expect($log.error.logs).toContain(['fake log']); }); it('should store debug messages', function() { $log.debug('fake log'); expect($log.debug.logs).toContain(['fake log']); }); it('should assertEmpty', function() { try { $log.error(new Error('MyError')); $log.warn(new Error('MyWarn')); $log.info(new Error('MyInfo')); $log.log(new Error('MyLog')); $log.debug(new Error('MyDebug')); $log.assertEmpty(); } catch (error) { var err = error.message || error; expect(err).toMatch(/Error: MyError/m); expect(err).toMatch(/Error: MyWarn/m); expect(err).toMatch(/Error: MyInfo/m); expect(err).toMatch(/Error: MyLog/m); expect(err).toMatch(/Error: MyDebug/m); } finally { $log.reset(); } }); it('should reset state', function() { $log.error(new Error('MyError')); $log.warn(new Error('MyWarn')); $log.info(new Error('MyInfo')); $log.log(new Error('MyLog')); $log.reset(); var passed = false; try { $log.assertEmpty(); // should not throw error! passed = true; } catch (e) { passed = e; } expect(passed).toBe(true); }); }); }); describe('$interval', function() { it('should run tasks repeatedly', inject(function($interval) { var counter = 0; $interval(function() { counter++; }, 1000); expect(counter).toBe(0); $interval.flush(1000); expect(counter).toBe(1); $interval.flush(1000); expect(counter).toBe(2); $interval.flush(2000); expect(counter).toBe(4); })); it('should call $apply after each task is executed', inject(function($interval, $rootScope) { var applySpy = spyOn($rootScope, '$apply').and.callThrough(); $interval(noop, 1000); expect(applySpy).not.toHaveBeenCalled(); $interval.flush(1000); expect(applySpy).toHaveBeenCalledOnce(); applySpy.calls.reset(); $interval(noop, 1000); $interval(noop, 1000); $interval.flush(1000); expect(applySpy).toHaveBeenCalledTimes(3); })); it('should NOT call $apply if invokeApply is set to false', inject(function($interval, $rootScope) { var applySpy = spyOn($rootScope, '$apply').and.callThrough(); var counter = 0; $interval(function increment() { counter++; }, 1000, 0, false); expect(applySpy).not.toHaveBeenCalled(); expect(counter).toBe(0); $interval.flush(2000); expect(applySpy).not.toHaveBeenCalled(); expect(counter).toBe(2); })); it('should allow you to specify the delay time', inject(function($interval) { var counter = 0; $interval(function() { counter++; }, 123); expect(counter).toBe(0); $interval.flush(122); expect(counter).toBe(0); $interval.flush(1); expect(counter).toBe(1); })); it('should allow you to NOT specify the delay time', inject(function($interval) { var counterA = 0; var counterB = 0; $interval(function() { counterA++; }); $interval(function() { counterB++; }, 0); $interval.flush(100); expect(counterA).toBe(100); expect(counterB).toBe(100); $interval.flush(100); expect(counterA).toBe(200); expect(counterB).toBe(200); })); it('should run tasks in correct relative order', inject(function($interval) { var counterA = 0; var counterB = 0; $interval(function() { counterA++; }, 0); $interval(function() { counterB++; }, 1000); $interval.flush(1000); expect(counterA).toBe(1000); expect(counterB).toBe(1); $interval.flush(999); expect(counterA).toBe(1999); expect(counterB).toBe(1); $interval.flush(1); expect(counterA).toBe(2000); expect(counterB).toBe(2); })); it('should NOT trigger zero-delay interval when flush has ran before', inject(function($interval) { var counterA = 0; var counterB = 0; $interval.flush(100); $interval(function() { counterA++; }); $interval(function() { counterB++; }, 0); expect(counterA).toBe(0); expect(counterB).toBe(0); $interval.flush(100); expect(counterA).toBe(100); expect(counterB).toBe(100); })); it('should trigger zero-delay interval only once on flush zero', inject(function($interval) { var counterA = 0; var counterB = 0; $interval(function() { counterA++; }); $interval(function() { counterB++; }, 0); $interval.flush(0); expect(counterA).toBe(1); expect(counterB).toBe(1); $interval.flush(0); expect(counterA).toBe(1); expect(counterB).toBe(1); })); it('should allow you to specify a number of iterations', inject(function($interval) { var counter = 0; $interval(function() {counter++;}, 1000, 2); $interval.flush(1000); expect(counter).toBe(1); $interval.flush(1000); expect(counter).toBe(2); $interval.flush(1000); expect(counter).toBe(2); })); describe('flush', function() { it('should move the clock forward by the specified time', inject(function($interval) { var counterA = 0; var counterB = 0; $interval(function() { counterA++; }, 100); $interval(function() { counterB++; }, 401); $interval.flush(200); expect(counterA).toEqual(2); $interval.flush(201); expect(counterA).toEqual(4); expect(counterB).toEqual(1); })); }); it('should return a promise which will be updated with the count on each iteration', inject(function($interval) { var log = [], promise = $interval(function() { log.push('tick'); }, 1000); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0']); $interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0', 'tick', 'promise update: 1']); })); it('should return a promise which will be resolved after the specified number of iterations', inject(function($interval) { var log = [], promise = $interval(function() { log.push('tick'); }, 1000, 2); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0']); $interval.flush(1000); expect(log).toEqual([ 'tick', 'promise update: 0', 'tick', 'promise update: 1', 'promise success: 2' ]); })); describe('exception handling', function() { beforeEach(module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); })); it('should delegate exception to the $exceptionHandler service', inject( function($interval, $exceptionHandler) { $interval(function() { throw 'Test Error'; }, 1000); expect($exceptionHandler.errors).toEqual([]); $interval.flush(1000); expect($exceptionHandler.errors).toEqual(['Test Error']); $interval.flush(1000); expect($exceptionHandler.errors).toEqual(['Test Error', 'Test Error']); })); it('should call $apply even if an exception is thrown in callback', inject( function($interval, $rootScope) { var applySpy = spyOn($rootScope, '$apply').and.callThrough(); $interval(function() { throw new Error('Test Error'); }, 1000); expect(applySpy).not.toHaveBeenCalled(); $interval.flush(1000); expect(applySpy).toHaveBeenCalled(); })); it('should still update the interval promise when an exception is thrown', inject(function($interval) { var log = [], promise = $interval(function() { throw new Error('Some Error'); }, 1000); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); $interval.flush(1000); expect(log).toEqual(['promise update: 0']); })); }); describe('cancel', function() { it('should cancel tasks', inject(function($interval) { var task1 = jasmine.createSpy('task1', 1000), task2 = jasmine.createSpy('task2', 1000), task3 = jasmine.createSpy('task3', 1000), promise1, promise3; promise1 = $interval(task1, 200); $interval(task2, 1000); promise3 = $interval(task3, 333); $interval.cancel(promise3); $interval.cancel(promise1); $interval.flush(1000); expect(task1).not.toHaveBeenCalled(); expect(task2).toHaveBeenCalledOnce(); expect(task3).not.toHaveBeenCalled(); })); it('should cancel the promise', inject(function($interval, $rootScope) { var promise = $interval(noop, 1000), log = []; promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); $interval.flush(1000); $interval.cancel(promise); $interval.flush(1000); $rootScope.$apply(); // For resolving the promise - // necessary since q uses $rootScope.evalAsync. expect(log).toEqual(['promise update: 0', 'promise error: canceled']); })); it('should return true if a task was successfully canceled', inject(function($interval) { var task1 = jasmine.createSpy('task1'), task2 = jasmine.createSpy('task2'), promise1, promise2; promise1 = $interval(task1, 1000, 1); $interval.flush(1000); promise2 = $interval(task2, 1000, 1); expect($interval.cancel(promise1)).toBe(false); expect($interval.cancel(promise2)).toBe(true); })); it('should not throw a runtime exception when given an undefined promise', inject(function($interval) { var task1 = jasmine.createSpy('task1'), promise1; promise1 = $interval(task1, 1000, 1); expect($interval.cancel()).toBe(false); })); }); }); describe('defer', function() { var browser, log; beforeEach(inject(function($browser) { browser = $browser; log = ''; })); function logFn(text) { return function() { log += text + ';'; }; } it('should flush', function() { browser.defer(logFn('A')); expect(log).toEqual(''); browser.defer.flush(); expect(log).toEqual('A;'); }); it('should flush delayed', function() { browser.defer(logFn('A')); browser.defer(logFn('B'), 10); browser.defer(logFn('C'), 20); expect(log).toEqual(''); expect(browser.defer.now).toEqual(0); browser.defer.flush(0); expect(log).toEqual('A;'); browser.defer.flush(); expect(log).toEqual('A;B;C;'); }); it('should defer and flush over time', function() { browser.defer(logFn('A'), 1); browser.defer(logFn('B'), 2); browser.defer(logFn('C'), 3); browser.defer.flush(0); expect(browser.defer.now).toEqual(0); expect(log).toEqual(''); browser.defer.flush(1); expect(browser.defer.now).toEqual(1); expect(log).toEqual('A;'); browser.defer.flush(2); expect(browser.defer.now).toEqual(3); expect(log).toEqual('A;B;C;'); }); it('should throw an exception if there is nothing to be flushed', function() { expect(function() {browser.defer.flush();}).toThrowError('No deferred tasks to be flushed'); }); }); describe('$exceptionHandler', function() { it('should rethrow exceptions', inject(function($exceptionHandler) { expect(function() { $exceptionHandler('myException'); }).toThrow('myException'); })); it('should log exceptions', function() { module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('log'); }); inject(function($exceptionHandler) { $exceptionHandler('MyError'); expect($exceptionHandler.errors).toEqual(['MyError']); $exceptionHandler('MyError', 'comment'); expect($exceptionHandler.errors[1]).toEqual(['MyError', 'comment']); }); }); it('should log and rethrow exceptions', function() { module(function($exceptionHandlerProvider) { $exceptionHandlerProvider.mode('rethrow'); }); inject(function($exceptionHandler) { expect(function() { $exceptionHandler('MyError'); }).toThrow('MyError'); expect($exceptionHandler.errors).toEqual(['MyError']); expect(function() { $exceptionHandler('MyError', 'comment'); }).toThrow('MyError'); expect($exceptionHandler.errors[1]).toEqual(['MyError', 'comment']); }); }); it('should throw on wrong argument', function() { module(function($exceptionHandlerProvider) { expect(function() { $exceptionHandlerProvider.mode('XXX'); }).toThrowError('Unknown mode \'XXX\', only \'log\'/\'rethrow\' modes are allowed!'); }); inject(); // Trigger the tests in `module` }); }); describe('$timeout', function() { it('should expose flush method that will flush the pending queue of tasks', inject( function($timeout) { var logger = [], logFn = function(msg) { return function() { logger.push(msg); }; }; $timeout(logFn('t1')); $timeout(logFn('t2'), 200); $timeout(logFn('t3')); expect(logger).toEqual([]); $timeout.flush(); expect(logger).toEqual(['t1', 't3', 't2']); })); it('should throw an exception when not flushed', inject(function($timeout) { $timeout(noop); var expectedError = 'Deferred tasks to flush (1): {id: 0, time: 0}'; expect(function() {$timeout.verifyNoPendingTasks();}).toThrowError(expectedError); })); it('should do nothing when all tasks have been flushed', inject(function($timeout) { $timeout(noop); $timeout.flush(); expect(function() {$timeout.verifyNoPendingTasks();}).not.toThrow(); })); it('should check against the delay if provided within timeout', inject(function($timeout) { $timeout(noop, 100); $timeout.flush(100); expect(function() {$timeout.verifyNoPendingTasks();}).not.toThrow(); $timeout(noop, 1000); $timeout.flush(100); expect(function() {$timeout.verifyNoPendingTasks();}).toThrow(); $timeout.flush(900); expect(function() {$timeout.verifyNoPendingTasks();}).not.toThrow(); })); it('should assert against the delay value', inject(function($timeout) { var count = 0; var iterate = function() { count++; }; $timeout(iterate, 100); $timeout(iterate, 123); $timeout.flush(100); expect(count).toBe(1); $timeout.flush(123); expect(count).toBe(2); })); it('should resolve timeout functions following the timeline', inject(function($timeout) { var count1 = 0, count2 = 0; var iterate1 = function() { count1++; $timeout(iterate1, 100); }; var iterate2 = function() { count2++; $timeout(iterate2, 150); }; $timeout(iterate1, 100); $timeout(iterate2, 150); $timeout.flush(150); expect(count1).toBe(1); expect(count2).toBe(1); $timeout.flush(50); expect(count1).toBe(2); expect(count2).toBe(1); $timeout.flush(400); expect(count1).toBe(6); expect(count2).toBe(4); })); }); describe('angular.mock.dump', function() { var d = angular.mock.dump; it('should serialize primitive types', function() { expect(d(undefined)).toEqual('undefined'); expect(d(1)).toEqual('1'); expect(d(null)).toEqual('null'); expect(d('abc')).toEqual('abc'); }); it('should serialize element', function() { var e = angular.element('
abc
xyz'); expect(d(e).toLowerCase()).toEqual('
abc
xyz'); expect(d(e[0]).toLowerCase()).toEqual('
abc
'); }); it('should serialize scope', inject(function($rootScope) { $rootScope.obj = {abc:'123'}; expect(d($rootScope)).toMatch(/Scope\(.*\): \{/); expect(d($rootScope)).toMatch(/{"abc":"123"}/); })); it('should serialize scope that has overridden "hasOwnProperty"', inject(function($rootScope, $sniffer) { $rootScope.hasOwnProperty = 'X'; expect(d($rootScope)).toMatch(/Scope\(.*\): \{/); expect(d($rootScope)).toMatch(/hasOwnProperty: "X"/); })); }); describe('jasmine module and inject', function() { var log; beforeEach(function() { log = ''; }); describe('module', function() { describe('object literal format', function() { var mock = { log: 'module' }; beforeEach(function() { module({ 'service': mock, 'other': { some: 'replacement'} }, 'ngResource', function($provide) { $provide.value('example', 'win'); } ); }); it('should inject the mocked module', function() { inject(function(service) { expect(service).toEqual(mock); }); }); it('should support multiple key value pairs', function() { inject(function(service, other) { expect(other.some).toEqual('replacement'); expect(service).toEqual(mock); }); }); it('should integrate with string and function', function() { inject(function(service, $resource, example) { expect(service).toEqual(mock); expect($resource).toBeDefined(); expect(example).toEqual('win'); }); }); describe('$inject cleanup', function() { function testFn() { } it('should add $inject when invoking test function', inject(function($injector) { $injector.invoke(testFn); expect(testFn.$inject).toBeDefined(); })); it('should cleanup $inject after previous test', function() { expect(testFn.$inject).toBeUndefined(); }); it('should add $inject when annotating test function', inject(function($injector) { $injector.annotate(testFn); expect(testFn.$inject).toBeDefined(); })); it('should cleanup $inject after previous test', function() { expect(testFn.$inject).toBeUndefined(); }); it('should invoke an already annotated function', inject(function($injector) { testFn.$inject = []; $injector.invoke(testFn); })); it('should not cleanup $inject after previous test', function() { expect(testFn.$inject).toBeDefined(); }); }); }); describe('in DSL', function() { it('should load module', module(function() { log += 'module'; })); afterEach(function() { inject(); expect(log).toEqual('module'); }); }); describe('nested calls', function() { it('should invoke nested module calls immediately', function() { module(function($provide) { $provide.constant('someConst', 'blah'); module(function(someConst) { log = someConst; }); }); inject(function() { expect(log).toBe('blah'); }); }); }); describe('inline in test', function() { it('should load module', function() { module(function() { log += 'module'; }); inject(); }); afterEach(function() { expect(log).toEqual('module'); }); }); }); describe('inject', function() { describe('in DSL', function() { it('should load module', inject(function() { log += 'inject'; })); afterEach(function() { expect(log).toEqual('inject'); }); }); describe('inline in test', function() { it('should load module', function() { inject(function() { log += 'inject'; }); }); afterEach(function() { expect(log).toEqual('inject'); }); }); describe('module with inject', function() { beforeEach(module(function() { log += 'module;'; })); it('should inject', inject(function() { log += 'inject;'; })); afterEach(function() { expect(log).toEqual('module;inject;'); }); }); it('should not change thrown Errors', inject(function($sniffer) { expect(function() { inject(function() { throw new Error('test message'); }); }).toThrow(jasmine.objectContaining({message: 'test message'})); })); it('should not change thrown strings', inject(function($sniffer) { expect(function() { inject(function() { throw 'test message'; }); }).toThrow('test message'); })); describe('error stack trace when called outside of spec context', function() { // - Chrome, Firefox, Edge 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) { /* empty */} } return !!error.stack; })(); function testCaller() { return inject(function injectableError() { throw new Error(); }); } var throwErrorFromInjectCallback = testCaller(); if (stackTraceSupported) { describe('on browsers supporting stack traces', function() { it('should update thrown Error stack trace with inject call location', function() { try { throwErrorFromInjectCallback(); } catch (e) { expect(e.stack).toMatch('injectableError'); } }); }); } else { describe('on browsers not supporting stack traces', function() { it('should not add stack trace information to thrown Error', function() { try { throwErrorFromInjectCallback(); } catch (e) { expect(e.stack).toBeUndefined(); } }); }); } }); describe('ErrorAddingDeclarationLocationStack', function() { it('should be caught by Jasmine\'s `toThrowError()`', function() { function throwErrorAddingDeclarationStack() { module(function($provide) { $provide.factory('badFactory', function() { throw new Error('BadFactoryError'); }); }); inject(function(badFactory) {}); } expect(throwErrorAddingDeclarationStack).toThrowError(/BadFactoryError/); }); }); }); }); describe('$httpBackend', function() { var hb, callback, realBackendSpy; beforeEach(inject(function($httpBackend) { callback = jasmine.createSpy('callback'); hb = $httpBackend; })); it('should provide "expect" methods for each HTTP verb', function() { expect(typeof hb.expectGET).toBe('function'); expect(typeof hb.expectPOST).toBe('function'); expect(typeof hb.expectPUT).toBe('function'); expect(typeof hb.expectPATCH).toBe('function'); expect(typeof hb.expectDELETE).toBe('function'); expect(typeof hb.expectHEAD).toBe('function'); }); it('should provide "when" methods for each HTTP verb', function() { expect(typeof hb.whenGET).toBe('function'); expect(typeof hb.whenPOST).toBe('function'); expect(typeof hb.whenPUT).toBe('function'); expect(typeof hb.whenPATCH).toBe('function'); expect(typeof hb.whenDELETE).toBe('function'); expect(typeof hb.whenHEAD).toBe('function'); }); it('should provide "route" shortcuts for expect and when', function() { expect(typeof hb.whenRoute).toBe('function'); expect(typeof hb.expectRoute).toBe('function'); }); it('should respond with first matched definition by default', function() { hb.when('GET', '/url1').respond(200, 'content', {}); hb.when('GET', '/url1').respond(201, 'another', {}); callback.and.callFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('content'); }); hb('GET', '/url1', null, callback); expect(callback).not.toHaveBeenCalled(); hb.flush(); expect(callback).toHaveBeenCalledOnce(); }); describe('matchLatestDefinitionEnabled()', function() { it('should be set to false by default', function() { expect(hb.matchLatestDefinitionEnabled()).toBe(false); }); it('should allow to change the value', function() { hb.matchLatestDefinitionEnabled(true); expect(hb.matchLatestDefinitionEnabled()).toBe(true); }); it('should return the httpBackend when used as a setter', function() { expect(hb.matchLatestDefinitionEnabled(true)).toBe(hb); }); it('should respond with the first matched definition when false', function() { hb.matchLatestDefinitionEnabled(false); hb.when('GET', '/url1').respond(200, 'content', {}); hb.when('GET', '/url1').respond(201, 'another', {}); callback.and.callFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('content'); }); hb('GET', '/url1', null, callback); expect(callback).not.toHaveBeenCalled(); hb.flush(); expect(callback).toHaveBeenCalledOnce(); } ); it('should respond with latest matched definition when true', function() { hb.matchLatestDefinitionEnabled(true); hb.when('GET', '/url1').respond(200, 'match1', {}); hb.when('GET', '/url1').respond(200, 'match2', {}); hb.when('GET', '/url2').respond(204, 'nomatch', {}); callback.and.callFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('match2'); }); hb('GET', '/url1', null, callback); // Check if a newly added match is used hb.when('GET', '/url1').respond(201, 'match3', {}); var callback2 = jasmine.createSpy(); callback2.and.callFake(function(status, response) { expect(status).toBe(201); expect(response).toBe('match3'); }); hb('GET', '/url1', null, callback2); expect(callback).not.toHaveBeenCalled(); hb.flush(); expect(callback).toHaveBeenCalledOnce(); } ); }); it('should respond with a copy of the mock data', function() { var mockObject = {a: 'b'}; hb.when('GET', '/url1').respond(200, mockObject, {}); callback.and.callFake(function(status, response) { expect(status).toBe(200); expect(response).toEqual({a: 'b'}); expect(response).not.toBe(mockObject); response.a = 'c'; }); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnce(); // Fire it again and verify that the returned mock data has not been // modified. callback.calls.reset(); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnce(); expect(mockObject).toEqual({a: 'b'}); }); it('should be able to handle Blobs as mock data', function() { if (typeof Blob !== 'undefined') { // eslint-disable-next-line no-undef var mockBlob = new Blob(['{"foo":"bar"}'], {type: 'application/json'}); hb.when('GET', '/url1').respond(200, mockBlob, {}); callback.and.callFake(function(status, response) { expect(response).not.toBe(mockBlob); expect(response.size).toBe(13); expect(response.type).toBe('application/json'); expect(response.toString()).toBe('[object Blob]'); }); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnce(); } }); it('should throw error when unexpected request', function() { hb.when('GET', '/url1').respond(200, 'content'); expect(function() { hb('GET', '/xxx'); }).toThrowError('Unexpected request: GET /xxx\nNo more request expected'); }); it('should match headers if specified', function() { hb.when('GET', '/url', null, {'X': 'val1'}).respond(201, 'content1'); hb.when('GET', '/url', null, {'X': 'val2'}).respond(202, 'content2'); hb.when('GET', '/url').respond(203, 'content3'); hb('GET', '/url', null, function(status, response) { expect(status).toBe(203); expect(response).toBe('content3'); }); hb('GET', '/url', null, function(status, response) { expect(status).toBe(201); expect(response).toBe('content1'); }, {'X': 'val1'}); hb('GET', '/url', null, function(status, response) { expect(status).toBe(202); expect(response).toBe('content2'); }, {'X': 'val2'}); hb.flush(); }); it('should match data if specified', function() { hb.when('GET', '/a/b', '{a: true}').respond(201, 'content1'); hb.when('GET', '/a/b').respond(202, 'content2'); hb('GET', '/a/b', '{a: true}', function(status, response) { expect(status).toBe(201); expect(response).toBe('content1'); }); hb('GET', '/a/b', null, function(status, response) { expect(status).toBe(202); expect(response).toBe('content2'); }); hb.flush(); }); it('should match data object if specified', function() { hb.when('GET', '/a/b', {a: 1, b: 2}).respond(201, 'content1'); hb.when('GET', '/a/b').respond(202, 'content2'); hb('GET', '/a/b', '{"a":1,"b":2}', function(status, response) { expect(status).toBe(201); expect(response).toBe('content1'); }); hb('GET', '/a/b', '{"b":2,"a":1}', function(status, response) { expect(status).toBe(201); expect(response).toBe('content1'); }); hb('GET', '/a/b', null, function(status, response) { expect(status).toBe(202); expect(response).toBe('content2'); }); hb.flush(); }); it('should match only method', function() { hb.when('GET').respond(202, 'c'); callback.and.callFake(function(status, response) { expect(status).toBe(202); expect(response).toBe('c'); }); hb('GET', '/some', null, callback, {}); hb('GET', '/another', null, callback, {'X-Fake': 'Header'}); hb('GET', '/third', 'some-data', callback, {}); hb.flush(); expect(callback).toHaveBeenCalled(); }); it('should not error if the url is not provided', function() { expect(function() { hb.when('GET'); hb.whenGET(); hb.whenPOST(); hb.whenPUT(); hb.whenPATCH(); hb.whenDELETE(); hb.whenHEAD(); hb.expect('GET'); hb.expectGET(); hb.expectPOST(); hb.expectPUT(); hb.expectPATCH(); hb.expectDELETE(); hb.expectHEAD(); }).not.toThrow(); }); it('should error if the url is undefined', function() { expect(function() { hb.when('GET', undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.whenGET(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.whenDELETE(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.whenJSONP(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.whenHEAD(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.whenPATCH(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.whenPOST(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.whenPUT(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.expect('GET', undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.expectGET(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.expectDELETE(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.expectJSONP(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.expectHEAD(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.expectPATCH(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.expectPOST(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); expect(function() { hb.expectPUT(undefined); }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); }); it('should preserve the order of requests', function() { hb.when('GET', '/url1').respond(200, 'first'); hb.when('GET', '/url2').respond(201, 'second'); hb('GET', '/url2', null, callback); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledTimes(2); expect(callback.calls.argsFor(0)).toEqual([201, 'second', '', '', 'complete']); expect(callback.calls.argsFor(1)).toEqual([200, 'first', '', '', 'complete']); }); describe('respond()', function() { it('should take values', function() { hb.expect('GET', '/url1').respond(200, 'first', {'header': 'val'}, 'OK'); hb('GET', '/url1', undefined, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK', 'complete'); }); it('should default status code to 200', function() { callback.and.callFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('some-data'); }); hb.expect('GET', '/url1').respond('some-data'); hb.expect('GET', '/url2').respond('some-data', {'X-Header': 'true'}); hb('GET', '/url1', null, callback); hb('GET', '/url2', null, callback); hb.flush(); expect(callback).toHaveBeenCalled(); expect(callback).toHaveBeenCalledTimes(2); }); it('should default status code to 200 and provide status text', function() { hb.expect('GET', '/url1').respond('first', {'header': 'val'}, 'OK'); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK', 'complete'); }); it('should default xhrStatus to complete', function() { callback.and.callFake(function(status, response, headers, x, xhrStatus) { expect(xhrStatus).toBe('complete'); }); hb.expect('GET', '/url1').respond('some-data'); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalled(); }); it('should take function', function() { hb.expect('GET', '/some?q=s').respond(function(m, u, d, h, p) { return [301, m + u + ';' + d + ';a=' + h.a + ';q=' + p.q, {'Connection': 'keep-alive'}, 'Moved Permanently']; }); hb('GET', '/some?q=s', 'data', callback, {a: 'b'}); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some?q=s;data;a=b;q=s', 'Connection: keep-alive', 'Moved Permanently', undefined); }); it('should decode query parameters in respond() function', function() { hb.expect('GET', '/url?query=l%E2%80%A2ng%20string%20w%2F%20spec%5Eal%20char%24&id=1234&orderBy=-name') .respond(function(m, u, d, h, p) { return [200, 'id=' + p.id + ';orderBy=' + p.orderBy + ';query=' + p.query]; }); hb('GET', '/url?query=l%E2%80%A2ng%20string%20w%2F%20spec%5Eal%20char%24&id=1234&orderBy=-name', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'id=1234;orderBy=-name;query=l•ng string w/ spec^al char$', '', '', undefined); }); it('should include regex captures in respond() params when keys provided', function() { hb.expect('GET', /\/(.+)\/article\/(.+)/, undefined, undefined, ['id', 'name']) .respond(function(m, u, d, h, p) { return [200, 'id=' + p.id + ';name=' + p.name]; }); hb('GET', '/1234/article/cool-angular-article', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'id=1234;name=cool-angular-article', '', '', undefined); }); it('should default response headers to ""', function() { hb.expect('GET', '/url1').respond(200, 'first'); hb.expect('GET', '/url2').respond('second'); hb('GET', '/url1', null, callback); hb('GET', '/url2', null, callback); hb.flush(); expect(callback).toHaveBeenCalledTimes(2); expect(callback.calls.argsFor(0)).toEqual([200, 'first', '', '', 'complete']); expect(callback.calls.argsFor(1)).toEqual([200, 'second', '', '', 'complete']); }); it('should be able to override response of expect definition', function() { var definition = hb.expect('GET', '/url1'); definition.respond('first'); definition.respond('second'); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '', 'complete'); }); it('should be able to override response of when definition', function() { var definition = hb.when('GET', '/url1'); definition.respond('first'); definition.respond('second'); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '', 'complete'); }); it('should be able to override response of expect definition with chaining', function() { var definition = hb.expect('GET', '/url1').respond('first'); definition.respond('second'); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '', 'complete'); }); it('should be able to override response of when definition with chaining', function() { var definition = hb.when('GET', '/url1').respond('first'); definition.respond('second'); hb('GET', '/url1', null, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '', 'complete'); }); }); describe('expect()', function() { it('should require specified order', function() { hb.expect('GET', '/url1').respond(200, ''); hb.expect('GET', '/url2').respond(200, ''); expect(function() { hb('GET', '/url2', null, noop, {}); }).toThrowError('Unexpected request: GET /url2\nExpected GET /url1'); }); it('should have precedence over when()', function() { callback.and.callFake(function(status, response) { expect(status).toBe(300); expect(response).toBe('expect'); }); hb.when('GET', '/url').respond(200, 'when'); hb.expect('GET', '/url').respond(300, 'expect'); hb('GET', '/url', null, callback, {}); hb.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should throw exception when only headers differs from expectation', function() { hb.when('GET').respond(200, '', {}); hb.expect('GET', '/match', undefined, {'Content-Type': 'application/json'}); expect(function() { hb('GET', '/match', null, noop, {}); }).toThrowError('Expected GET /match with different headers\n' + 'EXPECTED: {"Content-Type":"application/json"}\nGOT: {}'); }); it('should throw exception when only data differs from expectation', function() { hb.when('GET').respond(200, '', {}); hb.expect('GET', '/match', 'some-data'); expect(function() { hb('GET', '/match', 'different', noop, {}); }).toThrowError('Expected GET /match with different data\n' + 'EXPECTED: some-data\nGOT: different'); }); it('should not throw an exception when parsed body is equal to expected body object', function() { hb.when('GET').respond(200, '', {}); hb.expect('GET', '/match', {a: 1, b: 2}); expect(function() { hb('GET', '/match', '{"a":1,"b":2}', noop, {}); }).not.toThrow(); hb.expect('GET', '/match', {a: 1, b: 2}); expect(function() { hb('GET', '/match', '{"b":2,"a":1}', noop, {}); }).not.toThrow(); }); it('should throw exception when only parsed body differs from expected body object', function() { hb.when('GET').respond(200, '', {}); hb.expect('GET', '/match', {a: 1, b: 2}); expect(function() { hb('GET', '/match', '{"a":1,"b":3}', noop, {}); }).toThrowError('Expected GET /match with different data\n' + 'EXPECTED: {"a":1,"b":2}\nGOT: {"a":1,"b":3}'); }); it('should use when\'s respond() when no expect() respond is defined', function() { callback.and.callFake(function(status, response) { expect(status).toBe(201); expect(response).toBe('data'); }); hb.when('GET', '/some').respond(201, 'data'); hb.expect('GET', '/some'); hb('GET', '/some', null, callback); hb.flush(); expect(callback).toHaveBeenCalled(); expect(function() { hb.verifyNoOutstandingExpectation(); }).not.toThrow(); }); }); describe('flush()', function() { it('flush() should flush requests fired during callbacks', function() { hb.when('GET').respond(200, ''); hb('GET', '/some', null, function() { hb('GET', '/other', null, callback); }); hb.flush(); expect(callback).toHaveBeenCalled(); }); it('should flush given number of pending requests', function() { hb.when('GET').respond(200, ''); hb('GET', '/some', null, callback); hb('GET', '/some', null, callback); hb('GET', '/some', null, callback); hb.flush(2); expect(callback).toHaveBeenCalled(); expect(callback).toHaveBeenCalledTimes(2); }); it('should flush given number of pending requests beginning at specified request', function() { var dontCallMe = jasmine.createSpy('dontCallMe'); hb.when('GET').respond(200, ''); hb('GET', '/some', null, dontCallMe); hb('GET', '/some', null, callback); hb('GET', '/some', null, callback); hb('GET', '/some', null, dontCallMe); hb.flush(2, 1); expect(dontCallMe).not.toHaveBeenCalled(); expect(callback).toHaveBeenCalledTimes(2); }); it('should flush all pending requests beginning at specified request', function() { var dontCallMe = jasmine.createSpy('dontCallMe'); hb.when('GET').respond(200, ''); hb('GET', '/some', null, dontCallMe); hb('GET', '/some', null, dontCallMe); hb('GET', '/some', null, callback); hb('GET', '/some', null, callback); hb.flush(null, 2); expect(dontCallMe).not.toHaveBeenCalled(); expect(callback).toHaveBeenCalledTimes(2); }); it('should throw exception when flushing more requests than pending', function() { hb.when('GET').respond(200, ''); hb('GET', '/url', null, callback); expect(function() {hb.flush(2);}).toThrowError('No more pending request to flush !'); expect(callback).toHaveBeenCalledOnce(); }); it('should throw exception when no request to flush', function() { expect(function() {hb.flush();}).toThrowError('No pending request to flush !'); hb.when('GET').respond(200, ''); hb('GET', '/some', null, callback); expect(function() {hb.flush(null, 1);}).toThrowError('No pending request to flush !'); hb.flush(); expect(function() {hb.flush();}).toThrowError('No pending request to flush !'); }); it('should throw exception if not all expectations satisfied', function() { hb.expect('GET', '/url1').respond(); hb.expect('GET', '/url2').respond(); hb('GET', '/url1', null, angular.noop); expect(function() {hb.flush();}).toThrowError('Unsatisfied requests: GET /url2'); }); }); it('should abort requests when timeout promise resolves', function() { hb.expect('GET', '/url1').respond(200); var canceler, then = jasmine.createSpy('then').and.callFake(function(fn) { canceler = fn; }); hb('GET', '/url1', null, callback, null, {then: then}); expect(typeof canceler).toBe('function'); canceler(); // simulate promise resolution expect(callback).toHaveBeenCalledWith(-1, undefined, '', undefined, 'abort'); hb.verifyNoOutstandingExpectation(); hb.verifyNoOutstandingRequest(); }); it('should abort requests when timeout passed as a numeric value', inject(function($timeout) { hb.expect('GET', '/url1').respond(200); hb('GET', '/url1', null, callback, null, 200); $timeout.flush(300); expect(callback).toHaveBeenCalledWith(-1, undefined, '', undefined, 'timeout'); hb.verifyNoOutstandingExpectation(); hb.verifyNoOutstandingRequest(); })); it('should throw an exception if no response defined', function() { hb.when('GET', '/test'); expect(function() { hb('GET', '/test', null, callback); }).toThrowError('No response defined !'); }); it('should throw an exception if no response for exception and no definition', function() { hb.expect('GET', '/url'); expect(function() { hb('GET', '/url', null, callback); }).toThrowError('No response defined !'); }); it('should respond undefined when JSONP method', function() { hb.when('JSONP', '/url1').respond(200); hb.expect('JSONP', '/url2').respond(200); expect(hb('JSONP', '/url1')).toBeUndefined(); expect(hb('JSONP', '/url2')).toBeUndefined(); }); it('should not have passThrough method', function() { expect(hb.passThrough).toBeUndefined(); }); describe('verifyExpectations', function() { it('should throw exception if not all expectations were satisfied', function() { hb.expect('POST', '/u1', 'ddd').respond(201, '', {}); hb.expect('GET', '/u2').respond(200, '', {}); hb.expect('POST', '/u3').respond(201, '', {}); hb('POST', '/u1', 'ddd', noop, {}); expect(function() {hb.verifyNoOutstandingExpectation();}). toThrowError('Unsatisfied requests: GET /u2, POST /u3'); }); it('should do nothing when no expectation', function() { hb.when('DELETE', '/some').respond(200, ''); expect(function() {hb.verifyNoOutstandingExpectation();}).not.toThrow(); }); it('should do nothing when all expectations satisfied', function() { hb.expect('GET', '/u2').respond(200, '', {}); hb.expect('POST', '/u3').respond(201, '', {}); hb.when('DELETE', '/some').respond(200, ''); hb('GET', '/u2', noop); hb('POST', '/u3', noop); expect(function() {hb.verifyNoOutstandingExpectation();}).not.toThrow(); }); }); describe('verifyRequests', function() { it('should throw exception if not all requests were flushed', function() { hb.when('GET').respond(200); hb('GET', '/some', null, noop, {}); expect(function() { hb.verifyNoOutstandingRequest(); }).toThrowError('Unflushed requests: 1\n' + ' GET /some'); }); it('should verify requests fired asynchronously', inject(function($q) { hb.when('GET').respond(200); $q.resolve().then(function() { hb('GET', '/some', null, noop, {}); }); expect(function() { hb.verifyNoOutstandingRequest(); }).toThrowError('Unflushed requests: 1\n' + ' GET /some'); })); it('should describe multiple unflushed requests', function() { hb.when('GET').respond(200); hb.when('PUT').respond(200); hb('GET', '/some', null, noop, {}); hb('PUT', '/elsewhere', null, noop, {}); expect(function() { hb.verifyNoOutstandingRequest(); }).toThrowError('Unflushed requests: 2\n' + ' GET /some\n' + ' PUT /elsewhere'); }); }); describe('resetExpectations', function() { it('should remove all expectations', function() { hb.expect('GET', '/u2').respond(200, '', {}); hb.expect('POST', '/u3').respond(201, '', {}); hb.resetExpectations(); expect(function() {hb.verifyNoOutstandingExpectation();}).not.toThrow(); }); it('should remove all pending responses', function() { var cancelledClb = jasmine.createSpy('cancelled'); hb.expect('GET', '/url').respond(200, ''); hb('GET', '/url', null, cancelledClb); hb.resetExpectations(); hb.expect('GET', '/url').respond(300, ''); hb('GET', '/url', null, callback, {}); hb.flush(); expect(callback).toHaveBeenCalledOnce(); expect(cancelledClb).not.toHaveBeenCalled(); }); it('should not remove definitions', function() { var cancelledClb = jasmine.createSpy('cancelled'); hb.when('GET', '/url').respond(200, 'success'); hb('GET', '/url', null, cancelledClb); hb.resetExpectations(); hb('GET', '/url', null, callback, {}); hb.flush(); expect(callback).toHaveBeenCalledOnce(); expect(cancelledClb).not.toHaveBeenCalled(); }); }); describe('expect/when shortcuts', function() { angular.forEach(['expect', 'when'], function(prefix) { angular.forEach(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'JSONP'], function(method) { var shortcut = prefix + method; it('should provide ' + shortcut + ' shortcut method', function() { hb[shortcut]('/foo').respond('bar'); hb(method, '/foo', undefined, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'bar', '', '', 'complete'); }); }); }); }); describe('expectRoute/whenRoute shortcuts', function() { angular.forEach(['expectRoute', 'whenRoute'], function(routeShortcut) { var methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'JSONP']; they('should provide ' + routeShortcut + ' shortcut with $prop method', methods, function() { hb[routeShortcut](this, '/route').respond('path'); hb(this, '/route', undefined, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '', 'complete'); } ); they('should match colon delimited parameters in ' + routeShortcut + ' $prop method', methods, function() { hb[routeShortcut](this, '/route/:id/path/:s_id').respond('path'); hb(this, '/route/123/path/456', undefined, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '', 'complete'); } ); they('should ignore query params when matching in ' + routeShortcut + ' $prop method', methods, function() { angular.forEach([ {route: '/route1/:id', url: '/route1/Alpha', expectedParams: {id: 'Alpha'}}, {route: '/route2/:id', url: '/route2/Bravo/?', expectedParams: {id: 'Bravo'}}, {route: '/route3/:id', url: '/route3/Charlie?q=str&foo=bar', expectedParams: {id: 'Charlie', q: 'str', foo: 'bar'}}, {route: '/:x/route4', url: '/Delta/route4?q=str&foo=bar', expectedParams: {x: 'Delta', q: 'str', foo: 'bar'}}, {route: '/route5/:id*', url: '/route5/Echo/456?q=str&foo=bar', expectedParams: {id: 'Echo/456', q: 'str', foo: 'bar'}}, {route: '/route6/:id*', url: '/route6/Foxtrot/456/?q=str&foo=bar', expectedParams: {id: 'Foxtrot/456', q: 'str', foo: 'bar'}}, {route: '/route7/:id*', url: '/route7/Golf/456//?q=str&foo=bar', expectedParams: {id: 'Golf/456', q: 'str', foo: 'bar'}}, {route: '/:x*/route8', url: '/Hotel/123/456/route8/?q=str&foo=bar', expectedParams: {x: 'Hotel/123/456', q: 'str', foo: 'bar'}}, {route: '/:x*/route9/:id', url: '/India/456/route9/0?q=str&foo=bar', expectedParams: {x: 'India/456', id: '0', q: 'str', foo: 'bar'}}, {route: '/route10', url: '/route10?q=Juliet&foo=bar', expectedParams: {q: 'Juliet', foo: 'bar'}}, {route: '/route11', url: '/route11///?q=Kilo', expectedParams: {q: 'Kilo'}}, {route: '/route12', url: '/route12///', expectedParams: {}} ], function(testDataEntry) { callback.calls.reset(); var paramsSpy = jasmine.createSpy('params'); hb[routeShortcut](this, testDataEntry.route).respond( function(method, url, data, headers, params) { paramsSpy(params); // status, response, headers, statusText, xhrStatus return [200, 'path', { 'x-header': 'foo' }, 'OK', 'complete']; } ); hb(this, testDataEntry.url, undefined, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'path', 'x-header: foo', 'OK', 'complete'); expect(paramsSpy).toHaveBeenCalledOnceWith(testDataEntry.expectedParams); }); } ); }); }); describe('MockHttpExpectation', function() { /* global MockHttpExpectation */ it('should accept url as regexp', function() { var exp = new MockHttpExpectation('GET', /^\/x/); expect(exp.match('GET', '/x')).toBe(true); expect(exp.match('GET', '/xxx/x')).toBe(true); expect(exp.match('GET', 'x')).toBe(false); expect(exp.match('GET', 'a/x')).toBe(false); }); it('should match url with same query params, but different order', function() { var exp = new MockHttpExpectation('GET', 'www.example.com/x/y?a=b&c=d&e=f'); expect(exp.matchUrl('www.example.com/x/y?e=f&c=d&a=b')).toBe(true); }); it('should accept url as function', function() { var urlValidator = function(url) { return url !== '/not-accepted'; }; var exp = new MockHttpExpectation('POST', urlValidator); expect(exp.match('POST', '/url')).toBe(true); expect(exp.match('POST', '/not-accepted')).toBe(false); }); it('should accept data as regexp', function() { var exp = new MockHttpExpectation('POST', '/url', /\{.*?\}/); expect(exp.match('POST', '/url', '{"a": "aa"}')).toBe(true); expect(exp.match('POST', '/url', '{"one": "two"}')).toBe(true); expect(exp.match('POST', '/url', '{"one"')).toBe(false); }); it('should accept data as function', function() { var dataValidator = function(data) { var json = angular.fromJson(data); return !!json.id && json.status === 'N'; }; var exp = new MockHttpExpectation('POST', '/url', dataValidator); expect(exp.matchData({})).toBe(false); expect(exp.match('POST', '/url', '{"id": "xxx", "status": "N"}')).toBe(true); expect(exp.match('POST', '/url', {'id': 'xxx', 'status': 'N'})).toBe(true); }); it('should ignore data only if undefined (not null or false)', function() { var exp = new MockHttpExpectation('POST', '/url', null); expect(exp.matchData(null)).toBe(true); expect(exp.matchData('some-data')).toBe(false); exp = new MockHttpExpectation('POST', '/url', undefined); expect(exp.matchData(null)).toBe(true); expect(exp.matchData('some-data')).toBe(true); }); it('should accept headers as function', function() { var exp = new MockHttpExpectation('GET', '/url', undefined, function(h) { return h['Content-Type'] === 'application/json'; }); expect(exp.matchHeaders({})).toBe(false); expect(exp.matchHeaders({'Content-Type': 'application/json', 'X-Another': 'true'})).toBe(true); }); }); }); describe('$rootElement', function() { it('should create mock application root', inject(function($rootElement) { expect($rootElement.text()).toEqual(''); })); it('should attach the `$injector` to `$rootElement`', inject(function($injector, $rootElement) { expect($rootElement.injector()).toBe($injector); })); }); describe('$rootScopeDecorator', function() { describe('$countChildScopes', function() { it('should return 0 when no child scopes', inject(function($rootScope) { expect($rootScope.$countChildScopes()).toBe(0); var childScope = $rootScope.$new(); expect($rootScope.$countChildScopes()).toBe(1); expect(childScope.$countChildScopes()).toBe(0); var grandChildScope = childScope.$new(); expect(childScope.$countChildScopes()).toBe(1); expect(grandChildScope.$countChildScopes()).toBe(0); })); it('should correctly navigate complex scope tree', inject(function($rootScope) { var child; $rootScope.$new(); $rootScope.$new().$new().$new(); child = $rootScope.$new().$new(); child.$new(); child.$new(); child.$new().$new().$new(); expect($rootScope.$countChildScopes()).toBe(11); })); it('should provide the current count even after child destructions', inject(function($rootScope) { expect($rootScope.$countChildScopes()).toBe(0); var childScope1 = $rootScope.$new(); expect($rootScope.$countChildScopes()).toBe(1); var childScope2 = $rootScope.$new(); expect($rootScope.$countChildScopes()).toBe(2); childScope1.$destroy(); expect($rootScope.$countChildScopes()).toBe(1); childScope2.$destroy(); expect($rootScope.$countChildScopes()).toBe(0); })); it('should work with isolate scopes', inject(function($rootScope) { /* RS | CIS / \ GCS GCIS */ var childIsolateScope = $rootScope.$new(true); expect($rootScope.$countChildScopes()).toBe(1); var grandChildScope = childIsolateScope.$new(); expect($rootScope.$countChildScopes()).toBe(2); expect(childIsolateScope.$countChildScopes()).toBe(1); var grandChildIsolateScope = childIsolateScope.$new(true); expect($rootScope.$countChildScopes()).toBe(3); expect(childIsolateScope.$countChildScopes()).toBe(2); childIsolateScope.$destroy(); expect($rootScope.$countChildScopes()).toBe(0); })); }); describe('$countWatchers', function() { it('should return the sum of watchers for the current scope and all of its children', inject( function($rootScope) { expect($rootScope.$countWatchers()).toBe(0); var childScope = $rootScope.$new(); expect($rootScope.$countWatchers()).toBe(0); childScope.$watch('foo'); expect($rootScope.$countWatchers()).toBe(1); expect(childScope.$countWatchers()).toBe(1); $rootScope.$watch('bar'); childScope.$watch('baz'); expect($rootScope.$countWatchers()).toBe(3); expect(childScope.$countWatchers()).toBe(2); })); it('should correctly navigate complex scope tree', inject(function($rootScope) { var child; $rootScope.$watch('foo1'); $rootScope.$new(); $rootScope.$new().$new().$new(); child = $rootScope.$new().$new(); child.$watch('foo2'); child.$new(); child.$new(); child = child.$new().$new().$new(); child.$watch('foo3'); child.$watch('foo4'); expect($rootScope.$countWatchers()).toBe(4); })); it('should provide the current count even after child destruction and watch deregistration', inject(function($rootScope) { var deregisterWatch1 = $rootScope.$watch('exp1'); var childScope = $rootScope.$new(); childScope.$watch('exp2'); expect($rootScope.$countWatchers()).toBe(2); childScope.$destroy(); expect($rootScope.$countWatchers()).toBe(1); deregisterWatch1(); expect($rootScope.$countWatchers()).toBe(0); })); it('should work with isolate scopes', inject(function($rootScope) { /* RS=1 | CIS=1 / \ GCS=1 GCIS=1 */ $rootScope.$watch('exp1'); expect($rootScope.$countWatchers()).toBe(1); var childIsolateScope = $rootScope.$new(true); childIsolateScope.$watch('exp2'); expect($rootScope.$countWatchers()).toBe(2); expect(childIsolateScope.$countWatchers()).toBe(1); var grandChildScope = childIsolateScope.$new(); grandChildScope.$watch('exp3'); var grandChildIsolateScope = childIsolateScope.$new(true); grandChildIsolateScope.$watch('exp4'); expect($rootScope.$countWatchers()).toBe(4); expect(childIsolateScope.$countWatchers()).toBe(3); expect(grandChildScope.$countWatchers()).toBe(1); expect(grandChildIsolateScope.$countWatchers()).toBe(1); childIsolateScope.$destroy(); expect($rootScope.$countWatchers()).toBe(1); })); }); }); describe('$controllerDecorator', function() { it('should support creating controller with bindings', function() { var called = false; var data = [ { name: 'derp1', id: 0 }, { name: 'testname', id: 1 }, { name: 'flurp', id: 2 } ]; module(function($controllerProvider) { $controllerProvider.register('testCtrl', function() { expect(this.data).toBeUndefined(); called = true; }); }); inject(function($controller, $rootScope) { var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); expect(ctrl.data).toBe(data); expect(called).toBe(true); }); }); it('should support assigning bindings when a value is returned from the constructor', function() { var called = false; var data = [ { name: 'derp1', id: 0 }, { name: 'testname', id: 1 }, { name: 'flurp', id: 2 } ]; module(function($controllerProvider) { $controllerProvider.register('testCtrl', function() { expect(this.data).toBeUndefined(); called = true; return {}; }); }); inject(function($controller, $rootScope) { var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); expect(ctrl.data).toBe(data); expect(called).toBe(true); }); } ); if (support.classes) { it('should support assigning bindings to class-based controller', function() { var called = false; var data = [ { name: 'derp1', id: 0 }, { name: 'testname', id: 1 }, { name: 'flurp', id: 2 } ]; module(function($controllerProvider) { // eslint-disable-next-line no-eval var TestCtrl = eval('(class { constructor() { called = true; } })'); $controllerProvider.register('testCtrl', TestCtrl); }); inject(function($controller, $rootScope) { var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); expect(ctrl.data).toBe(data); expect(called).toBe(true); }); }); } }); describe('$componentController', function() { it('should instantiate a simple controller defined inline in a component', function() { function TestController($scope, a, b) { this.$scope = $scope; this.a = a; this.b = b; } module(function($compileProvider) { $compileProvider.component('test', { controller: TestController }); }); inject(function($componentController, $rootScope) { var $scope = {}; var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); expect(ctrl).toEqual(extend(new TestController($scope, 'A', 'B'), { x: 'X', y: 'Y' })); expect($scope.$ctrl).toBe(ctrl); }); }); it('should instantiate a controller with $$inject annotation defined inline in a component', function() { function TestController(x, y, z) { this.$scope = x; this.a = y; this.b = z; } TestController.$inject = ['$scope', 'a', 'b']; module(function($compileProvider) { $compileProvider.component('test', { controller: TestController }); }); inject(function($componentController, $rootScope) { var $scope = {}; var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); expect(ctrl).toEqual(extend(new TestController($scope, 'A', 'B'), { x: 'X', y: 'Y' })); expect($scope.$ctrl).toBe(ctrl); }); }); it('should instantiate a named controller defined in a component', function() { function TestController($scope, a, b) { this.$scope = $scope; this.a = a; this.b = b; } module(function($controllerProvider, $compileProvider) { $controllerProvider.register('TestController', TestController); $compileProvider.component('test', { controller: 'TestController' }); }); inject(function($componentController, $rootScope) { var $scope = {}; var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); expect(ctrl).toEqual(extend(new TestController($scope, 'A', 'B'), { x: 'X', y: 'Y' })); expect($scope.$ctrl).toBe(ctrl); }); }); it('should instantiate a named controller with `controller as` syntax defined in a component', function() { function TestController($scope, a, b) { this.$scope = $scope; this.a = a; this.b = b; } module(function($controllerProvider, $compileProvider) { $controllerProvider.register('TestController', TestController); $compileProvider.component('test', { controller: 'TestController as testCtrl' }); }); inject(function($componentController, $rootScope) { var $scope = {}; var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); expect(ctrl).toEqual(extend(new TestController($scope, 'A', 'B'), {x: 'X', y: 'Y'})); expect($scope.testCtrl).toBe(ctrl); }); }); it('should instantiate the controller of the restrict:\'E\' component if there are more directives with the same name but not restricted to \'E\'', function() { function TestController() { this.r = 6779; } module(function($compileProvider) { $compileProvider.directive('test', function() { return { restrict: 'A' }; }); $compileProvider.component('test', { controller: TestController }); }); inject(function($componentController, $rootScope) { var ctrl = $componentController('test', { $scope: {} }); expect(ctrl).toEqual(new TestController()); }); }); it('should instantiate the controller of the restrict:\'E\' component if there are more directives with the same name and restricted to \'E\' but no controller', function() { function TestController() { this.r = 22926; } module(function($compileProvider) { $compileProvider.directive('test', function() { return { restrict: 'E' }; }); $compileProvider.component('test', { controller: TestController }); }); inject(function($componentController, $rootScope) { var ctrl = $componentController('test', { $scope: {} }); expect(ctrl).toEqual(new TestController()); }); }); it('should instantiate the controller of the directive with controller, controllerAs and restrict:\'E\' if there are more directives', function() { function TestController() { this.r = 18842; } module(function($compileProvider) { $compileProvider.directive('test', function() { return { }; }); $compileProvider.directive('test', function() { return { restrict: 'E', controller: TestController, controllerAs: '$ctrl' }; }); }); inject(function($componentController, $rootScope) { var ctrl = $componentController('test', { $scope: {} }); expect(ctrl).toEqual(new TestController()); }); }); it('should fail if there is no directive with restrict:\'E\' and controller', function() { function TestController() { this.r = 31145; } module(function($compileProvider) { $compileProvider.directive('test', function() { return { restrict: 'AC', controller: TestController }; }); $compileProvider.directive('test', function() { return { restrict: 'E', controller: TestController }; }); $compileProvider.directive('test', function() { return { restrict: 'EA', controller: TestController, controllerAs: '$ctrl' }; }); $compileProvider.directive('test', function() { return { restrict: 'E' }; }); }); inject(function($componentController, $rootScope) { expect(function() { $componentController('test', { $scope: {} }); }).toThrowError('No component found'); }); }); it('should fail if there more than two components with same name', function() { function TestController($scope, a, b) { this.$scope = $scope; this.a = a; this.b = b; } module(function($compileProvider) { $compileProvider.directive('test', function() { return { restrict: 'E', controller: TestController, controllerAs: '$ctrl' }; }); $compileProvider.component('test', { controller: TestController }); }); inject(function($componentController, $rootScope) { expect(function() { var $scope = {}; $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); }).toThrowError('Too many components found'); }); }); it('should create an isolated child of $rootScope, if no `$scope` local is provided', function() { function TestController($scope) { this.$scope = $scope; } module(function($compileProvider) { $compileProvider.component('test', { controller: TestController }); }); inject(function($componentController, $rootScope) { var $ctrl = $componentController('test'); expect($ctrl.$scope).toBeDefined(); expect($ctrl.$scope.$parent).toBe($rootScope); // check it is isolated $rootScope.a = 17; expect($ctrl.$scope.a).toBeUndefined(); $ctrl.$scope.a = 42; expect($rootScope.a).toEqual(17); }); }); }); }); describe('ngMockE2E', function() { describe('$httpBackend', function() { var hb, realHttpBackend, realHttpBackendBrowser, $http, callback; beforeEach(function() { callback = jasmine.createSpy('callback'); angular.module('ng').config(function($provide) { realHttpBackend = jasmine.createSpy('real $httpBackend'); $provide.factory('$httpBackend', ['$browser', function($browser) { return realHttpBackend.and.callFake(function() { realHttpBackendBrowser = $browser; }); }]); }); module('ngMockE2E'); inject(function($injector) { hb = $injector.get('$httpBackend'); $http = $injector.get('$http'); }); }); it('should throw error when unexpected request - without error callback', function() { expect(function() { $http.get('/some').then(noop); hb.verifyNoOutstandingRequest(); }).toThrowError('Unexpected request: GET /some\nNo more request expected'); }); it('should throw error when unexpected request - with error callback', function() { expect(function() { $http.get('/some').then(noop, noop); hb.verifyNoOutstandingRequest(); }).toThrowError('Unexpected request: GET /some\nNo more request expected'); }); describe('passThrough()', function() { it('should delegate requests to the real backend when passThrough is invoked', function() { var eventHandlers = {progress: angular.noop}; var uploadEventHandlers = {progress: angular.noop}; hb.when('GET', /\/passThrough\/.*/).passThrough(); hb('GET', '/passThrough/23', null, callback, {}, null, true, 'blob', eventHandlers, uploadEventHandlers); expect(realHttpBackend).toHaveBeenCalledOnceWith( 'GET', '/passThrough/23', null, callback, {}, null, true, 'blob', eventHandlers, uploadEventHandlers); }); it('should be able to override a respond definition with passThrough', function() { var definition = hb.when('GET', /\/passThrough\/.*/).respond('override me'); definition.passThrough(); hb('GET', '/passThrough/23', null, callback, {}, null, true); expect(realHttpBackend).toHaveBeenCalledOnceWith( 'GET', '/passThrough/23', null, callback, {}, null, true, undefined, undefined, undefined); }); it('should be able to override a respond definition with passThrough', inject(function($browser) { var definition = hb.when('GET', /\/passThrough\/.*/).passThrough(); definition.respond('passThrough override'); hb('GET', '/passThrough/23', null, callback, {}, null, true); $browser.defer.flush(); expect(realHttpBackend).not.toHaveBeenCalled(); expect(callback).toHaveBeenCalledOnceWith(200, 'passThrough override', '', '', 'complete'); })); it('should pass through to an httpBackend that uses the same $browser service', inject(function($browser) { hb.when('GET', /\/passThrough\/.*/).passThrough(); hb('GET', '/passThrough/23'); expect(realHttpBackend).toHaveBeenCalledOnce(); expect(realHttpBackendBrowser).toBe($browser); })); }); describe('autoflush', function() { it('should flush responses via $browser.defer', inject(function($browser) { hb.when('GET', '/foo').respond('bar'); hb('GET', '/foo', null, callback); expect(callback).not.toHaveBeenCalled(); $browser.defer.flush(); expect(callback).toHaveBeenCalledOnce(); })); }); }); describe('ngAnimateMock', function() { beforeEach(module('ngAnimate')); beforeEach(module('ngAnimateMock')); var ss, element, trackedAnimations, animationLog; afterEach(function() { if (element) { element.remove(); } if (ss) { ss.destroy(); } }); beforeEach(module(function($animateProvider) { trackedAnimations = []; animationLog = []; $animateProvider.register('.animate', function() { return { leave: logFn('leave'), addClass: logFn('addClass') }; function logFn(method) { return function(element) { animationLog.push('start ' + method); trackedAnimations.push(getDoneCallback(arguments)); return function closingFn(cancel) { var lab = cancel ? 'cancel' : 'end'; animationLog.push(lab + ' ' + method); }; }; } function getDoneCallback(args) { for (var i = args.length; i > 0; i--) { if (angular.isFunction(args[i])) return args[i]; } } }); return function($animate, $rootElement, $document, $rootScope) { ss = createMockStyleSheet($document); element = angular.element('
'); $rootElement.append(element); angular.element($document[0].body).append($rootElement); $animate.enabled(true); $rootScope.$digest(); }; })); describe('$animate.queue', function() { it('should maintain a queue of the executed animations', inject(function($animate) { element.removeClass('animate'); // we don't care to test any actual animations var options = {}; $animate.addClass(element, 'on', options); var first = $animate.queue[0]; expect(first.element).toBe(element); expect(first.event).toBe('addClass'); expect(first.options).toBe(options); $animate.removeClass(element, 'off', options); var second = $animate.queue[1]; expect(second.element).toBe(element); expect(second.event).toBe('removeClass'); expect(second.options).toBe(options); $animate.leave(element, options); var third = $animate.queue[2]; expect(third.element).toBe(element); expect(third.event).toBe('leave'); expect(third.options).toBe(options); })); }); describe('$animate.flush()', function() { it('should throw an error if there is nothing to animate', inject(function($animate) { expect(function() { $animate.flush(); }).toThrowError('No pending animations ready to be closed or flushed'); })); it('should trigger the animation to start', inject(function($animate) { expect(trackedAnimations.length).toBe(0); $animate.leave(element); $animate.flush(); expect(trackedAnimations.length).toBe(1); })); it('should trigger the animation to end once run and called', inject(function($animate) { $animate.leave(element); $animate.flush(); expect(element.parent().length).toBe(1); trackedAnimations[0](); $animate.flush(); expect(element.parent().length).toBe(0); })); it('should trigger the animation promise callback to fire once run and closed', inject(function($animate) { var doneSpy = jasmine.createSpy(); $animate.leave(element).then(doneSpy); $animate.flush(); trackedAnimations[0](); expect(doneSpy).not.toHaveBeenCalled(); $animate.flush(); expect(doneSpy).toHaveBeenCalled(); })); it('should trigger a series of CSS animations to trigger and start once run', inject(function($animate, $rootScope) { if (!browserSupportsCssAnimations()) return; ss.addRule('.leave-me.ng-leave', 'transition:1s linear all;'); var i, elm, elms = []; for (i = 0; i < 5; i++) { elm = angular.element('
'); element.append(elm); elms.push(elm); $animate.leave(elm); } $rootScope.$digest(); for (i = 0; i < 5; i++) { elm = elms[i]; expect(elm.hasClass('ng-leave')).toBe(true); expect(elm.hasClass('ng-leave-active')).toBe(false); } $animate.flush(); for (i = 0; i < 5; i++) { elm = elms[i]; expect(elm.hasClass('ng-leave')).toBe(true); expect(elm.hasClass('ng-leave-active')).toBe(true); } })); it('should trigger parent and child animations to run within the same flush', inject(function($animate, $rootScope) { var child = angular.element('
'); element.append(child); expect(trackedAnimations.length).toBe(0); $animate.addClass(element, 'go'); $animate.addClass(child, 'start'); $animate.flush(); expect(trackedAnimations.length).toBe(2); })); it('should trigger animation callbacks when called', inject(function($animate, $rootScope) { var spy = jasmine.createSpy(); $animate.on('addClass', element, spy); $animate.addClass(element, 'on'); expect(spy).not.toHaveBeenCalled(); $animate.flush(); expect(spy).toHaveBeenCalledTimes(1); trackedAnimations[0](); $animate.flush(); expect(spy).toHaveBeenCalledTimes(2); })); }); describe('$animate.closeAndFlush()', function() { it('should close the currently running $animateCss animations', inject(function($animateCss, $animate) { if (!browserSupportsCssAnimations()) return; var spy = jasmine.createSpy(); var runner = $animateCss(element, { duration: 1, to: { color: 'red' } }).start(); runner.then(spy); expect(spy).not.toHaveBeenCalled(); $animate.closeAndFlush(); expect(spy).toHaveBeenCalled(); })); it('should close the currently running $$animateJs animations', inject(function($$animateJs, $animate) { var spy = jasmine.createSpy(); var runner = $$animateJs(element, 'leave', 'animate', {}).start(); runner.then(spy); expect(spy).not.toHaveBeenCalled(); $animate.closeAndFlush(); expect(spy).toHaveBeenCalled(); })); it('should run the closing javascript animation function upon flush', inject(function($$animateJs, $animate) { $$animateJs(element, 'leave', 'animate', {}).start(); expect(animationLog).toEqual(['start leave']); $animate.closeAndFlush(); expect(animationLog).toEqual(['start leave', 'end leave']); })); it('should not throw when a regular animation has no javascript animation', inject(function($animate, $$animation, $rootElement) { if (!browserSupportsCssAnimations()) return; var element = jqLite('
'); $rootElement.append(element); // Make sure the animation has valid $animateCss options $$animation(element, null, { from: { background: 'red' }, to: { background: 'blue' }, duration: 1, transitionStyle: 'all 1s' }); expect(function() { $animate.closeAndFlush(); }).not.toThrow(); dealoc(element); })); it('should throw an error if there are no animations to close and flush', inject(function($animate) { expect(function() { $animate.closeAndFlush(); }).toThrowError('No pending animations ready to be closed or flushed'); })); }); }); }); describe('make sure that we can create an injector outside of tests', function() { //since some libraries create custom injectors outside of tests, //we want to make sure that this is not breaking the internals of //how we manage annotated function cleanup during tests. See #10967 angular.injector([function($injector) {}]); }); describe('`afterEach` clean-up', function() { describe('`$rootElement`', function() { describe('undecorated', function() { var prevRootElement; var prevCleanDataSpy; it('should set up spies for the next test to verify that `$rootElement` was cleaned up', function() { module(function($provide) { $provide.decorator('$rootElement', function($delegate) { prevRootElement = $delegate; // Spy on `angular.element.cleanData()`, so the next test can verify // that it has been called as necessary prevCleanDataSpy = spyOn(angular.element, 'cleanData').and.callThrough(); return $delegate; }); }); // Inject the `$rootElement` to ensure it has been created inject(function($rootElement) { expect($rootElement.injector()).toBeDefined(); }); } ); it('should clean up `$rootElement` after each test', function() { // One call is made by `testabilityPatch`'s `dealoc()` // We want to verify the subsequent call, made by `angular-mocks` expect(prevCleanDataSpy).toHaveBeenCalledTimes(2); var cleanUpNodes = prevCleanDataSpy.calls.argsFor(1)[0]; expect(cleanUpNodes.length).toBe(1); expect(cleanUpNodes[0]).toBe(prevRootElement[0]); }); }); describe('decorated', function() { var prevOriginalRootElement; var prevRootElement; var prevCleanDataSpy; it('should set up spies for the next text to verify that `$rootElement` was cleaned up', function() { module(function($provide) { $provide.decorator('$rootElement', function($delegate) { prevOriginalRootElement = $delegate; // Mock `$rootElement` to be able to verify that the correct object is cleaned up prevRootElement = angular.element('
'); // Spy on `angular.element.cleanData()`, so the next test can verify // that it has been called as necessary prevCleanDataSpy = spyOn(angular.element, 'cleanData').and.callThrough(); return prevRootElement; }); }); // Inject the `$rootElement` to ensure it has been created inject(function($rootElement) { expect($rootElement).toBe(prevRootElement); expect(prevOriginalRootElement.injector()).toBeDefined(); expect(prevRootElement.injector()).toBeUndefined(); // If we don't clean up `prevOriginalRootElement`-related data now, `testabilityPatch` will // complain about a memory leak, because it doesn't clean up after the original // `$rootElement` // This is a false alarm, because `angular-mocks` would have cleaned up in a subsequent // `afterEach` block prevOriginalRootElement.removeData(); }); } ); it('should clean up `$rootElement` (both original and decorated) after each test', function() { // One call is made by `testabilityPatch`'s `dealoc()` // We want to verify the subsequent call, made by `angular-mocks` expect(prevCleanDataSpy).toHaveBeenCalledTimes(2); var cleanUpNodes = prevCleanDataSpy.calls.argsFor(1)[0]; expect(cleanUpNodes.length).toBe(2); expect(cleanUpNodes[0]).toBe(prevOriginalRootElement[0]); expect(cleanUpNodes[1]).toBe(prevRootElement[0]); } ); }); describe('uninstantiated or falsy', function() { it('should not break if `$rootElement` was never instantiated', function() { // Just an empty test to verify that `angular-mocks` doesn't break, // when trying to clean up `$rootElement`, if `$rootElement` was never injected in the test // (and thus never instantiated/created) // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places inject(function() {}); }); it('should not break if the decorated `$rootElement` is falsy (e.g. `null`)', function() { module({$rootElement: null}); // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places inject(function() {}); }); }); }); describe('`$rootScope`', function() { describe('undecorated', function() { var prevRootScope; var prevDestroySpy; it('should set up spies for the next test to verify that `$rootScope` was cleaned up', inject(function($rootScope) { prevRootScope = $rootScope; prevDestroySpy = spyOn($rootScope, '$destroy').and.callThrough(); }) ); it('should clean up `$rootScope` after each test', inject(function($rootScope) { expect($rootScope).not.toBe(prevRootScope); expect(prevDestroySpy).toHaveBeenCalledOnce(); expect(prevRootScope.$$destroyed).toBe(true); })); }); describe('falsy or without `$destroy()` method', function() { it('should not break if `$rootScope` is falsy (e.g. `null`)', function() { // Just an empty test to verify that `angular-mocks` doesn't break, // when trying to clean up a mocked `$rootScope` set to `null` module({$rootScope: null}); // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places inject(function() {}); }); it('should not break if `$rootScope.$destroy` is not a function', function() { // Just an empty test to verify that `angular-mocks` doesn't break, // when trying to clean up a mocked `$rootScope` without a `$destroy()` method module({$rootScope: {}}); // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places inject(function() {}); }); }); }); }); describe('sharedInjector', function() { // this is of a bit tricky feature to test as we hit angular's own testing // mechanisms (e.g around jQuery cache checking), as ngMock augments the very // jasmine test runner we're using to test ngMock! // // with that in mind, we define a stubbed test framework // to simulate test cases being run with the ngMock hooks // we use the 'module' and 'inject' globals from ngMock it('allows me to mutate a single instance of a module (proving it has been shared)', ngMockTest(function() { sdescribe('test state is shared', function() { angular.module('sharedInjectorTestModuleA', []) .factory('testService', function() { return { state: 0 }; }); module.sharedInjector(); sbeforeAll(module('sharedInjectorTestModuleA')); sit('access and mutate', inject(function(testService) { testService.state += 1; })); sit('expect mutation to have persisted', inject(function(testService) { expect(testService.state).toEqual(1); })); }); })); it('works with standard beforeEach', ngMockTest(function() { sdescribe('test state is not shared', function() { angular.module('sharedInjectorTestModuleC', []) .factory('testService', function() { return { state: 0 }; }); sbeforeEach(module('sharedInjectorTestModuleC')); sit('access and mutate', inject(function(testService) { testService.state += 1; })); sit('expect mutation not to have persisted', inject(function(testService) { expect(testService.state).toEqual(0); })); }); })); it('allows me to stub with shared injector', ngMockTest(function() { sdescribe('test state is shared', function() { angular.module('sharedInjectorTestModuleD', []) .value('testService', 43); module.sharedInjector(); sbeforeAll(module('sharedInjectorTestModuleD', function($provide) { $provide.value('testService', 42); })); sit('expected access stubbed value', inject(function(testService) { expect(testService).toEqual(42); })); }); })); it('doesn\'t interfere with other test describes', ngMockTest(function() { angular.module('sharedInjectorTestModuleE', []) .factory('testService', function() { return { state: 0 }; }); sdescribe('with stubbed injector', function() { module.sharedInjector(); sbeforeAll(module('sharedInjectorTestModuleE')); sit('access and mutate', inject(function(testService) { expect(testService.state).toEqual(0); testService.state += 1; })); sit('expect mutation to have persisted', inject(function(testService) { expect(testService.state).toEqual(1); })); }); sdescribe('without stubbed injector', function() { sbeforeEach(module('sharedInjectorTestModuleE')); sit('access and mutate', inject(function(testService) { expect(testService.state).toEqual(0); testService.state += 1; })); sit('expect original, unmutated value', inject(function(testService) { expect(testService.state).toEqual(0); })); }); })); it('prevents nested use of sharedInjector()', function() { var test = ngMockTest(function() { sdescribe('outer', function() { module.sharedInjector(); sdescribe('inner', function() { module.sharedInjector(); sit('should not get here', function() { throw Error('should have thrown before here!'); }); }); }); }); assertThrowsErrorMatching(test.bind(this), /already called sharedInjector()/); }); it('warns that shared injector cannot be used unless test frameworks define before/after all hooks', function() { assertThrowsErrorMatching(function() { module.sharedInjector(); }, /sharedInjector()/); }); function assertThrowsErrorMatching(fn, re) { try { fn(); } catch (e) { if (re.test(e.message)) { return; } throw Error('thrown error \'' + e.message + '\' did not match:' + re); } throw Error('should have thrown error'); } // run a set of test cases in the sdescribe stub test framework function ngMockTest(define) { return function() { var spec = this; module.$$currentSpec(null); // configure our stubbed test framework and then hook ngMock into it // in much the same way module.$$beforeAllHook = sbeforeAll; module.$$afterAllHook = safterAll; sdescribe.root = sdescribe('root', function() {}); sdescribe.root.beforeEach.push(module.$$beforeEach); sdescribe.root.afterEach.push(module.$$afterEach); try { define(); sdescribe.root.run(); } finally { // clear up module.$$beforeAllHook = null; module.$$afterAllHook = null; module.$$currentSpec(spec); } }; } // stub test framework that follows the pattern of hooks that // jasmine/mocha do function sdescribe(name, define) { var self = { name: name }; self.parent = sdescribe.current || sdescribe.root; if (self.parent) { self.parent.describes.push(self); } var previous = sdescribe.current; sdescribe.current = self; self.beforeAll = []; self.beforeEach = []; self.afterAll = []; self.afterEach = []; self.define = define; self.tests = []; self.describes = []; self.run = function() { var spec = {}; self.hooks('beforeAll', spec); self.tests.forEach(function(test) { if (self.parent) self.parent.hooks('beforeEach', spec); self.hooks('beforeEach', spec); test.run.call(spec); self.hooks('afterEach', spec); if (self.parent) self.parent.hooks('afterEach', spec); }); self.describes.forEach(function(d) { d.run(); }); self.hooks('afterAll', spec); }; self.hooks = function(hook, spec) { self[hook].forEach(function(f) { f.call(spec); }); }; define(); sdescribe.current = previous; return self; } function sit(name, fn) { if (typeof fn !== 'function') throw Error('not fn', fn); sdescribe.current.tests.push({ name: name, run: fn }); } function sbeforeAll(fn) { if (typeof fn !== 'function') throw Error('not fn', fn); sdescribe.current.beforeAll.push(fn); } function safterAll(fn) { if (typeof fn !== 'function') throw Error('not fn', fn); sdescribe.current.afterAll.push(fn); } function sbeforeEach(fn) { if (typeof fn !== 'function') throw Error('not fn', fn); sdescribe.current.beforeEach.push(fn); } function safterEach(fn) { if (typeof fn !== 'function') throw Error('not fn', fn); sdescribe.current.afterEach.push(fn); } });