/** * @license AngularJS v1.2.0rc1 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT * * TODO(vojta): wrap whole file into closure during build */ /** * @ngdoc overview * @name angular.mock * @description * * Namespace from 'angular-mocks.js' which contains testing related code. */ angular.mock = {}; /** * ! This is a private undocumented service ! * * @name ngMock.$browser * * @description * This service is a mock implementation of {@link ng.$browser}. It provides fake * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, * cookies, etc... * * The api of this service is the same as that of the real {@link ng.$browser $browser}, except * that there are several helper methods available which can be used in tests. */ angular.mock.$BrowserProvider = function() { this.$get = function() { return new angular.mock.$Browser(); }; }; angular.mock.$Browser = function() { var self = this; this.isMock = true; self.$$url = "http://server/"; self.$$lastUrl = self.$$url; // used by url polling fn self.pollFns = []; // TODO(vojta): remove this temporary api self.$$completeOutstandingRequest = angular.noop; self.$$incOutstandingRequestCount = angular.noop; // register url polling fn self.onUrlChange = function(listener) { self.pollFns.push( function() { if (self.$$lastUrl != self.$$url) { self.$$lastUrl = self.$$url; listener(self.$$url); } } ); return listener; }; self.cookieHash = {}; self.lastCookieHash = {}; self.deferredFns = []; self.deferredNextId = 0; self.defer = function(fn, delay) { delay = delay || 0; self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); self.deferredFns.sort(function(a,b){ return a.time - b.time;}); return self.deferredNextId++; }; self.defer.now = 0; self.defer.cancel = function(deferId) { var fnIndex; angular.forEach(self.deferredFns, function(fn, index) { if (fn.id === deferId) fnIndex = index; }); if (fnIndex !== undefined) { self.deferredFns.splice(fnIndex, 1); return true; } return false; }; /** * @name ngMock.$browser#defer.flush * @methodOf ngMock.$browser * * @description * Flushes all pending requests and executes the defer callbacks. * * @param {number=} number of milliseconds to flush. See {@link #defer.now} */ self.defer.flush = function(delay) { if (angular.isDefined(delay)) { self.defer.now += delay; } else { if (self.deferredFns.length) { self.defer.now = self.deferredFns[self.deferredFns.length-1].time; } else { throw Error('No deferred tasks to be flushed'); } } while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { self.deferredFns.shift().fn(); } }; /** * @name ngMock.$browser#defer.flushNext * @methodOf ngMock.$browser * * @description * Flushes next pending request and compares it to the provided delay * * @param {number=} expectedDelay the delay value that will be asserted against the delay of the next timeout function */ self.defer.flushNext = function(expectedDelay) { var tick = self.deferredFns.shift(); expect(tick.time).toEqual(expectedDelay); tick.fn(); }; /** * @name ngMock.$browser#defer.now * @propertyOf ngMock.$browser * * @description * Current milliseconds mock time. */ self.$$baseHref = ''; self.baseHref = function() { return this.$$baseHref; }; }; angular.mock.$Browser.prototype = { /** * @name ngMock.$browser#poll * @methodOf ngMock.$browser * * @description * run all fns in pollFns */ poll: function poll() { angular.forEach(this.pollFns, function(pollFn){ pollFn(); }); }, addPollFn: function(pollFn) { this.pollFns.push(pollFn); return pollFn; }, url: function(url, replace) { if (url) { this.$$url = url; return this; } return this.$$url; }, cookies: function(name, value) { if (name) { if (value == undefined) { delete this.cookieHash[name]; } else { if (angular.isString(value) && //strings only value.length <= 4096) { //strict cookie storage limits this.cookieHash[name] = value; } } } else { if (!angular.equals(this.cookieHash, this.lastCookieHash)) { this.lastCookieHash = angular.copy(this.cookieHash); this.cookieHash = angular.copy(this.cookieHash); } return this.cookieHash; } }, notifyWhenNoOutstandingRequests: function(fn) { fn(); } }; /** * @ngdoc object * @name ngMock.$exceptionHandlerProvider * * @description * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed * into the `$exceptionHandler`. */ /** * @ngdoc object * @name ngMock.$exceptionHandler * * @description * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration * information. * * *
* describe('$exceptionHandlerProvider', function() { * * it('should capture log messages and exceptions', function() { * * module(function($exceptionHandlerProvider) { * $exceptionHandlerProvider.mode('log'); * }); * * inject(function($log, $exceptionHandler, $timeout) { * $timeout(function() { $log.log(1); }); * $timeout(function() { $log.log(2); throw 'banana peel'; }); * $timeout(function() { $log.log(3); }); * expect($exceptionHandler.errors).toEqual([]); * expect($log.assertEmpty()); * $timeout.flush(); * expect($exceptionHandler.errors).toEqual(['banana peel']); * expect($log.log.logs).toEqual([[1], [2], [3]]); * }); * }); * }); **/ angular.mock.$ExceptionHandlerProvider = function() { var handler; /** * @ngdoc method * @name ngMock.$exceptionHandlerProvider#mode * @methodOf ngMock.$exceptionHandlerProvider * * @description * Sets the logging mode. * * @param {string} mode Mode of operation, defaults to `rethrow`. * * - `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 * make these tests fail. * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. * See {@link ngMock.$log#assertEmpty assertEmpty()} and * {@link ngMock.$log#reset reset()} */ this.mode = function(mode) { switch(mode) { case 'rethrow': handler = function(e) { throw e; }; break; case 'log': var errors = []; handler = function(e) { if (arguments.length == 1) { errors.push(e); } else { errors.push([].slice.call(arguments, 0)); } }; handler.errors = errors; break; default: throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); } }; this.$get = function() { return handler; }; this.mode('rethrow'); }; /** * @ngdoc service * @name ngMock.$log * * @description * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays * (one array per logging level). These arrays are exposed as `logs` property of each of the * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. * */ angular.mock.$LogProvider = function() { var debug = true; function concat(array1, array2, index) { return array1.concat(Array.prototype.slice.call(array2, index)); } this.debugEnabled = function(flag) { if (isDefined(flag)) { debug = flag; return this; } else { return debug; } }; this.$get = function () { var $log = { log: function() { $log.log.logs.push(concat([], arguments, 0)); }, warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, info: function() { $log.info.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 * @name ngMock.$log#reset * @methodOf ngMock.$log * * @description * Reset all of the logging arrays to empty. */ $log.reset = function () { /** * @ngdoc property * @name ngMock.$log#log.logs * @propertyOf ngMock.$log * * @description * Array of messages logged using {@link ngMock.$log#log}. * * @example *
* $log.log('Some Log'); * var first = $log.log.logs.unshift(); **/ $log.log.logs = []; /** * @ngdoc property * @name ngMock.$log#info.logs * @propertyOf ngMock.$log * * @description * Array of messages logged using {@link ngMock.$log#info}. * * @example *
* $log.info('Some Info'); * var first = $log.info.logs.unshift(); **/ $log.info.logs = []; /** * @ngdoc property * @name ngMock.$log#warn.logs * @propertyOf ngMock.$log * * @description * Array of messages logged using {@link ngMock.$log#warn}. * * @example *
* $log.warn('Some Warning'); * var first = $log.warn.logs.unshift(); **/ $log.warn.logs = []; /** * @ngdoc property * @name ngMock.$log#error.logs * @propertyOf ngMock.$log * * @description * Array of messages logged using {@link ngMock.$log#error}. * * @example *
* $log.log('Some Error'); * var first = $log.error.logs.unshift(); **/ $log.error.logs = []; /** * @ngdoc property * @name ngMock.$log#debug.logs * @propertyOf ngMock.$log * * @description * Array of messages logged using {@link ngMock.$log#debug}. * * @example *
* $log.debug('Some Error'); * var first = $log.debug.logs.unshift(); **/ $log.debug.logs = [] }; /** * @ngdoc method * @name ngMock.$log#assertEmpty * @methodOf ngMock.$log * * @description * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. */ $log.assertEmpty = function() { var errors = []; angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) { angular.forEach($log[logLevel].logs, function(log) { angular.forEach(log, function (logItem) { errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); }); }); }); if (errors.length) { errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + "log message was not checked and removed:"); errors.push(''); throw new Error(errors.join('\n---------\n')); } }; $log.reset(); return $log; }; }; (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)))?$/; function jsonStringToDate(string) { var match; if (match = string.match(R_ISO8061_STR)) { var date = new Date(0), tzHour = 0, tzMin = 0; if (match[9]) { tzHour = int(match[9] + match[10]); tzMin = int(match[9] + match[11]); } 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)); return date; } return string; } function int(str) { return parseInt(str, 10); } function padNumber(num, digits, trim) { var neg = ''; if (num < 0) { neg = '-'; num = -num; } num = '' + num; while(num.length < digits) num = '0' + num; if (trim) num = num.substr(num.length - digits); return neg + num; } /** * @ngdoc object * @name angular.mock.TzDate * @description * * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. * * Mock of the Date type which has its timezone specified via constructor arg. * * The main purpose is to create Date-like instances with timezone fixed to the specified timezone * offset, so that we can test code that depends on local timezone settings without dependency on * the time zone settings of the machine where the code is running. * * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* * * @example * !!!! WARNING !!!!! * This is not a complete Date object so only methods that were implemented can be called safely. * To make matters worse, TzDate instances inherit stuff from Date via a prototype. * * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is * incomplete we might be missing some non-standard methods. This can result in errors like: * "Date.prototype.foo called on incompatible Object". * *
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); * newYearInBratislava.getTimezoneOffset() => -60; * newYearInBratislava.getFullYear() => 2010; * newYearInBratislava.getMonth() => 0; * newYearInBratislava.getDate() => 1; * newYearInBratislava.getHours() => 0; * newYearInBratislava.getMinutes() => 0; * newYearInBratislava.getSeconds() => 0; ** */ angular.mock.TzDate = function (offset, timestamp) { var self = new Date(0); if (angular.isString(timestamp)) { var tsStr = timestamp; self.origDate = jsonStringToDate(timestamp); timestamp = self.origDate.getTime(); if (isNaN(timestamp)) throw { name: "Illegal Argument", message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" }; } else { self.origDate = new Date(timestamp); } var localOffset = new Date(timestamp).getTimezoneOffset(); self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; self.date = new Date(timestamp + self.offsetDiff); self.getTime = function() { return self.date.getTime() - self.offsetDiff; }; self.toLocaleDateString = function() { return self.date.toLocaleDateString(); }; self.getFullYear = function() { return self.date.getFullYear(); }; self.getMonth = function() { return self.date.getMonth(); }; self.getDate = function() { return self.date.getDate(); }; self.getHours = function() { return self.date.getHours(); }; self.getMinutes = function() { return self.date.getMinutes(); }; self.getSeconds = function() { return self.date.getSeconds(); }; self.getMilliseconds = function() { return self.date.getMilliseconds(); }; self.getTimezoneOffset = function() { return offset * 60; }; self.getUTCFullYear = function() { return self.origDate.getUTCFullYear(); }; self.getUTCMonth = function() { return self.origDate.getUTCMonth(); }; self.getUTCDate = function() { return self.origDate.getUTCDate(); }; self.getUTCHours = function() { return self.origDate.getUTCHours(); }; self.getUTCMinutes = function() { return self.origDate.getUTCMinutes(); }; self.getUTCSeconds = function() { return self.origDate.getUTCSeconds(); }; self.getUTCMilliseconds = function() { return self.origDate.getUTCMilliseconds(); }; self.getDay = function() { return self.date.getDay(); }; // provide this method only on browsers that already have it if (self.toISOString) { self.toISOString = function() { return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + padNumber(self.origDate.getUTCDate(), 2) + 'T' + padNumber(self.origDate.getUTCHours(), 2) + ':' + padNumber(self.origDate.getUTCMinutes(), 2) + ':' + padNumber(self.origDate.getUTCSeconds(), 2) + '.' + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' } } //hide all methods not implemented in this mock that the Date prototype exposes var unimplementedMethods = ['getUTCDay', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; angular.forEach(unimplementedMethods, function(methodName) { self[methodName] = function() { throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); }; }); return self; }; //make "tzDateInstance instanceof Date" return true angular.mock.TzDate.prototype = Date.prototype; })(); angular.mock.animate = angular.module('mock.animate', ['ng']) .config(['$provide', function($provide) { $provide.decorator('$animate', function($delegate) { var animate = { queue : [], enabled : $delegate.enabled, flushNext : function(name) { var tick = animate.queue.shift(); expect(tick.method).toBe(name); tick.fn(); return tick; } }; forEach(['enter','leave','move','addClass','removeClass'], function(method) { animate[method] = function() { var params = arguments; animate.queue.push({ method : method, params : params, element : angular.isElement(params[0]) && params[0], parent : angular.isElement(params[1]) && params[1], after : angular.isElement(params[2]) && params[2], fn : function() { $delegate[method].apply($delegate, params); } }); }; }); return animate; }); }]); /** * @ngdoc function * @name angular.mock.dump * @description * * *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. * * 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. * @return {string} a serialized string of the argument */ angular.mock.dump = function(object) { return serialize(object); function serialize(object) { var out; if (angular.isElement(object)) { object = angular.element(object); out = angular.element(''); angular.forEach(object, function(element) { out.append(angular.element(element).clone()); }); out = out.html(); } else if (angular.isArray(object)) { out = []; angular.forEach(object, function(o) { out.push(serialize(o)); }); out = '[ ' + out.join(', ') + ' ]'; } else if (angular.isObject(object)) { if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { out = serializeScope(object); } else if (object instanceof Error) { out = object.stack || ('' + object.name + ': ' + object.message); } else { // TODO(i): this prevents methods to be logged, we should have a better way to serialize objects out = angular.toJson(object, true); } } else { out = String(object); } return out; } function serializeScope(scope, offset) { offset = offset || ' '; var log = [offset + 'Scope(' + scope.$id + '): {']; for ( var key in scope ) { if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { log.push(' ' + key + ': ' + angular.toJson(scope[key])); } } var child = scope.$$childHead; while(child) { log.push(serializeScope(child, offset + ' ')); child = child.$$nextSibling; } log.push('}'); return log.join('\n' + offset); } }; /** * @ngdoc object * @name ngMock.$httpBackend * @description * Fake HTTP backend implementation suitable for unit testing applications that use the * {@link ng.$http $http service}. * * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less * 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 * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or * {@link http://en.wikipedia.org/wiki/JSONP 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 * application make requests, respond with pre-trained responses and assert that the end result is * what we expect it to be. * * This mock implementation can be used to respond with static or dynamic responses via the * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). * * 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 * 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. * * 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: * * - `$httpBackend.expect` - specifies a request expectation * - `$httpBackend.when` - specifies a backend definition * * * # Request Expectations vs Backend Definitions * * Request expectations provide a way to make assertions about requests made by the application and * to define responses for those requests. The test will fail if the expected requests are not made * or they are made in the wrong order. * * Backend definitions allow you to define a fake backend for your application which doesn't assert * if a particular request was made or not, it just returns a trained response if a request is made. * The test will pass whether or not the request gets made during testing. * * *
Request expectations | Backend definitions | |
---|---|---|
Syntax | *.expect(...).respond(...) | *.when(...).respond(...) | *
Typical usage | *strict unit tests | *loose (black-box) unit testing | *
Fulfills multiple requests | *NO | *YES | *
Order of requests matters | *YES | *NO | *
Request required | *YES | *NO | *
Response required | *optional (see below) | *YES | *
// The controller code function MyController($scope, $http) { var authToken; $http.get('/auth.py').success(function(data, status, headers) { authToken = headers('A-Token'); $scope.user = data; }); $scope.saveMessage = function(message) { var headers = { 'Authorization': authToken }; $scope.status = 'Saving...'; $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) { $scope.status = ''; }).error(function() { $scope.status = 'ERROR!'; }); }; }* * Now we setup the mock backend and create the test specs. *
// testing controller describe('MyController', function() { var $httpBackend, $rootScope, createController; beforeEach(inject(function($injector) { // Set up the mock http service responses $httpBackend = $injector.get('$httpBackend'); // backend definition common for all tests $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 }); }; })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); it('should fetch authentication token', function() { $httpBackend.expectGET('/auth.py'); var controller = createController(); $httpBackend.flush(); }); it('should send msg to server', function() { var controller = createController(); $httpBackend.flush(); // now you don’t care about the authentication, but // the controller will still send the request and // $httpBackend will respond without you having to // specify the expectation and response for this request $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); $rootScope.saveMessage('message content'); expect($rootScope.status).toBe('Saving...'); $httpBackend.flush(); expect($rootScope.status).toBe(''); }); it('should send auth header', function() { var controller = createController(); $httpBackend.flush(); $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { // check if the header was send, if it wasn't the expectation won't // match the request and the test will fail return headers['Authorization'] == 'xxx'; }).respond(201, ''); $rootScope.saveMessage('whatever'); $httpBackend.flush(); }); });*/ angular.mock.$HttpBackendProvider = function() { this.$get = ['$rootScope', createHttpBackendMock]; }; /** * General factory function for $httpBackend mock. * Returns instance for unit testing (when no arguments specified): * - passing through is disabled * - auto flushing is disabled * * Returns instance for e2e testing (when `$delegate` and `$browser` specified): * - passing through (delegating request to real backend) is enabled * - auto flushing is enabled * * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) * @param {Object=} $browser Auto-flushing enabled if specified * @return {Object} Instance of $httpBackend mock */ function createHttpBackendMock($rootScope, $delegate, $browser) { var definitions = [], expectations = [], responses = [], responsesPush = angular.bind(responses, responses.push); function createResponse(status, data, headers) { if (angular.isFunction(status)) return status; return function() { return angular.isNumber(status) ? [status, data, headers] : [200, status, data]; }; } // TODO(vojta): change params to: method, url, data, headers, callback function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { var xhr = new MockXhr(), expectation = expectations[0], wasExpected = false; function prettyPrint(data) { return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) ? data : angular.toJson(data); } function wrapResponse(wrapped) { if (!$browser && timeout && timeout.then) timeout.then(handleTimeout); return handleResponse; function handleResponse() { var response = wrapped.response(method, url, data, headers); xhr.$$respHeaders = response[2]; callback(response[0], response[1], xhr.getAllResponseHeaders()); } function handleTimeout() { for (var i = 0, ii = responses.length; i < ii; i++) { if (responses[i] === handleResponse) { responses.splice(i, 1); callback(-1, undefined, ''); break; } } } } if (expectation && expectation.match(method, url)) { if (!expectation.matchData(data)) throw new Error('Expected ' + expectation + ' with different data\n' + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); if (!expectation.matchHeaders(headers)) throw new Error('Expected ' + expectation + ' with different headers\n' + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + prettyPrint(headers)); expectations.shift(); if (expectation.response) { responses.push(wrapResponse(expectation)); return; } wasExpected = true; } var i = -1, definition; while ((definition = definitions[++i])) { if (definition.match(method, url, data, headers || {})) { if (definition.response) { // if $browser specified, we do auto flush all requests ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); } else if (definition.passThrough) { $delegate(method, url, data, callback, headers, timeout, withCredentials); } else throw Error('No response defined !'); return; } } throw wasExpected ? new Error('No response defined !') : new Error('Unexpected request: ' + method + ' ' + url + '\n' + (expectation ? 'Expected ' + expectation : 'No more request expected')); } /** * @ngdoc method * @name ngMock.$httpBackend#when * @methodOf ngMock.$httpBackend * @description * Creates a new backend definition. * * @param {string} method HTTP method. * @param {string|RegExp} url HTTP url. * @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 * object and returns true if the headers match the current definition. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. * * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` * – 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 * (Object). */ $httpBackend.when = function(method, url, data, headers) { var definition = new MockHttpExpectation(method, url, data, headers), chain = { respond: function(status, data, headers) { definition.response = createResponse(status, data, headers); } }; if ($browser) { chain.passThrough = function() { definition.passThrough = true; }; } definitions.push(definition); return chain; }; /** * @ngdoc method * @name ngMock.$httpBackend#whenGET * @methodOf ngMock.$httpBackend * @description * Creates a new backend definition for GET requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#whenHEAD * @methodOf ngMock.$httpBackend * @description * Creates a new backend definition for HEAD requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#whenDELETE * @methodOf ngMock.$httpBackend * @description * Creates a new backend definition for DELETE requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#whenPOST * @methodOf ngMock.$httpBackend * @description * Creates a new backend definition for POST requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @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. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#whenPUT * @methodOf ngMock.$httpBackend * @description * Creates a new backend definition for PUT requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @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. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#whenJSONP * @methodOf ngMock.$httpBackend * @description * Creates a new backend definition for JSONP requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ createShortMethods('when'); /** * @ngdoc method * @name ngMock.$httpBackend#expect * @methodOf ngMock.$httpBackend * @description * Creates a new request expectation. * * @param {string} method HTTP method. * @param {string|RegExp} url HTTP url. * @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 * object and returns true if the headers match the current expectation. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. * * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` * – 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 * (Object). */ $httpBackend.expect = function(method, url, data, headers) { var expectation = new MockHttpExpectation(method, url, data, headers); expectations.push(expectation); return { respond: function(status, data, headers) { expectation.response = createResponse(status, data, headers); } }; }; /** * @ngdoc method * @name ngMock.$httpBackend#expectGET * @methodOf ngMock.$httpBackend * @description * Creates a new request expectation for GET requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. See #expect for more info. */ /** * @ngdoc method * @name ngMock.$httpBackend#expectHEAD * @methodOf ngMock.$httpBackend * @description * Creates a new request expectation for HEAD requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#expectDELETE * @methodOf ngMock.$httpBackend * @description * Creates a new request expectation for DELETE requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#expectPOST * @methodOf ngMock.$httpBackend * @description * Creates a new request expectation for POST requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @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. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#expectPUT * @methodOf ngMock.$httpBackend * @description * Creates a new request expectation for PUT requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @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. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#expectPATCH * @methodOf ngMock.$httpBackend * @description * Creates a new request expectation for PATCH requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @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. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ /** * @ngdoc method * @name ngMock.$httpBackend#expectJSONP * @methodOf ngMock.$httpBackend * @description * Creates a new request expectation for JSONP requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ createShortMethods('expect'); /** * @ngdoc method * @name ngMock.$httpBackend#flush * @methodOf ngMock.$httpBackend * @description * Flushes all pending requests using the trained responses. * * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, * all pending requests will be flushed. If there are no pending requests when the flush method * is called an exception is thrown (as this typically a sign of programming error). */ $httpBackend.flush = function(count) { $rootScope.$digest(); if (!responses.length) throw Error('No pending request to flush !'); if (angular.isDefined(count)) { while (count--) { if (!responses.length) throw Error('No more pending request to flush !'); responses.shift()(); } } else { while (responses.length) { responses.shift()(); } } $httpBackend.verifyNoOutstandingExpectation(); }; /** * @ngdoc method * @name ngMock.$httpBackend#verifyNoOutstandingExpectation * @methodOf ngMock.$httpBackend * @description * 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. * * Typically, you would call this method following each test case that asserts requests using an * "afterEach" clause. * *
* afterEach($httpBackend.verifyNoOutstandingExpectation); **/ $httpBackend.verifyNoOutstandingExpectation = function() { $rootScope.$digest(); if (expectations.length) { throw new Error('Unsatisfied requests: ' + expectations.join(', ')); } }; /** * @ngdoc method * @name ngMock.$httpBackend#verifyNoOutstandingRequest * @methodOf ngMock.$httpBackend * @description * 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 * "afterEach" clause. * *
* afterEach($httpBackend.verifyNoOutstandingRequest); **/ $httpBackend.verifyNoOutstandingRequest = function() { if (responses.length) { throw Error('Unflushed requests: ' + responses.length); } }; /** * @ngdoc method * @name ngMock.$httpBackend#resetExpectations * @methodOf ngMock.$httpBackend * @description * 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 * $httpBackend mock. */ $httpBackend.resetExpectations = function() { expectations.length = 0; responses.length = 0; }; return $httpBackend; function createShortMethods(prefix) { angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { $httpBackend[prefix + method] = function(url, headers) { return $httpBackend[prefix](method, url, undefined, headers) } }); angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { $httpBackend[prefix + method] = function(url, data, headers) { return $httpBackend[prefix](method, url, data, headers) } }); } } function MockHttpExpectation(method, url, data, headers) { this.data = data; this.headers = headers; this.match = function(m, u, d, h) { if (method != m) return false; if (!this.matchUrl(u)) return false; if (angular.isDefined(d) && !this.matchData(d)) return false; if (angular.isDefined(h) && !this.matchHeaders(h)) return false; return true; }; this.matchUrl = function(u) { if (!url) return true; if (angular.isFunction(url.test)) return url.test(u); return url == u; }; this.matchHeaders = function(h) { if (angular.isUndefined(headers)) return true; if (angular.isFunction(headers)) return headers(h); return angular.equals(headers, h); }; this.matchData = function(d) { if (angular.isUndefined(data)) return true; if (data && angular.isFunction(data.test)) return data.test(d); if (data && angular.isFunction(data)) return data(d); if (data && !angular.isString(data)) return angular.toJson(data) == d; return data == d; }; this.toString = function() { return method + ' ' + url; }; } function MockXhr() { // hack for testing $http, $httpBackend MockXhr.$$lastInstance = this; this.open = function(method, url, async) { this.$$method = method; this.$$url = url; this.$$async = async; this.$$reqHeaders = {}; this.$$respHeaders = {}; }; this.send = function(data) { this.$$data = data; }; this.setRequestHeader = function(key, value) { this.$$reqHeaders[key] = value; }; this.getResponseHeader = function(name) { // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last var header = this.$$respHeaders[name]; if (header) return header; name = angular.lowercase(name); header = this.$$respHeaders[name]; if (header) return header; header = undefined; angular.forEach(this.$$respHeaders, function(headerVal, headerName) { if (!header && angular.lowercase(headerName) == name) header = headerVal; }); return header; }; this.getAllResponseHeaders = function() { var lines = []; angular.forEach(this.$$respHeaders, function(value, key) { lines.push(key + ': ' + value); }); return lines.join('\n'); }; this.abort = angular.noop; } /** * @ngdoc function * @name ngMock.$timeout * @description * * This service is just a simple decorator for {@link ng.$timeout $timeout} service * that adds a "flush" and "verifyNoPendingTasks" methods. */ angular.mock.$TimeoutDecorator = function($delegate, $browser) { /** * @ngdoc method * @name ngMock.$timeout#flush * @methodOf ngMock.$timeout * @description * * Flushes the queue of pending tasks. * * @param {number=} delay maximum timeout amount to flush up until */ $delegate.flush = function(delay) { $browser.defer.flush(delay); }; /** * @ngdoc method * @name ngMock.$timeout#flushNext * @methodOf ngMock.$timeout * @description * * Flushes the next timeout in the queue and compares it to the provided delay * * @param {number=} expectedDelay the delay value that will be asserted against the delay of the next timeout function */ $delegate.flushNext = function(expectedDelay) { $browser.defer.flushNext(expectedDelay); }; /** * @ngdoc method * @name ngMock.$timeout#verifyNoPendingTasks * @methodOf ngMock.$timeout * @description * * Verifies that there are no pending tasks that need to be flushed. */ $delegate.verifyNoPendingTasks = function() { if ($browser.deferredFns.length) { throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + formatPendingTasksAsString($browser.deferredFns)); } }; function formatPendingTasksAsString(tasks) { var result = []; angular.forEach(tasks, function(task) { result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); }); return result.join(', '); } return $delegate; }; /** * */ angular.mock.$RootElementProvider = function() { this.$get = function() { return angular.element(''); } }; /** * @ngdoc overview * @name ngMock * @description * * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful * mocks to the {@link AUTO.$injector $injector}. */ angular.module('ngMock', ['ng']).provider({ $browser: angular.mock.$BrowserProvider, $exceptionHandler: angular.mock.$ExceptionHandlerProvider, $log: angular.mock.$LogProvider, $httpBackend: angular.mock.$HttpBackendProvider, $rootElement: angular.mock.$RootElementProvider }).config(function($provide) { $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); }); /** * @ngdoc overview * @name ngMockE2E * @description * * 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 - * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. */ angular.module('ngMockE2E', ['ng']).config(function($provide) { $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); }); /** * @ngdoc object * @name ngMockE2E.$httpBackend * @description * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of * applications that use the {@link ng.$http $http service}. * * *Note*: For fake http backend implementation suitable for unit testing please see * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. * * This implementation can be used to respond with static or dynamic responses via the `when` api * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch * templates from a webserver). * * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application * is being developed with the real backend api replaced with a mock, it is often desirable for * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch * templates or static files from the webserver). To configure the backend with this behavior * use the `passThrough` request handler of `when` instead of `respond`. * * Additionally, we don't want to manually have to flush mocked out requests like we do during unit * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests * automatically, closely simulating the behavior of the XMLHttpRequest object. * * 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: * *
* myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); * myAppDev.run(function($httpBackend) { * phones = [{name: 'phone1'}, {name: 'phone2'}]; * * // returns the current list of phones * $httpBackend.whenGET('/phones').respond(phones); * * // adds a new phone to the phones array * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { * phones.push(angular.fromJson(data)); * }); * $httpBackend.whenGET(/^\/templates\//).passThrough(); * //... * }); ** * Afterwards, bootstrap your app with this new module. */ /** * @ngdoc method * @name ngMockE2E.$httpBackend#when * @methodOf ngMockE2E.$httpBackend * @description * Creates a new backend definition. * * @param {string} method HTTP method. * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header * object and returns true if the headers match the current definition. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * control how a matched request is handled. * * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` * – 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 * (Object). * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` * handler, will be pass through to the real backend (an XHR request will be made to the * server. */ /** * @ngdoc method * @name ngMockE2E.$httpBackend#whenGET * @methodOf ngMockE2E.$httpBackend * @description * Creates a new backend definition for GET requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * control how a matched request is handled. */ /** * @ngdoc method * @name ngMockE2E.$httpBackend#whenHEAD * @methodOf ngMockE2E.$httpBackend * @description * Creates a new backend definition for HEAD requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * control how a matched request is handled. */ /** * @ngdoc method * @name ngMockE2E.$httpBackend#whenDELETE * @methodOf ngMockE2E.$httpBackend * @description * Creates a new backend definition for DELETE requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * control how a matched request is handled. */ /** * @ngdoc method * @name ngMockE2E.$httpBackend#whenPOST * @methodOf ngMockE2E.$httpBackend * @description * Creates a new backend definition for POST requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * control how a matched request is handled. */ /** * @ngdoc method * @name ngMockE2E.$httpBackend#whenPUT * @methodOf ngMockE2E.$httpBackend * @description * Creates a new backend definition for PUT requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * control how a matched request is handled. */ /** * @ngdoc method * @name ngMockE2E.$httpBackend#whenPATCH * @methodOf ngMockE2E.$httpBackend * @description * Creates a new backend definition for PATCH requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * control how a matched request is handled. */ /** * @ngdoc method * @name ngMockE2E.$httpBackend#whenJSONP * @methodOf ngMockE2E.$httpBackend * @description * Creates a new backend definition for JSONP requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that * control how a matched request is handled. */ angular.mock.e2e = {}; angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; angular.mock.clearDataCache = function() { var key, cache = angular.element.cache; for(key in cache) { if (cache.hasOwnProperty(key)) { var handle = cache[key].handle; handle && angular.element(handle.elem).off(); delete cache[key]; } } }; (window.jasmine || window.mocha) && (function(window) { var currentSpec = null; beforeEach(function() { currentSpec = this; }); afterEach(function() { var injector = currentSpec.$injector; currentSpec.$injector = null; currentSpec.$modules = null; currentSpec = null; if (injector) { injector.get('$rootElement').off(); injector.get('$browser').pollFns.length = 0; } angular.mock.clearDataCache(); // clean up jquery's fragment cache angular.forEach(angular.element.fragments, function(val, key) { delete angular.element.fragments[key]; }); MockXhr.$$lastInstance = null; angular.forEach(angular.callbacks, function(val, key) { delete angular.callbacks[key]; }); angular.callbacks.counter = 0; }); function isSpecRunning() { return currentSpec && (window.mocha || currentSpec.queue.running); } /** * @ngdoc function * @name angular.mock.module * @description * * *NOTE*: This function is also published on window for easy access.
* * angular.module('myApplicationModule', []) * .value('mode', 'app') * .value('version', 'v1.0.1'); * * * describe('MyApp', function() { * * // You need to load modules that you want to test, * // it loads only the "ng" module by default. * beforeEach(module('myApplicationModule')); * * * // inject() is used to inject arguments of all given functions * it('should provide a version', inject(function(mode, version) { * expect(version).toEqual('v1.0.1'); * expect(mode).toEqual('app'); * })); * * * // The inject and module method can also be used inside of the it or beforeEach * it('should override a version and test the new version is injected', function() { * // module() takes functions or strings (module aliases) * module(function($provide) { * $provide.value('version', 'overridden'); // override version here * }); * * inject(function(version) { * expect(version).toEqual('overridden'); * }); * )); * }); * ** * @param {...Function} fns any number of functions which will be injected using the injector. */ window.inject = angular.mock.inject = function() { var blockFns = Array.prototype.slice.call(arguments, 0); var errorForStack = new Error('Declaration Location'); return isSpecRunning() ? workFn() : workFn; ///////////////////// function workFn() { var modules = currentSpec.$modules || []; modules.unshift('ngMock'); modules.unshift('ng'); var injector = currentSpec.$injector; if (!injector) { injector = currentSpec.$injector = angular.injector(modules); } for(var i = 0, ii = blockFns.length; i < ii; i++) { try { injector.invoke(blockFns[i] || angular.noop, this); } catch (e) { if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; throw e; } finally { errorForStack = null; } } } }; })(window);