From 4f7e87bfcceaf5916b5c06ccc9fbf4d965ed73ff Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Wed, 4 Sep 2013 17:00:29 -0500 Subject: [PATCH] Random angular stuff and testing things --- connectors/angular/angular-glue.js | 12 - docs/iphone.png | Bin 0 -> 25652 bytes example/angular/index.html | 32 + example/angular/menu.js | 20 +- ext/angular/src/ionicContent.js | 9 + ext/angular/src/ionicTabBar.js | 78 + ext/angular/test/ionicContent.unit.js | 15 + ext/angular/test/ionicTabBar.unit.js | 84 + ext/angular/tmpl/ionicTabBar.tmpl.html | 2 + ext/angular/tmpl/ionicTabs.tmpl.html | 12 + {connectors => ext}/default/panel-handler.js | 0 ionic.conf.js | 70 + test/event-listeners.js | 42 - vendor/angular/1.2.0rc1/angular-mocks.js | 1952 ++++++++++++++++++ 14 files changed, 2265 insertions(+), 63 deletions(-) delete mode 100644 connectors/angular/angular-glue.js create mode 100644 docs/iphone.png create mode 100644 ext/angular/src/ionicContent.js create mode 100644 ext/angular/src/ionicTabBar.js create mode 100644 ext/angular/test/ionicContent.unit.js create mode 100644 ext/angular/test/ionicTabBar.unit.js create mode 100644 ext/angular/tmpl/ionicTabBar.tmpl.html create mode 100644 ext/angular/tmpl/ionicTabs.tmpl.html rename {connectors => ext}/default/panel-handler.js (100%) create mode 100644 ionic.conf.js delete mode 100644 test/event-listeners.js create mode 100644 vendor/angular/1.2.0rc1/angular-mocks.js diff --git a/connectors/angular/angular-glue.js b/connectors/angular/angular-glue.js deleted file mode 100644 index 89bfbc0612..0000000000 --- a/connectors/angular/angular-glue.js +++ /dev/null @@ -1,12 +0,0 @@ -/* used to tie angular.js with the framework */ -/* nowhere should the framework reference angular.js */ -/* nowhere in angular.js should it reference the framework */ - -var ionic = angular.module('ionic', ['ngTouch']); - -ionic.directive('panel', ['$parse', '$timeout', '$rootElement', - function($parse, $timeout, $rootElement) { - return function(scope, element, attrs) { - }; - } -]); diff --git a/docs/iphone.png b/docs/iphone.png new file mode 100644 index 0000000000000000000000000000000000000000..88b3c6f65ef95a0833c441bff6b0e5b650596863 GIT binary patch literal 25652 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 literal 0 HcmV?d00001 diff --git a/example/angular/index.html b/example/angular/index.html index 6a11b04f8c..b73ae7fabf 100644 --- a/example/angular/index.html +++ b/example/angular/index.html @@ -18,10 +18,42 @@ + + + + + diff --git a/example/angular/menu.js b/example/angular/menu.js index 9f9bf0ec0f..3e7de776b6 100644 --- a/example/angular/menu.js +++ b/example/angular/menu.js @@ -3,25 +3,26 @@ angular.module('ionic.menu', []) .controller('LeftRightMenuController', ['$scope', '$element', function LeftRightMenuCtrl($scope, $element) { var ctrl = ion.controllers.LeftRightMenuViewController; + + $scope.controllerInitData = {}; + + $scope.initIonicController = function() { + $scope._ionicController = new ctrl($scope.controllerInitData); + }; }]) .directive('ionicLeftRightMenu', function() { return { restrict: 'EA', - scope: true, - transclude: true, controller: 'LeftRightMenuController', - compile: function(elm, attrs, transclude) { - return function(scope, element, attrs, menuCtrl) { - console.log('Compile'); - }; - }, - link: function(scope) { - console.log('link'); + link: function($scope, element, attributes) { + $scope + console.log('link', $scope); } } }) +/* .directive('ionicMenu', function() { return { restrict: 'EA', @@ -37,3 +38,4 @@ function LeftRightMenuCtrl($scope, $element) { } }); +*/ diff --git a/ext/angular/src/ionicContent.js b/ext/angular/src/ionicContent.js new file mode 100644 index 0000000000..ac4a2d091b --- /dev/null +++ b/ext/angular/src/ionicContent.js @@ -0,0 +1,9 @@ +angular.module('ionic.ui.content', {}) + +.directive('content', function() { + return { + restrict: 'E', + replace: true, + template: '
' + } +}); diff --git a/ext/angular/src/ionicTabBar.js b/ext/angular/src/ionicTabBar.js new file mode 100644 index 0000000000..fa97d86cfc --- /dev/null +++ b/ext/angular/src/ionicTabBar.js @@ -0,0 +1,78 @@ +angular.module('ionic.ui.tabbar', {}) + +.controller('TabBarCtrl', function($scope) { + $scope.selectTab = function(index) { + }; + $scope.beforeTabSelect = function(index) { + }; + $scope.tabSelected = function(index) { + }; + + this.getSelectedTabIndex = function() { + return $scope.selectedIndex; + } + + this.selectTabAtIndex = function(index) { + $scope.selectedIndex = index; + }; +}) + +.directive('tabBar', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + controller: 'TabBarCtrl', + //templateUrl: 'ext/angular/tmpl/ionicTabBar.tmpl.html', + template: '
', + } +}) + +.controller('TabsCtrl', function($scope) { +}) + +.directive('tabs', function() { + return { + restrict: 'E', + replace: true, + controller: 'TabBarCtrl', + template: '
' + + '' + +'
' + } +}) + +.directive('tabItem', function() { + return { + restrict: 'E', + replace: true, + controller: 'TabBarCtrl', + scope: { + text: '@', + icon: '@', + tabSelected: '@' + }, + link: function(scope, element, attrs, TabBarCtrl) { + // Set a default item text if none is provided + attrs.$observe('text', function(value) { + scope.text = value || 'Item'; + }); + + // Set a default item icon if none is provided + attrs.$observe('icon', function(value) { + scope.icon = value || 'icon-default'; + }); + }, + template: '
  • ' + + '' + + '' + + '{{text}}' + + '
  • ' + } +}); + diff --git a/ext/angular/test/ionicContent.unit.js b/ext/angular/test/ionicContent.unit.js new file mode 100644 index 0000000000..5ae6923660 --- /dev/null +++ b/ext/angular/test/ionicContent.unit.js @@ -0,0 +1,15 @@ +describe('Ionic Content directive', function() { + var compile, element, scope; + + beforeEach(module('ionic.ui.content')); + + beforeEach(inject(function($compile, $rootScope) { + compile = $compile; + scope = $rootScope; + })); + + it('Has content class', function() { + element = compile('')(scope); + expect(element.hasClass('content')).toBe(true); + }); +}); diff --git a/ext/angular/test/ionicTabBar.unit.js b/ext/angular/test/ionicTabBar.unit.js new file mode 100644 index 0000000000..b45babfb15 --- /dev/null +++ b/ext/angular/test/ionicTabBar.unit.js @@ -0,0 +1,84 @@ +describe('Tab Bar Controller', function() { + var compile, element, scope; + + beforeEach(module('ionic.ui.tabbar')); + + beforeEach(inject(function($compile, $rootScope, $controller) { + compile = $compile; + scope = $rootScope; + ctrl = $controller('TabBarCtrl', { $scope: scope, $element: null }); + })); + + it('Select item in controller works', function() { + ctrl.selectTabAtIndex(1); + expect(ctrl.getSelectedTabIndex()).toEqual(1); + }); +}); + +describe('Tab Bar directive', function() { + var compile, element, scope; + + beforeEach(module('ionic.ui.tabbar')); + + //beforeEach(module('ext/angular/tmpl/ionicTabBar.tmpl.html', 'ext/angular/tmpl/ionicTabs.tmpl.html')); + + beforeEach(inject(function($compile, $rootScope) { + compile = $compile; + scope = $rootScope; + })); + + it('Has section wrapper class', function() { + element = compile('')(scope); + expect(element.hasClass('full-section')).toBe(true); + }); +}); + +describe('Tabs directive', function() { + var compile, element, scope; + + beforeEach(module('ionic.ui.tabbar')); + + beforeEach(inject(function($compile, $rootScope) { + compile = $compile; + scope = $rootScope; + })); + + it('Has tab class', function() { + element = compile('')(scope); + expect(element.hasClass('bar-tabs')).toBe(true); + }); + + it('Has tab children', function() { + scope.tabs = [ + { text: 'Home', icon: 'icon-home' }, + { text: 'Fun', icon: 'icon-fun' }, + { text: 'Beer', icon: 'icon-beer' }, + ]; + element = compile('')(scope); + scope.$digest(); + expect(element.find('li').length).toBe(3); + }); +}); + +describe('Tab Item directive', function() { + var compile, element, scope, ctrl; + + beforeEach(module('ionic.ui.tabbar')); + + beforeEach(inject(function($compile, $rootScope) { + compile = $compile; + scope = $rootScope; + })); + + it('Default text works', function() { + element = compile('')(scope); + scope.$digest(); + expect(element.find('a').text()).toEqual('Item'); + }); + + it('Default icon works', function() { + element = compile('')(scope); + scope.$digest(); + expect(element.find('i').hasClass('icon-default')).toBeTruthy(); + }); +}) diff --git a/ext/angular/tmpl/ionicTabBar.tmpl.html b/ext/angular/tmpl/ionicTabBar.tmpl.html new file mode 100644 index 0000000000..92e4a57eb7 --- /dev/null +++ b/ext/angular/tmpl/ionicTabBar.tmpl.html @@ -0,0 +1,2 @@ +
    +
    diff --git a/ext/angular/tmpl/ionicTabs.tmpl.html b/ext/angular/tmpl/ionicTabs.tmpl.html new file mode 100644 index 0000000000..0eda98c893 --- /dev/null +++ b/ext/angular/tmpl/ionicTabs.tmpl.html @@ -0,0 +1,12 @@ + diff --git a/connectors/default/panel-handler.js b/ext/default/panel-handler.js similarity index 100% rename from connectors/default/panel-handler.js rename to ext/default/panel-handler.js diff --git a/ionic.conf.js b/ionic.conf.js new file mode 100644 index 0000000000..e63abde0c6 --- /dev/null +++ b/ionic.conf.js @@ -0,0 +1,70 @@ +// Karma configuration +// Generated on Wed Sep 04 2013 08:59:26 GMT-0500 (CDT) + +module.exports = function(config) { + config.set({ + + // base path, that will be used to resolve files and exclude + basePath: '', + + + // frameworks to use + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'vendor/angular/1.2.0rc1/*', + 'ext/angular/src/**/*.js', + 'ext/angular/test/**/*.js', + ], + + + // list of files to exclude + exclude: [ + '**/*.swp' + ], + + + // test results reporter to use + // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: ['Chrome'], + + + // If browser does not capture in given timeout [ms], kill it + captureTimeout: 60000, + + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false + }); +}; diff --git a/test/event-listeners.js b/test/event-listeners.js deleted file mode 100644 index 578dc14e46..0000000000 --- a/test/event-listeners.js +++ /dev/null @@ -1,42 +0,0 @@ -(function(window, document, ion) { - - // this file should not be apart of the build - // its just just for testing that the correct - // events are being triggered and at the correct - // times, and so we don't have to hardcode/remove - // console calls throughout the code - - ion.on('ready', function(){ - console.log('ready'); - }); - - ion.on('initalized', function(){ - console.log('initalized'); - }); - - ion.on('pageinit', function(e){ - console.log('pageinit:', e.detail); - }); - - ion.on('pageinitfailed', function(){ - console.log('pageinitfailed'); - }); - - ion.on('pageloaded', function(e){ - console.log('pageloaded,', e.detail.data.url, ", Title:", e.detail.data.title); - }); - - ion.on('pagecreate', function(e){ - console.log('pagecreate,', e.detail.url); - }); - - ion.on('pageview', function(){ - console.log('pageview'); - }); - - ion.on('pageremove', function(){ - console.log('pageremove'); - }); - - -})(this, document, ion = ion.FM || {}); \ No newline at end of file diff --git a/vendor/angular/1.2.0rc1/angular-mocks.js b/vendor/angular/1.2.0rc1/angular-mocks.js new file mode 100644 index 0000000000..743ad79541 --- /dev/null +++ b/vendor/angular/1.2.0rc1/angular-mocks.js @@ -0,0 +1,1952 @@ +/** + * @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 expectationsBackend definitions
    Syntax.expect(...).respond(...).when(...).respond(...)
    Typical usagestrict unit testsloose (black-box) unit testing
    Fulfills multiple requestsNOYES
    Order of requests mattersYESNO
    Request requiredYESNO
    Response requiredoptional (see below)YES
    + * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * # Flushing HTTP requests + * + * The $httpBackend used in production, always responds to requests with responses asynchronously. + * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are + * hard to write, follow and maintain. At the same time the testing mock, can't respond + * synchronously because that would change the execution of the code under test. For this reason the + * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending + * requests and thus preserving the async api of the backend, while allowing the test to execute + * synchronously. + * + * + * # Unit testing with mock $httpBackend + * The following code shows how to setup and use the mock backend in unit testing a controller. + * First we create the controller under test + * +
    +  // 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.
    + * + * This function registers a module configuration code. It collects the configuration information + * which will be used when the injector is created by {@link angular.mock.inject inject}. + * + * See {@link angular.mock.inject inject} for usage example + * + * @param {...(string|Function)} fns any number of modules which are represented as string + * aliases or as anonymous module initialization functions. The modules are used to + * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. + */ + window.module = angular.mock.module = function() { + var moduleFns = Array.prototype.slice.call(arguments, 0); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + if (currentSpec.$injector) { + throw Error('Injector already created, can not register a module!'); + } else { + var modules = currentSpec.$modules || (currentSpec.$modules = []); + angular.forEach(moduleFns, function(module) { + modules.push(module); + }); + } + } + }; + + /** + * @ngdoc function + * @name angular.mock.inject + * @description + * + * *NOTE*: This function is also published on window for easy access.
    + * + * 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 + * resolving references. + * + * See also {@link angular.mock.module module} + * + * Example of what a typical jasmine tests looks like with the inject method. + *
    +   *
    +   *   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);