Files
angular.js/test/ngMock/angular-mocksSpec.js
T

2874 lines
90 KiB
JavaScript

'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 millenium = new Date('2000').getTime();
// millenium in -3h
var t0 = new angular.mock.TzDate(-3, millenium);
expect(t0.toLocaleDateString()).toMatch('2000');
// millenium in +0h
var t1 = new angular.mock.TzDate(0, millenium);
expect(t1.toLocaleDateString()).toMatch('2000');
// millenium in +3h
var t2 = new angular.mock.TzDate(3, millenium);
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);
}));
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 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 "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 "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);
}));
});
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('<div>abc</div><span>xyz</span>');
expect(d(e).toLowerCase()).toEqual('<div>abc</div><span>xyz</span>');
expect(d(e[0]).toLowerCase()).toEqual('<div>abc</div>');
});
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) {
/* jshint -W001 */
$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('module cleanup', function() {
function testFn() {
}
it('should add hashKey to module function', function() {
module(testFn);
inject(function() {
expect(testFn.$$hashKey).toBeDefined();
});
});
it('should cleanup hashKey after previous test', function() {
expect(testFn.$$hashKey).toBeUndefined();
});
});
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, 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 testCaller() {
return inject(function() {
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('testCaller');
}
});
});
} 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('$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', 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();
});
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') {
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 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', '', '']);
expect(callback.calls.argsFor(1)).toEqual([200, 'first', '', '']);
});
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');
});
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');
});
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');
});
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$', '', '');
});
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', '', '');
});
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', '', '']);
expect(callback.calls.argsFor(1)).toEqual([200, 'second', '', '']);
});
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', '', '');
});
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', '', '');
});
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', '', '');
});
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', '', '');
});
});
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 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);
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, '');
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, '');
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');
});
});
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', '', '');
});
});
});
});
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', '', '');
}
);
they('should match colon deliminated 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', '', '');
}
);
they('should ignore query param when matching in ' + routeShortcut + ' $prop method', methods,
function() {
hb[routeShortcut](this, '/route/:id').respond('path');
hb(this, '/route/123?q=str&foo=bar', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '');
}
);
});
});
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 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() {
called = true;
expect(this.data).toBe(data);
});
});
inject(function($controller, $rootScope) {
$controller('testCtrl', { scope: $rootScope }, { data: 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, callback;
beforeEach(function() {
module(function($provide) {
callback = jasmine.createSpy('callback');
realHttpBackend = jasmine.createSpy('real $httpBackend');
$provide.value('$httpBackend', realHttpBackend);
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
});
inject(function($injector) {
hb = $injector.get('$httpBackend');
});
});
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', '', '');
}));
});
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('<div class="animate"></div>');
$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('<div class="leave-me"></div>');
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('<div class="animate child"></div>');
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('<div></div>');
$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('<div></div>');
// 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("allowes me to mutate a single instace 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 {
// avoid failing testability for the additional
// injectors etc created
angular.element.cache = {};
// 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);
}
});