Commit 1bc277fd by Torkel Ödegaard

Merge branch 'develop' into pro

parents f068b2c1 5b475a05
...@@ -6,7 +6,6 @@ define([ ...@@ -6,7 +6,6 @@ define([
function ControllerTestContext() { function ControllerTestContext() {
var self = this; var self = this;
this.timeRange = { from:'now-1h', to: 'now'};
this.datasource = {}; this.datasource = {};
this.annotationsSrv = {}; this.annotationsSrv = {};
this.datasourceSrv = { this.datasourceSrv = {
...@@ -25,18 +24,7 @@ define([ ...@@ -25,18 +24,7 @@ define([
return inject(function($controller, $rootScope, $q) { return inject(function($controller, $rootScope, $q) {
self.scope = $rootScope.$new(); self.scope = $rootScope.$new();
self.scope.panel = {}; self.scope.panel = {};
self.scope.filter = { self.scope.filter = new FilterSrvStub();
timeRange: function(parse) {
if (!parse) {
return self.timeRange;
}
return {
from : kbn.parseDate(self.timeRange.from),
to : kbn.parseDate(self.timeRange.to)
};
}
};
$rootScope.colors = []; $rootScope.colors = [];
for (var i = 0; i < 50; i++) { $rootScope.colors.push('#' + i); } for (var i = 0; i < 50; i++) { $rootScope.colors.push('#' + i); }
...@@ -50,9 +38,42 @@ define([ ...@@ -50,9 +38,42 @@ define([
}; };
} }
function ServiceTestContext() {
var self = this;
this.createService = function(name) {
return inject([name, '$q', '$rootScope', '$httpBackend', function(InfluxDatasource, $q, $rootScope, $httpBackend) {
self.service = InfluxDatasource;
self.$q = $q;
self.$rootScope = $rootScope;
self.filterSrv = new FilterSrvStub();
self.$httpBackend = $httpBackend;
}]);
};
}
function FilterSrvStub() {
this.time = { from:'now-1h', to: 'now'};
this.timeRange = function(parse) {
if (!parse) {
return this.time;
}
return {
from : kbn.parseDate(this.time.from),
to : kbn.parseDate(this.time.to)
};
};
this.applyTemplateToTarget = function(target) {
return target;
};
}
return { return {
ControllerTestContext: ControllerTestContext ControllerTestContext: ControllerTestContext,
FilterSrvStub: FilterSrvStub,
ServiceTestContext: ServiceTestContext
}; };
}); });
define([
'./helpers',
'services/influxdb/influxdbDatasource'
], function(helpers) {
'use strict';
describe('InfluxDatasource', function() {
var ctx = new helpers.ServiceTestContext();
beforeEach(module('grafana.services'));
beforeEach(ctx.createService('InfluxDatasource'));
describe('When querying influxdb with one target using query editor target spec', function() {
var results;
var urlExpected = "/series?p=mupp&q=select++mean(value)+from+%22test%22"+
"+where++time+%3E+now()+-+1h+++++group+by+time()++order+asc&time_precision=s";
var query = {
range: { from: 'now-1h', to: 'now' },
targets: [{ series: 'test', column: 'value', function: 'mean' }]
};
var response = [{
columns: ["time", "sequence_nr", "value"],
name: 'test',
points: [[10, 1, 1]],
}];
beforeEach(function() {
var ds = new ctx.service({ urls: [''], user: 'test', password: 'mupp' });
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ds.query(ctx.filterSrv, query).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
it('should generate the correct query', function() {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
it('should return series list', function() {
expect(results.data.length).to.be(1);
expect(results.data[0].target).to.be('test.value');
});
});
describe('When querying influxdb with one raw query', function() {
var results;
var urlExpected = "/series?p=mupp&q=select+value+from+series"+
"+where+time+%3E+now()+-+1h+and+time+%3E+1&time_precision=s";
var query = {
range: { from: 'now-1h', to: 'now' },
targets: [{ query: "select value from series where time > 1", rawQuery: true }]
};
var response = [];
beforeEach(function() {
var ds = new ctx.service({ urls: [''], user: 'test', password: 'mupp' });
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ds.query(ctx.filterSrv, query).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
it('should generate the correct query', function() {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
});
});
});
...@@ -118,6 +118,7 @@ require([ ...@@ -118,6 +118,7 @@ require([
'specs/parser-specs', 'specs/parser-specs',
'specs/gfunc-specs', 'specs/gfunc-specs',
'specs/graphiteTargetCtrl-specs', 'specs/graphiteTargetCtrl-specs',
'specs/influxdb-datasource-specs',
'specs/graph-ctrl-specs', 'specs/graph-ctrl-specs',
'specs/filterSrv-specs', 'specs/filterSrv-specs',
'specs/kbn-format-specs', 'specs/kbn-format-specs',
......
/** /**
* @license AngularJS v1.1.5 * @license AngularJS v1.2.21
* (c) 2010-2012 Google, Inc. http://angularjs.org * (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*
* TODO(vojta): wrap whole file into closure during build
*/ */
(function(window, angular, undefined) {
'use strict';
/** /**
* @ngdoc overview * @ngdoc object
* @name angular.mock * @name angular.mock
* @description * @description
* *
...@@ -18,7 +19,7 @@ angular.mock = {}; ...@@ -18,7 +19,7 @@ angular.mock = {};
/** /**
* ! This is a private undocumented service ! * ! This is a private undocumented service !
* *
* @name ngMock.$browser * @name $browser
* *
* @description * @description
* This service is a mock implementation of {@link ng.$browser}. It provides fake * This service is a mock implementation of {@link ng.$browser}. It provides fake
...@@ -29,7 +30,7 @@ angular.mock = {}; ...@@ -29,7 +30,7 @@ angular.mock = {};
* that there are several helper methods available which can be used in tests. * that there are several helper methods available which can be used in tests.
*/ */
angular.mock.$BrowserProvider = function() { angular.mock.$BrowserProvider = function() {
this.$get = function(){ this.$get = function() {
return new angular.mock.$Browser(); return new angular.mock.$Browser();
}; };
}; };
...@@ -75,6 +76,12 @@ angular.mock.$Browser = function() { ...@@ -75,6 +76,12 @@ angular.mock.$Browser = function() {
}; };
/**
* @name $browser#defer.now
*
* @description
* Current milliseconds mock time.
*/
self.defer.now = 0; self.defer.now = 0;
...@@ -95,8 +102,7 @@ angular.mock.$Browser = function() { ...@@ -95,8 +102,7 @@ angular.mock.$Browser = function() {
/** /**
* @name ngMock.$browser#defer.flush * @name $browser#defer.flush
* @methodOf ngMock.$browser
* *
* @description * @description
* Flushes all pending requests and executes the defer callbacks. * Flushes all pending requests and executes the defer callbacks.
...@@ -110,7 +116,7 @@ angular.mock.$Browser = function() { ...@@ -110,7 +116,7 @@ angular.mock.$Browser = function() {
if (self.deferredFns.length) { if (self.deferredFns.length) {
self.defer.now = self.deferredFns[self.deferredFns.length-1].time; self.defer.now = self.deferredFns[self.deferredFns.length-1].time;
} else { } else {
throw Error('No deferred tasks to be flushed'); throw new Error('No deferred tasks to be flushed');
} }
} }
...@@ -118,13 +124,6 @@ angular.mock.$Browser = function() { ...@@ -118,13 +124,6 @@ angular.mock.$Browser = function() {
self.deferredFns.shift().fn(); self.deferredFns.shift().fn();
} }
}; };
/**
* @name ngMock.$browser#defer.now
* @propertyOf ngMock.$browser
*
* @description
* Current milliseconds mock time.
*/
self.$$baseHref = ''; self.$$baseHref = '';
self.baseHref = function() { self.baseHref = function() {
...@@ -134,8 +133,7 @@ angular.mock.$Browser = function() { ...@@ -134,8 +133,7 @@ angular.mock.$Browser = function() {
angular.mock.$Browser.prototype = { angular.mock.$Browser.prototype = {
/** /**
* @name ngMock.$browser#poll * @name $browser#poll
* @methodOf ngMock.$browser
* *
* @description * @description
* run all fns in pollFns * run all fns in pollFns
...@@ -162,7 +160,7 @@ angular.mock.$Browser.prototype = { ...@@ -162,7 +160,7 @@ angular.mock.$Browser.prototype = {
cookies: function(name, value) { cookies: function(name, value) {
if (name) { if (name) {
if (value == undefined) { if (angular.isUndefined(value)) {
delete this.cookieHash[name]; delete this.cookieHash[name];
} else { } else {
if (angular.isString(value) && //strings only if (angular.isString(value) && //strings only
...@@ -186,17 +184,17 @@ angular.mock.$Browser.prototype = { ...@@ -186,17 +184,17 @@ angular.mock.$Browser.prototype = {
/** /**
* @ngdoc object * @ngdoc provider
* @name ngMock.$exceptionHandlerProvider * @name $exceptionHandlerProvider
* *
* @description * @description
* Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors
* into the `$exceptionHandler`. * passed into the `$exceptionHandler`.
*/ */
/** /**
* @ngdoc object * @ngdoc service
* @name ngMock.$exceptionHandler * @name $exceptionHandler
* *
* @description * @description
* Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed
...@@ -204,7 +202,7 @@ angular.mock.$Browser.prototype = { ...@@ -204,7 +202,7 @@ angular.mock.$Browser.prototype = {
* information. * information.
* *
* *
* <pre> * ```js
* describe('$exceptionHandlerProvider', function() { * describe('$exceptionHandlerProvider', function() {
* *
* it('should capture log messages and exceptions', function() { * it('should capture log messages and exceptions', function() {
...@@ -225,7 +223,7 @@ angular.mock.$Browser.prototype = { ...@@ -225,7 +223,7 @@ angular.mock.$Browser.prototype = {
* }); * });
* }); * });
* }); * });
* </pre> * ```
*/ */
angular.mock.$ExceptionHandlerProvider = function() { angular.mock.$ExceptionHandlerProvider = function() {
...@@ -233,8 +231,7 @@ angular.mock.$ExceptionHandlerProvider = function() { ...@@ -233,8 +231,7 @@ angular.mock.$ExceptionHandlerProvider = function() {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$exceptionHandlerProvider#mode * @name $exceptionHandlerProvider#mode
* @methodOf ngMock.$exceptionHandlerProvider
* *
* @description * @description
* Sets the logging mode. * Sets the logging mode.
...@@ -244,9 +241,9 @@ angular.mock.$ExceptionHandlerProvider = function() { ...@@ -244,9 +241,9 @@ angular.mock.$ExceptionHandlerProvider = function() {
* - `rethrow`: If any errors are passed into the handler in tests, it typically * - `rethrow`: If any errors are passed into the handler in tests, it typically
* means that there is a bug in the application or test, so this mock will * means that there is a bug in the application or test, so this mock will
* make these tests fail. * make these tests fail.
* - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log`
* array of errors in `$exceptionHandler.errors`, to allow later assertion of them. * mode stores an array of errors in `$exceptionHandler.errors`, to allow later
* See {@link ngMock.$log#assertEmpty assertEmpty()} and * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and
* {@link ngMock.$log#reset reset()} * {@link ngMock.$log#reset reset()}
*/ */
this.mode = function(mode) { this.mode = function(mode) {
...@@ -270,7 +267,7 @@ angular.mock.$ExceptionHandlerProvider = function() { ...@@ -270,7 +267,7 @@ angular.mock.$ExceptionHandlerProvider = function() {
handler.errors = errors; handler.errors = errors;
break; break;
default: default:
throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
} }
}; };
...@@ -284,7 +281,7 @@ angular.mock.$ExceptionHandlerProvider = function() { ...@@ -284,7 +281,7 @@ angular.mock.$ExceptionHandlerProvider = function() {
/** /**
* @ngdoc service * @ngdoc service
* @name ngMock.$log * @name $log
* *
* @description * @description
* Mock implementation of {@link ng.$log} that gathers all logged messages in arrays * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays
...@@ -293,24 +290,37 @@ angular.mock.$ExceptionHandlerProvider = function() { ...@@ -293,24 +290,37 @@ angular.mock.$ExceptionHandlerProvider = function() {
* *
*/ */
angular.mock.$LogProvider = function() { angular.mock.$LogProvider = function() {
var debug = true;
function concat(array1, array2, index) { function concat(array1, array2, index) {
return array1.concat(Array.prototype.slice.call(array2, index)); return array1.concat(Array.prototype.slice.call(array2, index));
} }
this.debugEnabled = function(flag) {
if (angular.isDefined(flag)) {
debug = flag;
return this;
} else {
return debug;
}
};
this.$get = function () { this.$get = function () {
var $log = { var $log = {
log: function() { $log.log.logs.push(concat([], arguments, 0)); }, log: function() { $log.log.logs.push(concat([], arguments, 0)); },
warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, warn: function() { $log.warn.logs.push(concat([], arguments, 0)); },
info: function() { $log.info.logs.push(concat([], arguments, 0)); }, info: function() { $log.info.logs.push(concat([], arguments, 0)); },
error: function() { $log.error.logs.push(concat([], arguments, 0)); } error: function() { $log.error.logs.push(concat([], arguments, 0)); },
debug: function() {
if (debug) {
$log.debug.logs.push(concat([], arguments, 0));
}
}
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$log#reset * @name $log#reset
* @methodOf ngMock.$log
* *
* @description * @description
* Reset all of the logging arrays to empty. * Reset all of the logging arrays to empty.
...@@ -318,86 +328,97 @@ angular.mock.$LogProvider = function() { ...@@ -318,86 +328,97 @@ angular.mock.$LogProvider = function() {
$log.reset = function () { $log.reset = function () {
/** /**
* @ngdoc property * @ngdoc property
* @name ngMock.$log#log.logs * @name $log#log.logs
* @propertyOf ngMock.$log
* *
* @description * @description
* Array of messages logged using {@link ngMock.$log#log}. * Array of messages logged using {@link ngMock.$log#log}.
* *
* @example * @example
* <pre> * ```js
* $log.log('Some Log'); * $log.log('Some Log');
* var first = $log.log.logs.unshift(); * var first = $log.log.logs.unshift();
* </pre> * ```
*/ */
$log.log.logs = []; $log.log.logs = [];
/** /**
* @ngdoc property * @ngdoc property
* @name ngMock.$log#warn.logs * @name $log#info.logs
* @propertyOf ngMock.$log *
* @description
* Array of messages logged using {@link ngMock.$log#info}.
*
* @example
* ```js
* $log.info('Some Info');
* var first = $log.info.logs.unshift();
* ```
*/
$log.info.logs = [];
/**
* @ngdoc property
* @name $log#warn.logs
* *
* @description * @description
* Array of messages logged using {@link ngMock.$log#warn}. * Array of messages logged using {@link ngMock.$log#warn}.
* *
* @example * @example
* <pre> * ```js
* $log.warn('Some Warning'); * $log.warn('Some Warning');
* var first = $log.warn.logs.unshift(); * var first = $log.warn.logs.unshift();
* </pre> * ```
*/ */
$log.warn.logs = []; $log.warn.logs = [];
/** /**
* @ngdoc property * @ngdoc property
* @name ngMock.$log#info.logs * @name $log#error.logs
* @propertyOf ngMock.$log
* *
* @description * @description
* Array of messages logged using {@link ngMock.$log#info}. * Array of messages logged using {@link ngMock.$log#error}.
* *
* @example * @example
* <pre> * ```js
* $log.info('Some Info'); * $log.error('Some Error');
* var first = $log.info.logs.unshift(); * var first = $log.error.logs.unshift();
* </pre> * ```
*/ */
$log.info.logs = []; $log.error.logs = [];
/** /**
* @ngdoc property * @ngdoc property
* @name ngMock.$log#error.logs * @name $log#debug.logs
* @propertyOf ngMock.$log
* *
* @description * @description
* Array of messages logged using {@link ngMock.$log#error}. * Array of messages logged using {@link ngMock.$log#debug}.
* *
* @example * @example
* <pre> * ```js
* $log.log('Some Error'); * $log.debug('Some Error');
* var first = $log.error.logs.unshift(); * var first = $log.debug.logs.unshift();
* </pre> * ```
*/ */
$log.error.logs = []; $log.debug.logs = [];
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$log#assertEmpty * @name $log#assertEmpty
* @methodOf ngMock.$log
* *
* @description * @description
* Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. * Assert that the all of the logging methods have no logged messages. If messages present, an
* exception is thrown.
*/ */
$log.assertEmpty = function() { $log.assertEmpty = function() {
var errors = []; var errors = [];
angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) {
angular.forEach($log[logLevel].logs, function(log) { angular.forEach($log[logLevel].logs, function(log) {
angular.forEach(log, function (logItem) { angular.forEach(log, function (logItem) {
errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' +
(logItem.stack || ''));
}); });
}); });
}); });
if (errors.length) { if (errors.length) {
errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or "+
"log message was not checked and removed:"); "an expected log message was not checked and removed:");
errors.push(''); errors.push('');
throw new Error(errors.join('\n---------\n')); throw new Error(errors.join('\n---------\n'));
} }
...@@ -409,10 +430,136 @@ angular.mock.$LogProvider = function() { ...@@ -409,10 +430,136 @@ angular.mock.$LogProvider = function() {
}; };
(function() { /**
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; * @ngdoc service
* @name $interval
*
* @description
* Mock implementation of the $interval service.
*
* Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
* move forward by `millis` milliseconds and trigger any functions scheduled to run in that
* time.
*
* @param {function()} fn A function that should be called repeatedly.
* @param {number} delay Number of milliseconds between each function call.
* @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
* indefinitely.
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
* @returns {promise} A promise which will be notified on each iteration.
*/
angular.mock.$IntervalProvider = function() {
this.$get = ['$rootScope', '$q',
function($rootScope, $q) {
var repeatFns = [],
nextRepeatId = 0,
now = 0;
var $interval = function(fn, delay, count, invokeApply) {
var deferred = $q.defer(),
promise = deferred.promise,
iteration = 0,
skipApply = (angular.isDefined(invokeApply) && !invokeApply);
count = (angular.isDefined(count)) ? count : 0;
promise.then(null, null, fn);
promise.$$intervalId = nextRepeatId;
function tick() {
deferred.notify(iteration++);
if (count > 0 && iteration >= count) {
var fnIndex;
deferred.resolve(iteration);
angular.forEach(repeatFns, function(fn, index) {
if (fn.id === promise.$$intervalId) fnIndex = index;
});
if (fnIndex !== undefined) {
repeatFns.splice(fnIndex, 1);
}
}
if (!skipApply) $rootScope.$apply();
}
repeatFns.push({
nextTime:(now + delay),
delay: delay,
fn: tick,
id: nextRepeatId,
deferred: deferred
});
repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
nextRepeatId++;
return promise;
};
/**
* @ngdoc method
* @name $interval#cancel
*
* @description
* Cancels a task associated with the `promise`.
*
* @param {promise} promise A promise from calling the `$interval` function.
* @returns {boolean} Returns `true` if the task was successfully cancelled.
*/
$interval.cancel = function(promise) {
if(!promise) return false;
var fnIndex;
angular.forEach(repeatFns, function(fn, index) {
if (fn.id === promise.$$intervalId) fnIndex = index;
});
if (fnIndex !== undefined) {
repeatFns[fnIndex].deferred.reject('canceled');
repeatFns.splice(fnIndex, 1);
return true;
}
return false;
};
/**
* @ngdoc method
* @name $interval#flush
* @description
*
* Runs interval tasks scheduled to be run in the next `millis` milliseconds.
*
* @param {number=} millis maximum timeout amount to flush up until.
*
* @return {number} The amount of time moved forward.
*/
$interval.flush = function(millis) {
now += millis;
while (repeatFns.length && repeatFns[0].nextTime <= now) {
var task = repeatFns[0];
task.fn();
task.nextTime += task.delay;
repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
}
return millis;
};
return $interval;
}];
};
/* jshint -W101 */
/* The R_ISO8061_STR regex is never going to fit into the 100 char limit!
* This directive should go inside the anonymous function but a bug in JSHint means that it would
* not be enacted early enough to prevent the warning.
*/
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
function jsonStringToDate(string){ function jsonStringToDate(string) {
var match; var match;
if (match = string.match(R_ISO8061_STR)) { if (match = string.match(R_ISO8061_STR)) {
var date = new Date(0), var date = new Date(0),
...@@ -423,17 +570,20 @@ angular.mock.$LogProvider = function() { ...@@ -423,17 +570,20 @@ angular.mock.$LogProvider = function() {
tzMin = int(match[9] + match[11]); tzMin = int(match[9] + match[11]);
} }
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); date.setUTCHours(int(match[4]||0) - tzHour,
int(match[5]||0) - tzMin,
int(match[6]||0),
int(match[7]||0));
return date; return date;
} }
return string; return string;
} }
function int(str) { function int(str) {
return parseInt(str, 10); return parseInt(str, 10);
} }
function padNumber(num, digits, trim) { function padNumber(num, digits, trim) {
var neg = ''; var neg = '';
if (num < 0) { if (num < 0) {
neg = '-'; neg = '-';
...@@ -444,11 +594,11 @@ angular.mock.$LogProvider = function() { ...@@ -444,11 +594,11 @@ angular.mock.$LogProvider = function() {
if (trim) if (trim)
num = num.substr(num.length - digits); num = num.substr(num.length - digits);
return neg + num; return neg + num;
} }
/** /**
* @ngdoc object * @ngdoc type
* @name angular.mock.TzDate * @name angular.mock.TzDate
* @description * @description
* *
...@@ -472,7 +622,7 @@ angular.mock.$LogProvider = function() { ...@@ -472,7 +622,7 @@ angular.mock.$LogProvider = function() {
* incomplete we might be missing some non-standard methods. This can result in errors like: * incomplete we might be missing some non-standard methods. This can result in errors like:
* "Date.prototype.foo called on incompatible Object". * "Date.prototype.foo called on incompatible Object".
* *
* <pre> * ```js
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
* newYearInBratislava.getTimezoneOffset() => -60; * newYearInBratislava.getTimezoneOffset() => -60;
* newYearInBratislava.getFullYear() => 2010; * newYearInBratislava.getFullYear() => 2010;
...@@ -481,10 +631,10 @@ angular.mock.$LogProvider = function() { ...@@ -481,10 +631,10 @@ angular.mock.$LogProvider = function() {
* newYearInBratislava.getHours() => 0; * newYearInBratislava.getHours() => 0;
* newYearInBratislava.getMinutes() => 0; * newYearInBratislava.getMinutes() => 0;
* newYearInBratislava.getSeconds() => 0; * newYearInBratislava.getSeconds() => 0;
* </pre> * ```
* *
*/ */
angular.mock.TzDate = function (offset, timestamp) { angular.mock.TzDate = function (offset, timestamp) {
var self = new Date(0); var self = new Date(0);
if (angular.isString(timestamp)) { if (angular.isString(timestamp)) {
var tsStr = timestamp; var tsStr = timestamp;
...@@ -586,8 +736,8 @@ angular.mock.$LogProvider = function() { ...@@ -586,8 +736,8 @@ angular.mock.$LogProvider = function() {
padNumber(self.origDate.getUTCHours(), 2) + ':' + padNumber(self.origDate.getUTCHours(), 2) + ':' +
padNumber(self.origDate.getUTCMinutes(), 2) + ':' + padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
padNumber(self.origDate.getUTCSeconds(), 2) + '.' + padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z';
} };
} }
//hide all methods not implemented in this mock that the Date prototype exposes //hide all methods not implemented in this mock that the Date prototype exposes
...@@ -600,70 +750,62 @@ angular.mock.$LogProvider = function() { ...@@ -600,70 +750,62 @@ angular.mock.$LogProvider = function() {
angular.forEach(unimplementedMethods, function(methodName) { angular.forEach(unimplementedMethods, function(methodName) {
self[methodName] = function() { self[methodName] = function() {
throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
}; };
}); });
return self; return self;
}; };
//make "tzDateInstance instanceof Date" return true //make "tzDateInstance instanceof Date" return true
angular.mock.TzDate.prototype = Date.prototype; angular.mock.TzDate.prototype = Date.prototype;
})(); /* jshint +W101 */
/** angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
* @ngdoc function
* @name angular.mock.createMockWindow .config(['$provide', function($provide) {
* @description
*
* This function creates a mock window object useful for controlling access ot setTimeout, but mocking out
* sufficient window's properties to allow Angular to execute.
*
* @example
*
* <pre>
beforeEach(module(function($provide) {
$provide.value('$window', window = angular.mock.createMockWindow());
}));
it('should do something', inject(function($window) { var reflowQueue = [];
var val = null; $provide.value('$$animateReflow', function(fn) {
$window.setTimeout(function() { val = 123; }, 10); var index = reflowQueue.length;
expect(val).toEqual(null); reflowQueue.push(fn);
window.setTimeout.expect(10).process(); return function cancel() {
expect(val).toEqual(123); reflowQueue.splice(index, 1);
};
}); });
* </pre>
* $provide.decorator('$animate', function($delegate, $$asyncCallback) {
*/ var animate = {
angular.mock.createMockWindow = function() { queue : [],
var mockWindow = {}; enabled : $delegate.enabled,
var setTimeoutQueue = []; triggerCallbacks : function() {
$$asyncCallback.flush();
mockWindow.document = window.document; },
mockWindow.getComputedStyle = angular.bind(window, window.getComputedStyle); triggerReflow : function() {
mockWindow.scrollTo = angular.bind(window, window.scrollTo); angular.forEach(reflowQueue, function(fn) {
mockWindow.navigator = window.navigator; fn();
mockWindow.setTimeout = function(fn, delay) { });
setTimeoutQueue.push({fn: fn, delay: delay}); reflowQueue = [];
};
mockWindow.setTimeout.queue = setTimeoutQueue;
mockWindow.setTimeout.expect = function(delay) {
if (setTimeoutQueue.length > 0) {
return {
process: function() {
var tick = setTimeoutQueue.shift();
expect(tick.delay).toEqual(delay);
tick.fn();
} }
}; };
} else {
expect('SetTimoutQueue empty. Expecting delay of ').toEqual(delay); angular.forEach(
} ['enter','leave','move','addClass','removeClass','setClass'], function(method) {
animate[method] = function() {
animate.queue.push({
event : method,
element : arguments[0],
args : arguments
});
$delegate[method].apply($delegate, arguments);
}; };
});
return animate;
});
}]);
return mockWindow;
};
/** /**
* @ngdoc function * @ngdoc function
...@@ -672,9 +814,11 @@ angular.mock.createMockWindow = function() { ...@@ -672,9 +814,11 @@ angular.mock.createMockWindow = function() {
* *
* *NOTE*: this is not an injectable instance, just a globally available function. * *NOTE*: this is not an injectable instance, just a globally available function.
* *
* Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for
* debugging.
* *
* This method is also available on window, where it can be used to display objects on debug console. * This method is also available on window, where it can be used to display objects on debug
* console.
* *
* @param {*} object - any object to turn into string. * @param {*} object - any object to turn into string.
* @return {string} a serialized string of the argument * @return {string} a serialized string of the argument
...@@ -704,6 +848,8 @@ angular.mock.dump = function(object) { ...@@ -704,6 +848,8 @@ angular.mock.dump = function(object) {
} else if (object instanceof Error) { } else if (object instanceof Error) {
out = object.stack || ('' + object.name + ': ' + object.message); out = object.stack || ('' + object.name + ': ' + object.message);
} else { } else {
// TODO(i): this prevents methods being logged,
// we should have a better way to serialize objects
out = angular.toJson(object, true); out = angular.toJson(object, true);
} }
} else { } else {
...@@ -717,7 +863,7 @@ angular.mock.dump = function(object) { ...@@ -717,7 +863,7 @@ angular.mock.dump = function(object) {
offset = offset || ' '; offset = offset || ' ';
var log = [offset + 'Scope(' + scope.$id + '): {']; var log = [offset + 'Scope(' + scope.$id + '): {'];
for ( var key in scope ) { for ( var key in scope ) {
if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) {
log.push(' ' + key + ': ' + angular.toJson(scope[key])); log.push(' ' + key + ': ' + angular.toJson(scope[key]));
} }
} }
...@@ -732,8 +878,8 @@ angular.mock.dump = function(object) { ...@@ -732,8 +878,8 @@ angular.mock.dump = function(object) {
}; };
/** /**
* @ngdoc object * @ngdoc service
* @name ngMock.$httpBackend * @name $httpBackend
* @description * @description
* Fake HTTP backend implementation suitable for unit testing applications that use the * Fake HTTP backend implementation suitable for unit testing applications that use the
* {@link ng.$http $http service}. * {@link ng.$http $http service}.
...@@ -742,8 +888,8 @@ angular.mock.dump = function(object) { ...@@ -742,8 +888,8 @@ angular.mock.dump = function(object) {
* development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
* *
* During unit testing, we want our unit tests to run quickly and have no external dependencies so * During unit testing, we want our unit tests to run quickly and have no external dependencies so
* we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or
* {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is
* to verify whether a certain request has been sent or not, or alternatively just let the * to verify whether a certain request has been sent or not, or alternatively just let the
* application make requests, respond with pre-trained responses and assert that the end result is * application make requests, respond with pre-trained responses and assert that the end result is
* what we expect it to be. * what we expect it to be.
...@@ -754,7 +900,7 @@ angular.mock.dump = function(object) { ...@@ -754,7 +900,7 @@ angular.mock.dump = function(object) {
* When an Angular application needs some data from a server, it calls the $http service, which * When an Angular application needs some data from a server, it calls the $http service, which
* sends the request to a real server using $httpBackend service. With dependency injection, it is * sends the request to a real server using $httpBackend service. With dependency injection, it is
* easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
* the requests and respond with some testing data without sending a request to real server. * the requests and respond with some testing data without sending a request to a real server.
* *
* There are two ways to specify what test data should be returned as http responses by the mock * There are two ways to specify what test data should be returned as http responses by the mock
* backend when the code under test makes http requests: * backend when the code under test makes http requests:
...@@ -821,42 +967,62 @@ angular.mock.dump = function(object) { ...@@ -821,42 +967,62 @@ angular.mock.dump = function(object) {
* *
* # Flushing HTTP requests * # Flushing HTTP requests
* *
* The $httpBackend used in production, always responds to requests with responses asynchronously. * The $httpBackend used in production always responds to requests asynchronously. If we preserved
* If we preserved this behavior in unit testing, we'd have to create async unit tests, which are * this behavior in unit testing, we'd have to create async unit tests, which are hard to write,
* hard to write, follow and maintain. At the same time the testing mock, can't respond * to follow and to maintain. But neither can the testing mock respond synchronously; that would
* synchronously because that would change the execution of the code under test. For this reason the * change the execution of the code under test. For this reason, the mock $httpBackend has a
* mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending * `flush()` method, which allows the test to explicitly flush pending requests. This preserves
* requests and thus preserving the async api of the backend, while allowing the test to execute * the async api of the backend, while allowing the test to execute synchronously.
* synchronously.
* *
* *
* # Unit testing with mock $httpBackend * # Unit testing with mock $httpBackend
* The following code shows how to setup and use the mock backend when unit testing a controller.
* First we create the controller under test:
* *
* <pre> ```js
// controller // The controller code
function MyController($scope, $http) { function MyController($scope, $http) {
$http.get('/auth.py').success(function(data) { var authToken;
$http.get('/auth.py').success(function(data, status, headers) {
authToken = headers('A-Token');
$scope.user = data; $scope.user = data;
}); });
this.saveMessage = function(message) { $scope.saveMessage = function(message) {
var headers = { 'Authorization': authToken };
$scope.status = 'Saving...'; $scope.status = 'Saving...';
$http.post('/add-msg.py', message).success(function(response) {
$http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
$scope.status = ''; $scope.status = '';
}).error(function() { }).error(function() {
$scope.status = 'ERROR!'; $scope.status = 'ERROR!';
}); });
}; };
} }
```
*
* Now we setup the mock backend and create the test specs:
*
```js
// testing controller // testing controller
var $httpBackend; describe('MyController', function() {
var $httpBackend, $rootScope, createController;
beforeEach(inject(function($injector) { beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend'); $httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests // backend definition common for all tests
$httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'}); $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
// Get hold of a scope (i.e. the root scope)
$rootScope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
return $controller('MyController', {'$scope' : $rootScope });
};
})); }));
...@@ -868,39 +1034,43 @@ angular.mock.dump = function(object) { ...@@ -868,39 +1034,43 @@ angular.mock.dump = function(object) {
it('should fetch authentication token', function() { it('should fetch authentication token', function() {
$httpBackend.expectGET('/auth.py'); $httpBackend.expectGET('/auth.py');
var controller = scope.$new(MyController); var controller = createController();
$httpBackend.flush(); $httpBackend.flush();
}); });
it('should send msg to server', function() { it('should send msg to server', function() {
var controller = createController();
$httpBackend.flush();
// now you don’t care about the authentication, but // now you don’t care about the authentication, but
// the controller will still send the request and // the controller will still send the request and
// $httpBackend will respond without you having to // $httpBackend will respond without you having to
// specify the expectation and response for this request // specify the expectation and response for this request
$httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
var controller = scope.$new(MyController); $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
$httpBackend.flush(); $rootScope.saveMessage('message content');
controller.saveMessage('message content'); expect($rootScope.status).toBe('Saving...');
expect(controller.status).toBe('Saving...');
$httpBackend.flush(); $httpBackend.flush();
expect(controller.status).toBe(''); expect($rootScope.status).toBe('');
}); });
it('should send auth header', function() { it('should send auth header', function() {
var controller = createController();
$httpBackend.flush();
$httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
// check if the header was send, if it wasn't the expectation won't // check if the header was send, if it wasn't the expectation won't
// match the request and the test will fail // match the request and the test will fail
return headers['Authorization'] == 'xxx'; return headers['Authorization'] == 'xxx';
}).respond(201, ''); }).respond(201, '');
var controller = scope.$new(MyController); $rootScope.saveMessage('whatever');
controller.saveMessage('whatever');
$httpBackend.flush(); $httpBackend.flush();
}); });
</pre> });
```
*/ */
angular.mock.$HttpBackendProvider = function() { angular.mock.$HttpBackendProvider = function() {
this.$get = ['$rootScope', createHttpBackendMock]; this.$get = ['$rootScope', createHttpBackendMock];
...@@ -924,20 +1094,21 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -924,20 +1094,21 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
var definitions = [], var definitions = [],
expectations = [], expectations = [],
responses = [], responses = [],
responsesPush = angular.bind(responses, responses.push); responsesPush = angular.bind(responses, responses.push),
copy = angular.copy;
function createResponse(status, data, headers) { function createResponse(status, data, headers, statusText) {
if (angular.isFunction(status)) return status; if (angular.isFunction(status)) return status;
return function() { return function() {
return angular.isNumber(status) return angular.isNumber(status)
? [status, data, headers] ? [status, data, headers, statusText]
: [200, status, data]; : [200, status, data];
}; };
} }
// TODO(vojta): change params to: method, url, data, headers, callback // TODO(vojta): change params to: method, url, data, headers, callback
function $httpBackend(method, url, data, callback, headers, timeout) { function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) {
var xhr = new MockXhr(), var xhr = new MockXhr(),
expectation = expectations[0], expectation = expectations[0],
wasExpected = false; wasExpected = false;
...@@ -956,7 +1127,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -956,7 +1127,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
function handleResponse() { function handleResponse() {
var response = wrapped.response(method, url, data, headers); var response = wrapped.response(method, url, data, headers);
xhr.$$respHeaders = response[2]; xhr.$$respHeaders = response[2];
callback(response[0], response[1], xhr.getAllResponseHeaders()); callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
copy(response[3] || ''));
} }
function handleTimeout() { function handleTimeout() {
...@@ -972,11 +1144,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -972,11 +1144,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
if (expectation && expectation.match(method, url)) { if (expectation && expectation.match(method, url)) {
if (!expectation.matchData(data)) if (!expectation.matchData(data))
throw Error('Expected ' + expectation + ' with different data\n' + throw new Error('Expected ' + expectation + ' with different data\n' +
'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
if (!expectation.matchHeaders(headers)) if (!expectation.matchHeaders(headers))
throw Error('Expected ' + expectation + ' with different headers\n' + throw new Error('Expected ' + expectation + ' with different headers\n' +
'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
prettyPrint(headers)); prettyPrint(headers));
...@@ -996,42 +1168,44 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -996,42 +1168,44 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
// if $browser specified, we do auto flush all requests // if $browser specified, we do auto flush all requests
($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); ($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
} else if (definition.passThrough) { } else if (definition.passThrough) {
$delegate(method, url, data, callback, headers, timeout); $delegate(method, url, data, callback, headers, timeout, withCredentials);
} else throw Error('No response defined !'); } else throw new Error('No response defined !');
return; return;
} }
} }
throw wasExpected ? throw wasExpected ?
Error('No response defined !') : new Error('No response defined !') :
Error('Unexpected request: ' + method + ' ' + url + '\n' + new Error('Unexpected request: ' + method + ' ' + url + '\n' +
(expectation ? 'Expected ' + expectation : 'No more request expected')); (expectation ? 'Expected ' + expectation : 'No more request expected'));
} }
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#when * @name $httpBackend#when
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new backend definition. * Creates a new backend definition.
* *
* @param {string} method HTTP method. * @param {string} method HTTP method.
* @param {string|RegExp} url HTTP url. * @param {string|RegExp} url HTTP url.
* @param {(string|RegExp)=} data HTTP request body. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
* object and returns true if the headers match the current definition. * object and returns true if the headers match the current definition.
* @returns {requestHandler} Returns an object with `respond` method that control how a matched * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. * request is handled.
* *
* - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` * - respond –
* – The respond method takes a set of static data to be returned or a function that can return * `{function([status,] data[, headers, statusText])
* an array containing response status (number), response data (string) and response headers * | function(function(method, url, data, headers)}`
* (Object). * – The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
* headers (Object), and the text for the status (string).
*/ */
$httpBackend.when = function(method, url, data, headers) { $httpBackend.when = function(method, url, data, headers) {
var definition = new MockHttpExpectation(method, url, data, headers), var definition = new MockHttpExpectation(method, url, data, headers),
chain = { chain = {
respond: function(status, data, headers) { respond: function(status, data, headers, statusText) {
definition.response = createResponse(status, data, headers); definition.response = createResponse(status, data, headers, statusText);
} }
}; };
...@@ -1047,8 +1221,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1047,8 +1221,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#whenGET * @name $httpBackend#whenGET
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new backend definition for GET requests. For more info see `when()`. * Creates a new backend definition for GET requests. For more info see `when()`.
* *
...@@ -1060,8 +1233,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1060,8 +1233,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#whenHEAD * @name $httpBackend#whenHEAD
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new backend definition for HEAD requests. For more info see `when()`. * Creates a new backend definition for HEAD requests. For more info see `when()`.
* *
...@@ -1073,8 +1245,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1073,8 +1245,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#whenDELETE * @name $httpBackend#whenDELETE
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new backend definition for DELETE requests. For more info see `when()`. * Creates a new backend definition for DELETE requests. For more info see `when()`.
* *
...@@ -1086,13 +1257,13 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1086,13 +1257,13 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#whenPOST * @name $httpBackend#whenPOST
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new backend definition for POST requests. For more info see `when()`. * Creates a new backend definition for POST requests. For more info see `when()`.
* *
* @param {string|RegExp} url HTTP url. * @param {string|RegExp} url HTTP url.
* @param {(string|RegExp)=} data HTTP request body. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers. * @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that control how a matched * @returns {requestHandler} Returns an object with `respond` method that control how a matched
* request is handled. * request is handled.
...@@ -1100,13 +1271,13 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1100,13 +1271,13 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#whenPUT * @name $httpBackend#whenPUT
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new backend definition for PUT requests. For more info see `when()`. * Creates a new backend definition for PUT requests. For more info see `when()`.
* *
* @param {string|RegExp} url HTTP url. * @param {string|RegExp} url HTTP url.
* @param {(string|RegExp)=} data HTTP request body. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers. * @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that control how a matched * @returns {requestHandler} Returns an object with `respond` method that control how a matched
* request is handled. * request is handled.
...@@ -1114,8 +1285,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1114,8 +1285,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#whenJSONP * @name $httpBackend#whenJSONP
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new backend definition for JSONP requests. For more info see `when()`. * Creates a new backend definition for JSONP requests. For more info see `when()`.
* *
...@@ -1128,30 +1298,33 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1128,30 +1298,33 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#expect * @name $httpBackend#expect
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new request expectation. * Creates a new request expectation.
* *
* @param {string} method HTTP method. * @param {string} method HTTP method.
* @param {string|RegExp} url HTTP url. * @param {string|RegExp} url HTTP url.
* @param {(string|RegExp)=} data HTTP request body. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
* object and returns true if the headers match the current expectation. * object and returns true if the headers match the current expectation.
* @returns {requestHandler} Returns an object with `respond` method that control how a matched * @returns {requestHandler} Returns an object with `respond` method that control how a matched
* request is handled. * request is handled.
* *
* - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` * - respond –
* – The respond method takes a set of static data to be returned or a function that can return * `{function([status,] data[, headers, statusText])
* an array containing response status (number), response data (string) and response headers * | function(function(method, url, data, headers)}`
* (Object). * – The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
* headers (Object), and the text for the status (string).
*/ */
$httpBackend.expect = function(method, url, data, headers) { $httpBackend.expect = function(method, url, data, headers) {
var expectation = new MockHttpExpectation(method, url, data, headers); var expectation = new MockHttpExpectation(method, url, data, headers);
expectations.push(expectation); expectations.push(expectation);
return { return {
respond: function(status, data, headers) { respond: function (status, data, headers, statusText) {
expectation.response = createResponse(status, data, headers); expectation.response = createResponse(status, data, headers, statusText);
} }
}; };
}; };
...@@ -1159,8 +1332,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1159,8 +1332,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#expectGET * @name $httpBackend#expectGET
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new request expectation for GET requests. For more info see `expect()`. * Creates a new request expectation for GET requests. For more info see `expect()`.
* *
...@@ -1172,8 +1344,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1172,8 +1344,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#expectHEAD * @name $httpBackend#expectHEAD
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new request expectation for HEAD requests. For more info see `expect()`. * Creates a new request expectation for HEAD requests. For more info see `expect()`.
* *
...@@ -1185,8 +1356,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1185,8 +1356,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#expectDELETE * @name $httpBackend#expectDELETE
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new request expectation for DELETE requests. For more info see `expect()`. * Creates a new request expectation for DELETE requests. For more info see `expect()`.
* *
...@@ -1198,13 +1368,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1198,13 +1368,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#expectPOST * @name $httpBackend#expectPOST
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new request expectation for POST requests. For more info see `expect()`. * Creates a new request expectation for POST requests. For more info see `expect()`.
* *
* @param {string|RegExp} url HTTP url. * @param {string|RegExp} url HTTP url.
* @param {(string|RegExp)=} data HTTP request body. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers. * @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that control how a matched * @returns {requestHandler} Returns an object with `respond` method that control how a matched
* request is handled. * request is handled.
...@@ -1212,13 +1383,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1212,13 +1383,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#expectPUT * @name $httpBackend#expectPUT
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new request expectation for PUT requests. For more info see `expect()`. * Creates a new request expectation for PUT requests. For more info see `expect()`.
* *
* @param {string|RegExp} url HTTP url. * @param {string|RegExp} url HTTP url.
* @param {(string|RegExp)=} data HTTP request body. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers. * @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that control how a matched * @returns {requestHandler} Returns an object with `respond` method that control how a matched
* request is handled. * request is handled.
...@@ -1226,13 +1398,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1226,13 +1398,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#expectPATCH * @name $httpBackend#expectPATCH
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new request expectation for PATCH requests. For more info see `expect()`. * Creates a new request expectation for PATCH requests. For more info see `expect()`.
* *
* @param {string|RegExp} url HTTP url. * @param {string|RegExp} url HTTP url.
* @param {(string|RegExp)=} data HTTP request body. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers. * @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that control how a matched * @returns {requestHandler} Returns an object with `respond` method that control how a matched
* request is handled. * request is handled.
...@@ -1240,8 +1413,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1240,8 +1413,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#expectJSONP * @name $httpBackend#expectJSONP
* @methodOf ngMock.$httpBackend
* @description * @description
* Creates a new request expectation for JSONP requests. For more info see `expect()`. * Creates a new request expectation for JSONP requests. For more info see `expect()`.
* *
...@@ -1254,8 +1426,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1254,8 +1426,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#flush * @name $httpBackend#flush
* @methodOf ngMock.$httpBackend
* @description * @description
* Flushes all pending requests using the trained responses. * Flushes all pending requests using the trained responses.
* *
...@@ -1265,11 +1436,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1265,11 +1436,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
*/ */
$httpBackend.flush = function(count) { $httpBackend.flush = function(count) {
$rootScope.$digest(); $rootScope.$digest();
if (!responses.length) throw Error('No pending request to flush !'); if (!responses.length) throw new Error('No pending request to flush !');
if (angular.isDefined(count)) { if (angular.isDefined(count)) {
while (count--) { while (count--) {
if (!responses.length) throw Error('No more pending request to flush !'); if (!responses.length) throw new Error('No more pending request to flush !');
responses.shift()(); responses.shift()();
} }
} else { } else {
...@@ -1283,8 +1454,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1283,8 +1454,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#verifyNoOutstandingExpectation * @name $httpBackend#verifyNoOutstandingExpectation
* @methodOf ngMock.$httpBackend
* @description * @description
* Verifies that all of the requests defined via the `expect` api were made. If any of the * Verifies that all of the requests defined via the `expect` api were made. If any of the
* requests were not made, verifyNoOutstandingExpectation throws an exception. * requests were not made, verifyNoOutstandingExpectation throws an exception.
...@@ -1292,43 +1462,41 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1292,43 +1462,41 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* Typically, you would call this method following each test case that asserts requests using an * Typically, you would call this method following each test case that asserts requests using an
* "afterEach" clause. * "afterEach" clause.
* *
* <pre> * ```js
* afterEach($httpBackend.verifyExpectations); * afterEach($httpBackend.verifyNoOutstandingExpectation);
* </pre> * ```
*/ */
$httpBackend.verifyNoOutstandingExpectation = function() { $httpBackend.verifyNoOutstandingExpectation = function() {
$rootScope.$digest(); $rootScope.$digest();
if (expectations.length) { if (expectations.length) {
throw Error('Unsatisfied requests: ' + expectations.join(', ')); throw new Error('Unsatisfied requests: ' + expectations.join(', '));
} }
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#verifyNoOutstandingRequest * @name $httpBackend#verifyNoOutstandingRequest
* @methodOf ngMock.$httpBackend
* @description * @description
* Verifies that there are no outstanding requests that need to be flushed. * Verifies that there are no outstanding requests that need to be flushed.
* *
* Typically, you would call this method following each test case that asserts requests using an * Typically, you would call this method following each test case that asserts requests using an
* "afterEach" clause. * "afterEach" clause.
* *
* <pre> * ```js
* afterEach($httpBackend.verifyNoOutstandingRequest); * afterEach($httpBackend.verifyNoOutstandingRequest);
* </pre> * ```
*/ */
$httpBackend.verifyNoOutstandingRequest = function() { $httpBackend.verifyNoOutstandingRequest = function() {
if (responses.length) { if (responses.length) {
throw Error('Unflushed requests: ' + responses.length); throw new Error('Unflushed requests: ' + responses.length);
} }
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$httpBackend#resetExpectations * @name $httpBackend#resetExpectations
* @methodOf ngMock.$httpBackend
* @description * @description
* Resets all request expectations, but preserves all backend definitions. Typically, you would * Resets all request expectations, but preserves all backend definitions. Typically, you would
* call resetExpectations during a multiple-phase test when you want to reuse the same instance of * call resetExpectations during a multiple-phase test when you want to reuse the same instance of
...@@ -1345,14 +1513,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { ...@@ -1345,14 +1513,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
function createShortMethods(prefix) { function createShortMethods(prefix) {
angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) {
$httpBackend[prefix + method] = function(url, headers) { $httpBackend[prefix + method] = function(url, headers) {
return $httpBackend[prefix](method, url, undefined, headers) return $httpBackend[prefix](method, url, undefined, headers);
} };
}); });
angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { angular.forEach(['PUT', 'POST', 'PATCH'], function(method) {
$httpBackend[prefix + method] = function(url, data, headers) { $httpBackend[prefix + method] = function(url, data, headers) {
return $httpBackend[prefix](method, url, data, headers) return $httpBackend[prefix](method, url, data, headers);
} };
}); });
} }
} }
...@@ -1385,7 +1553,8 @@ function MockHttpExpectation(method, url, data, headers) { ...@@ -1385,7 +1553,8 @@ function MockHttpExpectation(method, url, data, headers) {
this.matchData = function(d) { this.matchData = function(d) {
if (angular.isUndefined(data)) return true; if (angular.isUndefined(data)) return true;
if (data && angular.isFunction(data.test)) return data.test(d); if (data && angular.isFunction(data.test)) return data.test(d);
if (data && !angular.isString(data)) return angular.toJson(data) == d; if (data && angular.isFunction(data)) return data(d);
if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d));
return data == d; return data == d;
}; };
...@@ -1394,6 +1563,10 @@ function MockHttpExpectation(method, url, data, headers) { ...@@ -1394,6 +1563,10 @@ function MockHttpExpectation(method, url, data, headers) {
}; };
} }
function createMockXhr() {
return new MockXhr();
}
function MockXhr() { function MockXhr() {
// hack for testing $http, $httpBackend // hack for testing $http, $httpBackend
...@@ -1416,7 +1589,8 @@ function MockXhr() { ...@@ -1416,7 +1589,8 @@ function MockXhr() {
}; };
this.getResponseHeader = function(name) { this.getResponseHeader = function(name) {
// the lookup must be case insensitive, that's why we try two quick lookups and full scan at last // the lookup must be case insensitive,
// that's why we try two quick lookups first and full scan last
var header = this.$$respHeaders[name]; var header = this.$$respHeaders[name];
if (header) return header; if (header) return header;
...@@ -1445,8 +1619,8 @@ function MockXhr() { ...@@ -1445,8 +1619,8 @@ function MockXhr() {
/** /**
* @ngdoc function * @ngdoc service
* @name ngMock.$timeout * @name $timeout
* @description * @description
* *
* This service is just a simple decorator for {@link ng.$timeout $timeout} service * This service is just a simple decorator for {@link ng.$timeout $timeout} service
...@@ -1457,27 +1631,27 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) { ...@@ -1457,27 +1631,27 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$timeout#flush * @name $timeout#flush
* @methodOf ngMock.$timeout
* @description * @description
* *
* Flushes the queue of pending tasks. * Flushes the queue of pending tasks.
*
* @param {number=} delay maximum timeout amount to flush up until
*/ */
$delegate.flush = function() { $delegate.flush = function(delay) {
$browser.defer.flush(); $browser.defer.flush(delay);
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name ngMock.$timeout#verifyNoPendingTasks * @name $timeout#verifyNoPendingTasks
* @methodOf ngMock.$timeout
* @description * @description
* *
* Verifies that there are no pending tasks that need to be flushed. * Verifies that there are no pending tasks that need to be flushed.
*/ */
$delegate.verifyNoPendingTasks = function() { $delegate.verifyNoPendingTasks = function() {
if ($browser.deferredFns.length) { if ($browser.deferredFns.length) {
throw Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
formatPendingTasksAsString($browser.deferredFns)); formatPendingTasksAsString($browser.deferredFns));
} }
}; };
...@@ -1494,49 +1668,105 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) { ...@@ -1494,49 +1668,105 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) {
return $delegate; return $delegate;
}; };
angular.mock.$RAFDecorator = function($delegate) {
var queue = [];
var rafFn = function(fn) {
var index = queue.length;
queue.push(fn);
return function() {
queue.splice(index, 1);
};
};
rafFn.supported = $delegate.supported;
rafFn.flush = function() {
if(queue.length === 0) {
throw new Error('No rAF callbacks present');
}
var length = queue.length;
for(var i=0;i<length;i++) {
queue[i]();
}
queue = [];
};
return rafFn;
};
angular.mock.$AsyncCallbackDecorator = function($delegate) {
var callbacks = [];
var addFn = function(fn) {
callbacks.push(fn);
};
addFn.flush = function() {
angular.forEach(callbacks, function(fn) {
fn();
});
callbacks = [];
};
return addFn;
};
/** /**
* *
*/ */
angular.mock.$RootElementProvider = function() { angular.mock.$RootElementProvider = function() {
this.$get = function() { this.$get = function() {
return angular.element('<div ng-app></div>'); return angular.element('<div ng-app></div>');
} };
}; };
/** /**
* @ngdoc overview * @ngdoc module
* @name ngMock * @name ngMock
* @packageName angular-mocks
* @description * @description
* *
* The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful * # ngMock
* mocks to the {@link AUTO.$injector $injector}. *
* The `ngMock` module provides support to inject and mock Angular services into unit tests.
* In addition, ngMock also extends various core ng services such that they can be
* inspected and controlled in a synchronous manner within test code.
*
*
* <div doc-module-components="ngMock"></div>
*
*/ */
angular.module('ngMock', ['ng']).provider({ angular.module('ngMock', ['ng']).provider({
$browser: angular.mock.$BrowserProvider, $browser: angular.mock.$BrowserProvider,
$exceptionHandler: angular.mock.$ExceptionHandlerProvider, $exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider, $log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$httpBackend: angular.mock.$HttpBackendProvider, $httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider $rootElement: angular.mock.$RootElementProvider
}).config(function($provide) { }).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator); $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
}); $provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
}]);
/** /**
* @ngdoc overview * @ngdoc module
* @name ngMockE2E * @name ngMockE2E
* @module ngMockE2E
* @packageName angular-mocks
* @description * @description
* *
* The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing.
* Currently there is only one mock present in this module - * Currently there is only one mock present in this module -
* the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
*/ */
angular.module('ngMockE2E', ['ng']).config(function($provide) { angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
}); }]);
/** /**
* @ngdoc object * @ngdoc service
* @name ngMockE2E.$httpBackend * @name $httpBackend
* @module ngMockE2E
* @description * @description
* Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of
* applications that use the {@link ng.$http $http service}. * applications that use the {@link ng.$http $http service}.
...@@ -1562,7 +1792,7 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1562,7 +1792,7 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
* To setup the application to run with this http backend, you have to create a module that depends * To setup the application to run with this http backend, you have to create a module that depends
* on the `ngMockE2E` and your application modules and defines the fake backend: * on the `ngMockE2E` and your application modules and defines the fake backend:
* *
* <pre> * ```js
* myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
* myAppDev.run(function($httpBackend) { * myAppDev.run(function($httpBackend) {
* phones = [{name: 'phone1'}, {name: 'phone2'}]; * phones = [{name: 'phone1'}, {name: 'phone2'}];
...@@ -1572,20 +1802,22 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1572,20 +1802,22 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
* *
* // adds a new phone to the phones array * // adds a new phone to the phones array
* $httpBackend.whenPOST('/phones').respond(function(method, url, data) { * $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
* phones.push(angular.fromJSON(data)); * var phone = angular.fromJson(data);
* phones.push(phone);
* return [200, phone, {}];
* }); * });
* $httpBackend.whenGET(/^\/templates\//).passThrough(); * $httpBackend.whenGET(/^\/templates\//).passThrough();
* //... * //...
* }); * });
* </pre> * ```
* *
* Afterwards, bootstrap your app with this new module. * Afterwards, bootstrap your app with this new module.
*/ */
/** /**
* @ngdoc method * @ngdoc method
* @name ngMockE2E.$httpBackend#when * @name $httpBackend#when
* @methodOf ngMockE2E.$httpBackend * @module ngMockE2E
* @description * @description
* Creates a new backend definition. * Creates a new backend definition.
* *
...@@ -1597,19 +1829,21 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1597,19 +1829,21 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. * control how a matched request is handled.
* *
* - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` * - respond –
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* – The respond method takes a set of static data to be returned or a function that can return * – The respond method takes a set of static data to be returned or a function that can return
* an array containing response status (number), response data (string) and response headers * an array containing response status (number), response data (string), response headers
* (Object). * (Object), and the text for the status (string).
* - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` * - passThrough – `{function()}` – Any request matching a backend definition with
* handler, will be pass through to the real backend (an XHR request will be made to the * `passThrough` handler will be passed through to the real backend (an XHR request will be made
* server. * to the server.)
*/ */
/** /**
* @ngdoc method * @ngdoc method
* @name ngMockE2E.$httpBackend#whenGET * @name $httpBackend#whenGET
* @methodOf ngMockE2E.$httpBackend * @module ngMockE2E
* @description * @description
* Creates a new backend definition for GET requests. For more info see `when()`. * Creates a new backend definition for GET requests. For more info see `when()`.
* *
...@@ -1621,8 +1855,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1621,8 +1855,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMockE2E.$httpBackend#whenHEAD * @name $httpBackend#whenHEAD
* @methodOf ngMockE2E.$httpBackend * @module ngMockE2E
* @description * @description
* Creates a new backend definition for HEAD requests. For more info see `when()`. * Creates a new backend definition for HEAD requests. For more info see `when()`.
* *
...@@ -1634,8 +1868,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1634,8 +1868,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMockE2E.$httpBackend#whenDELETE * @name $httpBackend#whenDELETE
* @methodOf ngMockE2E.$httpBackend * @module ngMockE2E
* @description * @description
* Creates a new backend definition for DELETE requests. For more info see `when()`. * Creates a new backend definition for DELETE requests. For more info see `when()`.
* *
...@@ -1647,8 +1881,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1647,8 +1881,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMockE2E.$httpBackend#whenPOST * @name $httpBackend#whenPOST
* @methodOf ngMockE2E.$httpBackend * @module ngMockE2E
* @description * @description
* Creates a new backend definition for POST requests. For more info see `when()`. * Creates a new backend definition for POST requests. For more info see `when()`.
* *
...@@ -1661,8 +1895,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1661,8 +1895,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMockE2E.$httpBackend#whenPUT * @name $httpBackend#whenPUT
* @methodOf ngMockE2E.$httpBackend * @module ngMockE2E
* @description * @description
* Creates a new backend definition for PUT requests. For more info see `when()`. * Creates a new backend definition for PUT requests. For more info see `when()`.
* *
...@@ -1675,8 +1909,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1675,8 +1909,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMockE2E.$httpBackend#whenPATCH * @name $httpBackend#whenPATCH
* @methodOf ngMockE2E.$httpBackend * @module ngMockE2E
* @description * @description
* Creates a new backend definition for PATCH requests. For more info see `when()`. * Creates a new backend definition for PATCH requests. For more info see `when()`.
* *
...@@ -1689,8 +1923,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1689,8 +1923,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
/** /**
* @ngdoc method * @ngdoc method
* @name ngMockE2E.$httpBackend#whenJSONP * @name $httpBackend#whenJSONP
* @methodOf ngMockE2E.$httpBackend * @module ngMockE2E
* @description * @description
* Creates a new backend definition for JSONP requests. For more info see `when()`. * Creates a new backend definition for JSONP requests. For more info see `when()`.
* *
...@@ -1699,7 +1933,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { ...@@ -1699,7 +1933,8 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
* control how a matched request is handled. * control how a matched request is handled.
*/ */
angular.mock.e2e = {}; angular.mock.e2e = {};
angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; angular.mock.e2e.$httpBackendDecorator =
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
angular.mock.clearDataCache = function() { angular.mock.clearDataCache = function() {
...@@ -1707,50 +1942,43 @@ angular.mock.clearDataCache = function() { ...@@ -1707,50 +1942,43 @@ angular.mock.clearDataCache = function() {
cache = angular.element.cache; cache = angular.element.cache;
for(key in cache) { for(key in cache) {
if (cache.hasOwnProperty(key)) { if (Object.prototype.hasOwnProperty.call(cache,key)) {
var handle = cache[key].handle; var handle = cache[key].handle;
handle && angular.element(handle.elem).unbind(); handle && angular.element(handle.elem).off();
delete cache[key]; delete cache[key];
} }
} }
}; };
window.jstestdriver && (function(window) { if(window.jasmine || window.mocha) {
/**
* Global method to output any number of objects into JSTD console. Useful for debugging.
*/
window.dump = function() {
var args = [];
angular.forEach(arguments, function(arg) {
args.push(angular.mock.dump(arg));
});
jstestdriver.console.log.apply(jstestdriver.console, args);
if (window.console) {
window.console.log.apply(window.console, args);
}
};
})(window);
var currentSpec = null,
isSpecRunning = function() {
return !!currentSpec;
};
(window.jasmine || window.mocha) && (function(window) {
var currentSpec = null;
beforeEach(function() { (window.beforeEach || window.setup)(function() {
currentSpec = this; currentSpec = this;
}); });
afterEach(function() { (window.afterEach || window.teardown)(function() {
var injector = currentSpec.$injector; var injector = currentSpec.$injector;
angular.forEach(currentSpec.$modules, function(module) {
if (module && module.$$hashKey) {
module.$$hashKey = undefined;
}
});
currentSpec.$injector = null; currentSpec.$injector = null;
currentSpec.$modules = null; currentSpec.$modules = null;
currentSpec = null; currentSpec = null;
if (injector) { if (injector) {
injector.get('$rootElement').unbind(); injector.get('$rootElement').off();
injector.get('$browser').pollFns.length = 0; injector.get('$browser').pollFns.length = 0;
} }
...@@ -1769,10 +1997,6 @@ window.jstestdriver && (function(window) { ...@@ -1769,10 +1997,6 @@ window.jstestdriver && (function(window) {
angular.callbacks.counter = 0; angular.callbacks.counter = 0;
}); });
function isSpecRunning() {
return currentSpec && (window.mocha || currentSpec.queue.running);
}
/** /**
* @ngdoc function * @ngdoc function
* @name angular.mock.module * @name angular.mock.module
...@@ -1785,9 +2009,11 @@ window.jstestdriver && (function(window) { ...@@ -1785,9 +2009,11 @@ window.jstestdriver && (function(window) {
* *
* See {@link angular.mock.inject inject} for usage example * See {@link angular.mock.inject inject} for usage example
* *
* @param {...(string|Function)} fns any number of modules which are represented as string * @param {...(string|Function|Object)} fns any number of modules which are represented as string
* aliases or as anonymous module initialization functions. The modules are used to * aliases or as anonymous module initialization functions. The modules are used to
* configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an
* object literal is passed they will be registered as values in the module, the key being
* the module name and the value being what is returned.
*/ */
window.module = angular.mock.module = function() { window.module = angular.mock.module = function() {
var moduleFns = Array.prototype.slice.call(arguments, 0); var moduleFns = Array.prototype.slice.call(arguments, 0);
...@@ -1795,11 +2021,19 @@ window.jstestdriver && (function(window) { ...@@ -1795,11 +2021,19 @@ window.jstestdriver && (function(window) {
///////////////////// /////////////////////
function workFn() { function workFn() {
if (currentSpec.$injector) { if (currentSpec.$injector) {
throw Error('Injector already created, can not register a module!'); throw new Error('Injector already created, can not register a module!');
} else { } else {
var modules = currentSpec.$modules || (currentSpec.$modules = []); var modules = currentSpec.$modules || (currentSpec.$modules = []);
angular.forEach(moduleFns, function(module) { angular.forEach(moduleFns, function(module) {
if (angular.isObject(module) && !angular.isArray(module)) {
modules.push(function($provide) {
angular.forEach(module, function(value, key) {
$provide.value(key, value);
});
});
} else {
modules.push(module); modules.push(module);
}
}); });
} }
} }
...@@ -1813,13 +2047,45 @@ window.jstestdriver && (function(window) { ...@@ -1813,13 +2047,45 @@ window.jstestdriver && (function(window) {
* *NOTE*: This function is also published on window for easy access.<br> * *NOTE*: This function is also published on window for easy access.<br>
* *
* The inject function wraps a function into an injectable function. The inject() creates new * The inject function wraps a function into an injectable function. The inject() creates new
* instance of {@link AUTO.$injector $injector} per test, which is then used for * instance of {@link auto.$injector $injector} per test, which is then used for
* resolving references. * resolving references.
* *
* See also {@link angular.mock.module module}
* *
* ## Resolving References (Underscore Wrapping)
* Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this
* in multiple `it()` clauses. To be able to do this we must assign the reference to a variable
* that is declared in the scope of the `describe()` block. Since we would, most likely, want
* the variable to have the same name of the reference we have a problem, since the parameter
* to the `inject()` function would hide the outer variable.
*
* To help with this, the injected parameters can, optionally, be enclosed with underscores.
* These are ignored by the injector when the reference name is resolved.
*
* For example, the parameter `_myService_` would be resolved as the reference `myService`.
* Since it is available in the function body as _myService_, we can then assign it to a variable
* defined in an outer scope.
*
* ```
* // Defined out reference variable outside
* var myService;
*
* // Wrap the parameter in underscores
* beforeEach( inject( function(_myService_){
* myService = _myService_;
* }));
*
* // Use myService in a series of tests.
* it('makes use of myService', function() {
* myService.doStuff();
* });
*
* ```
*
* See also {@link angular.mock.module angular.mock.module}
*
* ## Example
* Example of what a typical jasmine tests looks like with the inject method. * Example of what a typical jasmine tests looks like with the inject method.
* <pre> * ```js
* *
* angular.module('myApplicationModule', []) * angular.module('myApplicationModule', [])
* .value('mode', 'app') * .value('mode', 'app')
...@@ -1850,17 +2116,31 @@ window.jstestdriver && (function(window) { ...@@ -1850,17 +2116,31 @@ window.jstestdriver && (function(window) {
* inject(function(version) { * inject(function(version) {
* expect(version).toEqual('overridden'); * expect(version).toEqual('overridden');
* }); * });
* )); * });
* }); * });
* *
* </pre> * ```
* *
* @param {...Function} fns any number of functions which will be injected using the injector. * @param {...Function} fns any number of functions which will be injected using the injector.
*/ */
var ErrorAddingDeclarationLocationStack = function(e, errorForStack) {
this.message = e.message;
this.name = e.name;
if (e.line) this.line = e.line;
if (e.sourceId) this.sourceId = e.sourceId;
if (e.stack && errorForStack)
this.stack = e.stack + '\n' + errorForStack.stack;
if (e.stackArray) this.stackArray = e.stackArray;
};
ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString;
window.inject = angular.mock.inject = function() { window.inject = angular.mock.inject = function() {
var blockFns = Array.prototype.slice.call(arguments, 0); var blockFns = Array.prototype.slice.call(arguments, 0);
var errorForStack = new Error('Declaration Location'); var errorForStack = new Error('Declaration Location');
return isSpecRunning() ? workFn() : workFn; return isSpecRunning() ? workFn.call(currentSpec) : workFn;
///////////////////// /////////////////////
function workFn() { function workFn() {
var modules = currentSpec.$modules || []; var modules = currentSpec.$modules || [];
...@@ -1873,9 +2153,13 @@ window.jstestdriver && (function(window) { ...@@ -1873,9 +2153,13 @@ window.jstestdriver && (function(window) {
} }
for(var i = 0, ii = blockFns.length; i < ii; i++) { for(var i = 0, ii = blockFns.length; i < ii; i++) {
try { try {
/* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */
injector.invoke(blockFns[i] || angular.noop, this); injector.invoke(blockFns[i] || angular.noop, this);
/* jshint +W040 */
} catch (e) { } catch (e) {
if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; if (e.stack && errorForStack) {
throw new ErrorAddingDeclarationLocationStack(e, errorForStack);
}
throw e; throw e;
} finally { } finally {
errorForStack = null; errorForStack = null;
...@@ -1883,4 +2167,7 @@ window.jstestdriver && (function(window) { ...@@ -1883,4 +2167,7 @@ window.jstestdriver && (function(window) {
} }
} }
}; };
})(window); }
\ No newline at end of file
})(window, window.angular);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment