diff --git a/.gitignore b/.gitignore
index e2a3069..942598d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
-/target
+target
*~
+.*
diff --git a/pom.xml b/pom.xml
index edcd52b..11cd0be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,113 +1,137 @@
- var values = {name: 'misko', gender: 'male'}; - var log = []; - angular.forEach(values, function(value, key){ - this.push(key + ': ' + value); - }, log); - expect(log).toEqual(['name: misko', 'gender:male']); -- * - * @param {Object|Array} obj Object to iterate over. - * @param {function()} iterator Iterator function. - * @param {Object} context Object to become context (`this`) for the iterator function. - * @returns {Objet|Array} Reference to `obj`. - */ -function forEach(obj, iterator, context) { - var key; - if (obj) { - if (isFunction(obj)){ - for (key in obj) { - if (key != 'prototype' && key != $length && key != $name && obj.hasOwnProperty(key)) { - iterator.call(context, obj[key], key); - } - } - } else if (obj.forEach && obj.forEach !== forEach) { - obj.forEach(iterator, context); - } else if (isObject(obj) && isNumber(obj.length)) { - for (key = 0; key < obj.length; key++) - iterator.call(context, obj[key], key); - } else { - for (key in obj) - iterator.call(context, obj[key], key); - } - } - return obj; -} - -function forEachSorted(obj, iterator, context) { - var keys = []; - for (var key in obj) keys.push(key); - keys.sort(); - for ( var i = 0; i < keys.length; i++) { - iterator.call(context, obj[keys[i]], keys[i]); - } - return keys; -} - - -function formatError(arg) { - if (arg instanceof Error) { - if (arg.stack) { - arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ? - 'Error: ' + arg.message + '\n' + arg.stack : arg.stack; - } else if (arg.sourceURL) { - arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; - } - } - return arg; -} - -/** - * @description - * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric - * characters such as '012ABC'. The reason why we are not using simply a number counter is that - * the number string gets longer over time, and it can also overflow, where as the the nextId - * will grow much slower, it is a string, and it will never overflow. - * - * @returns an unique alpha-numeric string - */ -function nextUid() { - var index = uid.length; - var digit; - - while(index) { - index--; - digit = uid[index].charCodeAt(0); - if (digit == 57 /*'9'*/) { - uid[index] = 'A'; - return uid.join(''); - } - if (digit == 90 /*'Z'*/) { - uid[index] = '0'; - } else { - uid[index] = String.fromCharCode(digit + 1); - return uid.join(''); - } - } - uid.unshift('0'); - return uid.join(''); -} - -/** - * @workInProgress - * @ngdoc function - * @name angular.extend - * @function - * - * @description - * Extends the destination object `dst` by copying all of the properties from the `src` object(s) to - * `dst`. You can specify multiple `src` objects. - * - * @param {Object} dst The destination object. - * @param {...Object} src The source object(s). - */ -function extend(dst) { - forEach(arguments, function(obj){ - if (obj !== dst) { - forEach(obj, function(value, key){ - dst[key] = value; - }); - } - }); - return dst; -} - - -function inherit(parent, extra) { - return extend(new (extend(function(){}, {prototype:parent}))(), extra); -} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.noop - * @function - * - * @description - * Empty function that performs no operation whatsoever. This function is useful when writing code - * in the functional style. -
- function foo(callback) { - var result = calculateResult(); - (callback || angular.noop)(result); - } -- */ -function noop() {} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.identity - * @function - * - * @description - * A function that does nothing except for returning its first argument. This function is useful - * when writing code in the functional style. - * -
- function transformer(transformationFn, value) { - return (transformationFn || identity)(value); - }; -- */ -function identity($) {return $;} - - -function valueFn(value) {return function(){ return value; };} - -function extensionMap(angular, name, transform) { - var extPoint; - return angular[name] || (extPoint = angular[name] = function (name, fn, prop){ - name = (transform || identity)(name); - if (isDefined(fn)) { - extPoint[name] = extend(fn, prop || {}); - } - return extPoint[name]; - }); -} - -/** - * @workInProgress - * @ngdoc function - * @name angular.isUndefined - * @function - * - * @description - * Checks if a reference is undefined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is undefined. - */ -function isUndefined(value){ return typeof value == $undefined; } - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isDefined - * @function - * - * @description - * Checks if a reference is defined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is defined. - */ -function isDefined(value){ return typeof value != $undefined; } - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isObject - * @function - * - * @description - * Checks if a reference is an `Object`. Unlike in JavaScript `null`s are not considered to be - * objects. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Object` but not `null`. - */ -function isObject(value){ return value!=null && typeof value == $object;} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isString - * @function - * - * @description - * Checks if a reference is a `String`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `String`. - */ -function isString(value){ return typeof value == $string;} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isNumber - * @function - * - * @description - * Checks if a reference is a `Number`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Number`. - */ -function isNumber(value){ return typeof value == $number;} - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isDate - * @function - * - * @description - * Checks if value is a date. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Date`. - */ -function isDate(value){ return value instanceof Date; } - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isArray - * @function - * - * @description - * Checks if a reference is an `Array`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Array`. - */ -function isArray(value) { return value instanceof Array; } - - -/** - * @workInProgress - * @ngdoc function - * @name angular.isFunction - * @function - * - * @description - * Checks if a reference is a `Function`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Function`. - */ -function isFunction(value){ return typeof value == $function;} - - -/** - * Checks if `obj` is a window object. - * - * @private - * @param {*} obj Object to check - * @returns {boolean} True if `obj` is a window obj. - */ -function isWindow(obj) { - return obj && obj.document && obj.location && obj.alert && obj.setInterval; -} - -function isBoolean(value) { return typeof value == $boolean;} -function isTextNode(node) { return nodeName_(node) == '#text'; } -function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; } -function isElement(node) { - return node && - (node.nodeName // we are a direct element - || (node.bind && node.find)); // we have a bind and find method part of jQuery API -} - -/** - * @param str 'key1,key2,...' - * @returns {object} in the form of {key1:true, key2:true, ...} - */ -function makeMap(str){ - var obj = {}, items = str.split(","), i; - for ( i = 0; i < items.length; i++ ) - obj[ items[i] ] = true; - return obj; -} - - - -/** - * HTML class which is the only class which can be used in ng:bind to inline HTML for security reasons. - * @constructor - * @param html raw (unsafe) html - * @param {string=} option if set to 'usafe' then get method will return raw (unsafe/unsanitized) html - */ -function HTML(html, option) { - this.html = html; - this.get = lowercase(option) == 'unsafe' - ? valueFn(html) - : function htmlSanitize() { - var buf = []; - htmlParser(html, htmlSanitizeWriter(buf)); - return buf.join(''); - }; -} - -if (msie < 9) { - nodeName_ = function(element) { - element = element.nodeName ? element : element[0]; - return (element.scopeName && element.scopeName != 'HTML' ) ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; - }; -} else { - nodeName_ = function(element) { - return element.nodeName ? element.nodeName : element[0].nodeName; - }; -} - -function isVisible(element) { - var rect = element[0].getBoundingClientRect(), - width = (rect.width || (rect.right||0 - rect.left||0)), - height = (rect.height || (rect.bottom||0 - rect.top||0)); - return width>0 && height>0; -} - -function map(obj, iterator, context) { - var results = []; - forEach(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); - return results; -} - - -/** - * @ngdoc function - * @name angular.Object.size - * @function - * - * @description - * Determines the number of elements in an array, number of properties of an object or string - * length. - * - * Note: this function is used to augment the Object type in angular expressions. See - * {@link angular.Object} for more info. - * - * @param {Object|Array|string} obj Object, array or string to inspect. - * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object - * @returns {number} The size of `obj` or `0` if `obj` is neither an object or an array. - * - * @example - *
master={{master}}-
form={{form}}- *
greeting
object is
- NOT equal to
- {salutation:'Hello', name:'world'}
.
-
- greeting={{greeting}}- *
- * var view = angular.element('- * - * - if on the other hand, you need the element to be cloned, the view reference from the original - * example would not point to the clone, but rather to the original template that was cloned. In - * this case, you can access the clone via the cloneAttachFn: - *{{total}}
'), - * scope = angular.compile(view)(); - *
- * var original = angular.element('- * - * - * Compiler Methods For Widgets and Directives: - * - * The following methods are available for use when you write your own widgets, directives, - * and markup. (Recall that the compile function's this is a reference to the compiler.) - * - * `compile(element)` - returns linker - - * Invoke a new instance of the compiler to compile a DOM element and return a linker function. - * You can apply the linker function to the original element or a clone of the original element. - * The linker function returns a scope. - * - * * `comment(commentText)` - returns element - Create a comment element. - * - * * `element(elementName)` - returns element - Create an element by name. - * - * * `text(text)` - returns element - Create a text element. - * - * * `descend([set])` - returns descend state (true or false). Get or set the current descend - * state. If true the compiler will descend to children elements. - * - * * `directives([set])` - returns directive state (true or false). Get or set the current - * directives processing state. The compiler will process directives only when directives set to - * true. - * - * For information on how the compiler works, see the - * {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide. - */ -function Compiler(markup, attrMarkup, directives, widgets){ - this.markup = markup; - this.attrMarkup = attrMarkup; - this.directives = directives; - this.widgets = widgets; -} - -Compiler.prototype = { - compile: function(templateElement) { - templateElement = jqLite(templateElement); - var index = 0, - template, - parent = templateElement.parent(); - if (templateElement.length > 1) { - // https://github.com/angular/angular.js/issues/338 - throw Error("Cannot compile multiple element roots: " + - jqLite('{{total}}
'), - * scope = someParentScope.$new(), - * clone; - * - * angular.compile(original)(scope, function(clonedElement, scope) { - * clone = clonedElement; - * //attach the clone to DOM document at the right place - * }); - * - * //now we have reference to the cloned DOM via `clone` - *
QTY | -Description | -Cost | -Total | -- |
- | - | - | {{item.total = item.qty * item.cost | currency}} | -X | -
add | -{{ items.$sum('total') | currency }} | -
- var scope = angular.scope(); - var fn = scope.$bind(function(){ - return this; - }); - expect(fn()).toEqual(scope); -- * - * @param {function()} fn Function to be bound. - */ - $bind: bind(instance, bind, instance), - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$get - * @function - * - * @description - * Returns the value for `property_chain` on the current scope. Unlike in JavaScript, if there - * are any `undefined` intermediary properties, `undefined` is returned instead of throwing an - * exception. - * -
- var scope = angular.scope(); - expect(scope.$get('person.name')).toEqual(undefined); - scope.person = {}; - expect(scope.$get('person.name')).toEqual(undefined); - scope.person.name = 'misko'; - expect(scope.$get('person.name')).toEqual('misko'); -- * - * @param {string} property_chain String representing name of a scope property. Optionally - * properties can be chained with `.` (dot), e.g. `'person.name.first'` - * @returns {*} Value for the (nested) property. - */ - $get: bind(instance, getter, instance), - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$set - * @function - * - * @description - * Assigns a value to a property of the current scope specified via `property_chain`. Unlike in - * JavaScript, if there are any `undefined` intermediary properties, empty objects are created - * and assigned in to them instead of throwing an exception. - * -
- var scope = angular.scope(); - expect(scope.person).toEqual(undefined); - scope.$set('person.name', 'misko'); - expect(scope.person).toEqual({name:'misko'}); - expect(scope.person.name).toEqual('misko'); -- * - * @param {string} property_chain String representing name of a scope property. Optionally - * properties can be chained with `.` (dot), e.g. `'person.name.first'` - * @param {*} value Value to assign to the scope property. - */ - $set: bind(instance, setter, instance), - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$eval - * @function - * - * @description - * Without the `exp` parameter triggers an eval cycle for this scope and its child scopes. - * - * With the `exp` parameter, compiles the expression to a function and calls it with `this` set - * to the current scope and returns the result. In other words, evaluates `exp` as angular - * expression in the context of the current scope. - * - * # Example -
- var scope = angular.scope(); - scope.a = 1; - scope.b = 2; - - expect(scope.$eval('a+b')).toEqual(3); - expect(scope.$eval(function(){ return this.a + this.b; })).toEqual(3); - - scope.$onEval('sum = a+b'); - expect(scope.sum).toEqual(undefined); - scope.$eval(); - expect(scope.sum).toEqual(3); -- * - * @param {(string|function())=} exp An angular expression to be compiled to a function or a js - * function. - * - * @returns {*} The result of calling compiled `exp` with `this` set to the current scope. - */ - $eval: function(exp) { - var type = typeof exp; - var i, iSize; - var j, jSize; - var queue; - var fn; - if (type == $undefined) { - for ( i = 0, iSize = evalLists.sorted.length; i < iSize; i++) { - for ( queue = evalLists.sorted[i], - jSize = queue.length, - j= 0; j < jSize; j++) { - instance.$tryEval(queue[j].fn, queue[j].handler); - } - } - } else if (type === $function) { - return exp.call(instance); - } else if (type === 'string') { - return expressionCompile(exp).call(instance); - } - }, - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$tryEval - * @function - * - * @description - * Evaluates the expression in the context of the current scope just like - * {@link angular.scope.$eval} with expression parameter, but also wraps it in a try/catch - * block. - * - * If an exception is thrown then `exceptionHandler` is used to handle the exception. - * - * # Example -
- var scope = angular.scope(); - scope.error = function(){ throw 'myerror'; }; - scope.$exceptionHandler = function(e) {this.lastException = e; }; - - expect(scope.$eval('error()')); - expect(scope.lastException).toEqual('myerror'); - this.lastException = null; - - expect(scope.$eval('error()'), function(e) {this.lastException = e; }); - expect(scope.lastException).toEqual('myerror'); - - var body = angular.element(window.document.body); - expect(scope.$eval('error()'), body); - expect(body.attr('ng-exception')).toEqual('"myerror"'); - expect(body.hasClass('ng-exception')).toEqual(true); -- * - * @param {string|function()} expression Angular expression to evaluate. - * @param {(function()|DOMElement)=} exceptionHandler Function to be called or DOMElement to be - * decorated. - * @returns {*} The result of `expression` evaluation. - */ - $tryEval: function (expression, exceptionHandler) { - var type = typeof expression; - try { - if (type == $function) { - return expression.call(instance); - } else if (type == 'string'){ - return expressionCompile(expression).call(instance); - } - } catch (e) { - if ($log) $log.error(e); - if (isFunction(exceptionHandler)) { - exceptionHandler(e); - } else if (exceptionHandler) { - errorHandlerFor(exceptionHandler, e); - } else if (isFunction($exceptionHandler)) { - $exceptionHandler(e); - } - } - }, - - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$watch - * @function - * - * @description - * Registers `listener` as a callback to be executed every time the `watchExp` changes. Be aware - * that the callback gets, by default, called upon registration, this can be prevented via the - * `initRun` parameter. - * - * # Example -
- var scope = angular.scope(); - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$watch('name', 'counter = counter + 1'); - expect(scope.counter).toEqual(1); - - scope.$eval(); - expect(scope.counter).toEqual(1); - - scope.name = 'adam'; - scope.$eval(); - expect(scope.counter).toEqual(2); -- * - * @param {function()|string} watchExp Expression that should be evaluated and checked for - * change during each eval cycle. Can be an angular string expression or a function. - * @param {function()|string} listener Function (or angular string expression) that gets called - * every time the value of the `watchExp` changes. The function will be called with two - * parameters, `newValue` and `oldValue`. - * @param {(function()|DOMElement)=} [exceptionHanlder=angular.service.$exceptionHandler] Handler - * that gets called when `watchExp` or `listener` throws an exception. If a DOMElement is - * specified as handler, the element gets decorated by angular with the information about the - * exception. - * @param {boolean=} [initRun=true] Flag that prevents the first execution of the listener upon - * registration. - * - */ - $watch: function(watchExp, listener, exceptionHandler, initRun) { - var watch = expressionCompile(watchExp), - last = watch.call(instance); - listener = expressionCompile(listener); - function watcher(firstRun){ - var value = watch.call(instance), - // we have to save the value because listener can call ourselves => inf loop - lastValue = last; - if (firstRun || lastValue !== value) { - last = value; - instance.$tryEval(function(){ - return listener.call(instance, value, lastValue); - }, exceptionHandler); - } - } - instance.$onEval(PRIORITY_WATCH, watcher); - if (isUndefined(initRun)) initRun = true; - if (initRun) watcher(true); - }, - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$onEval - * @function - * - * @description - * Evaluates the `expr` expression in the context of the current scope during each - * {@link angular.scope.$eval eval cycle}. - * - * # Example -
- var scope = angular.scope(); - scope.counter = 0; - scope.$onEval('counter = counter + 1'); - expect(scope.counter).toEqual(0); - scope.$eval(); - expect(scope.counter).toEqual(1); -- * - * @param {number} [priority=0] Execution priority. Lower priority numbers get executed first. - * @param {string|function()} expr Angular expression or function to be executed. - * @param {(function()|DOMElement)=} [exceptionHandler=angular.service.$exceptionHandler] Handler - * function to call or DOM element to decorate when an exception occurs. - * - */ - $onEval: function(priority, expr, exceptionHandler){ - if (!isNumber(priority)) { - exceptionHandler = expr; - expr = priority; - priority = 0; - } - var evalList = evalLists[priority]; - if (!evalList) { - evalList = evalLists[priority] = []; - evalList.priority = priority; - evalLists.sorted.push(evalList); - evalLists.sorted.sort(function(a,b){return a.priority-b.priority;}); - } - evalList.push({ - fn: expressionCompile(expr), - handler: exceptionHandler - }); - }, - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$become - * @function - * @deprecated This method will be removed before 1.0 - * - * @description - * Modifies the scope to act like an instance of the given class by: - * - * - copying the class's prototype methods - * - applying the class's initialization function to the scope instance (without using the new - * operator) - * - * That makes the scope be a `this` for the given class's methods — effectively an instance of - * the given class with additional (scope) stuff. A scope can later `$become` another class. - * - * `$become` gets used to make the current scope act like an instance of a controller class. - * This allows for use of a controller class in two ways. - * - * - as an ordinary JavaScript class for standalone testing, instantiated using the new - * operator, with no attached view. - * - as a controller for an angular model stored in a scope, "instantiated" by - * `scope.$become(ControllerClass)`. - * - * Either way, the controller's methods refer to the model variables like `this.name`. When - * stored in a scope, the model supports data binding. When bound to a view, {{name}} in the - * HTML template refers to the same variable. - */ - $become: function(Class) { - if (isFunction(Class)) { - instance.constructor = Class; - forEach(Class.prototype, function(fn, name){ - instance[name] = bind(instance, fn); - }); - instance.$service.invoke(instance, Class, slice.call(arguments, 1, arguments.length)); - - //TODO: backwards compatibility hack, remove when we don't depend on init methods - if (isFunction(Class.prototype.init)) { - instance.init(); - } - } - }, - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$new - * @function - * - * @description - * Creates a new {@link angular.scope scope}, that: - * - * - is a child of the current scope - * - will {@link angular.scope.$become $become} of type specified via `constructor` - * - * @param {function()} constructor Constructor function of the type the new scope should assume. - * @returns {Object} The newly created child scope. - * - */ - $new: function(constructor) { - var child = createScope(instance); - child.$become.apply(instance, concat([constructor], arguments, 1)); - instance.$onEval(child.$eval); - return child; - } - - }); - - if (!parent.$root) { - instance.$root = instance; - instance.$parent = instance; - - /** - * @workInProgress - * @ngdoc function - * @name angular.scope.$service - * @function - * - * @description - * Provides access to angular's dependency injector and - * {@link angular.service registered services}. In general the use of this api is discouraged, - * except for tests and components that currently don't support dependency injection (widgets, - * filters, etc). - * - * @param {string} serviceId String ID of the service to return. - * @returns {*} Value, object or function returned by the service factory function if any. - */ - (instance.$service = createInjector(instance, providers, instanceCache)).eager(); - } - - $log = instance.$service('$log'); - $exceptionHandler = instance.$service('$exceptionHandler'); - - return instance; -} -/** - * @ngdoc function - * @name angular.injector - * @function - * - * @description - * Creates an injector function that can be used for retrieving services as well as for - * dependency injection (see {@link guide/dev_guide.di dependency injection}). - * - * Angular creates an injector automatically for the root scope and it is available as the - * {@link angular.scope.$service $service} property. Creation of the injector automatically creates - * all of the `$eager` {@link angular.service services}. - * - * @param {Object=} [factoryScope={}] `this` for the service factory function. - * @param {Object.
- * var MyController = angular.annotate('$location', function($location){ ... }); - *- * - * is the same as - * - *
- * var MyController = function($location){ ... }; - * MyController.$inject = ['$location']; - *- * - * @param {String|Array} serviceName... zero or more service names to inject into the - * `annotatedFunction`. - * @param {function} annotatedFunction function to annotate with `$inject` - * functions. - * @returns {function} `annotatedFunction` - */ -function annotate(services, fn) { - if (services instanceof Array) { - fn.$inject = services; - return fn; - } else { - var i = 0, - length = arguments.length - 1, // last one is the destination function - $inject = arguments[length].$inject = []; - for (; i < length; i++) { - $inject.push(arguments[i]); - } - return arguments[length]; // return the last one - } -} - -function angularServiceInject(name, fn, inject, eager) { - angularService(name, fn, {$inject:inject, $eager:eager}); -} - - -/** - * @returns the $inject property of function. If not found the - * the $inject is computed by looking at the toString of function and - * extracting all arguments which and assuming that they are the - * injection names. - */ -var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/m; -var FN_ARG_SPLIT = /,/; -var FN_ARG = /^\s*(.+?)\s*$/; -var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; -function injectionArgs(fn) { - assertArgFn(fn); - if (!fn.$inject) { - var args = fn.$inject = []; - var fnText = fn.toString().replace(STRIP_COMMENTS, ''); - var argDecl = fnText.match(FN_ARGS); - forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ - arg.replace(FN_ARG, function(all, name){ - args.push(name); - }); - }); - } - return fn.$inject; -} -var OPERATORS = { - 'null':function(self){return null;}, - 'true':function(self){return true;}, - 'false':function(self){return false;}, - $undefined:noop, - '+':function(self, a,b){return (isDefined(a)?a:0)+(isDefined(b)?b:0);}, - '-':function(self, a,b){return (isDefined(a)?a:0)-(isDefined(b)?b:0);}, - '*':function(self, a,b){return a*b;}, - '/':function(self, a,b){return a/b;}, - '%':function(self, a,b){return a%b;}, - '^':function(self, a,b){return a^b;}, - '=':noop, - '==':function(self, a,b){return a==b;}, - '!=':function(self, a,b){return a!=b;}, - '<':function(self, a,b){return a':function(self, a,b){return a>b;}, - '<=':function(self, a,b){return a<=b;}, - '>=':function(self, a,b){return a>=b;}, - '&&':function(self, a,b){return a&&b;}, - '||':function(self, a,b){return a||b;}, - '&':function(self, a,b){return a&b;}, -// '|':function(self, a,b){return a|b;}, - '|':function(self, a,b){return b(self, a);}, - '!':function(self, a){return !a;} -}; -var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; - -function lex(text, parseStringsForObjects){ - var dateParseLength = parseStringsForObjects ? DATE_ISOSTRING_LN : -1, - tokens = [], - token, - index = 0, - json = [], - ch, - lastCh = ':'; // can start regexp - - while (index < text.length) { - ch = text.charAt(index); - if (is('"\'')) { - readString(ch); - } else if (isNumber(ch) || is('.') && isNumber(peek())) { - readNumber(); - } else if (isIdent(ch)) { - readIdent(); - // identifiers can only be if the preceding char was a { or , - if (was('{,') && json[0]=='{' && - (token=tokens[tokens.length-1])) { - token.json = token.text.indexOf('.') == -1; - } - } else if (is('(){}[].,;:')) { - tokens.push({ - index:index, - text:ch, - json:(was(':[,') && is('{[')) || is('}]:,') - }); - if (is('{[')) json.unshift(ch); - if (is('}]')) json.shift(); - index++; - } else if (isWhitespace(ch)) { - index++; - continue; - } else { - var ch2 = ch + peek(), - fn = OPERATORS[ch], - fn2 = OPERATORS[ch2]; - if (fn2) { - tokens.push({index:index, text:ch2, fn:fn2}); - index += 2; - } else if (fn) { - tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')}); - index += 1; - } else { - throwError("Unexpected next character ", index, index+1); - } - } - lastCh = ch; - } - return tokens; - - function is(chars) { - return chars.indexOf(ch) != -1; - } - - function was(chars) { - return chars.indexOf(lastCh) != -1; - } - - function peek() { - return index + 1 < text.length ? text.charAt(index + 1) : false; - } - function isNumber(ch) { - return '0' <= ch && ch <= '9'; - } - function isWhitespace(ch) { - return ch == ' ' || ch == '\r' || ch == '\t' || - ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0 - } - function isIdent(ch) { - return 'a' <= ch && ch <= 'z' || - 'A' <= ch && ch <= 'Z' || - '_' == ch || ch == '$'; - } - function isExpOperator(ch) { - return ch == '-' || ch == '+' || isNumber(ch); - } - - function throwError(error, start, end) { - end = end || index; - throw Error("Lexer Error: " + error + " at column" + - (isDefined(start) - ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]" - : " " + end) + - " in expression [" + text + "]."); - } - - function readNumber() { - var number = ""; - var start = index; - while (index < text.length) { - var ch = lowercase(text.charAt(index)); - if (ch == '.' || isNumber(ch)) { - number += ch; - } else { - var peekCh = peek(); - if (ch == 'e' && isExpOperator(peekCh)) { - number += ch; - } else if (isExpOperator(ch) && - peekCh && isNumber(peekCh) && - number.charAt(number.length - 1) == 'e') { - number += ch; - } else if (isExpOperator(ch) && - (!peekCh || !isNumber(peekCh)) && - number.charAt(number.length - 1) == 'e') { - throwError('Invalid exponent'); - } else { - break; - } - } - index++; - } - number = 1 * number; - tokens.push({index:start, text:number, json:true, - fn:function(){return number;}}); - } - function readIdent() { - var ident = ""; - var start = index; - var fn; - while (index < text.length) { - var ch = text.charAt(index); - if (ch == '.' || isIdent(ch) || isNumber(ch)) { - ident += ch; - } else { - break; - } - index++; - } - fn = OPERATORS[ident]; - tokens.push({ - index:start, - text:ident, - json: fn, - fn:fn||extend(getterFn(ident), { - assign:function(self, value){ - return setter(self, ident, value); - } - }) - }); - } - - function readString(quote) { - var start = index; - index++; - var string = ""; - var rawString = quote; - var escape = false; - while (index < text.length) { - var ch = text.charAt(index); - rawString += ch; - if (escape) { - if (ch == 'u') { - var hex = text.substring(index + 1, index + 5); - if (!hex.match(/[\da-f]{4}/i)) - throwError( "Invalid unicode escape [\\u" + hex + "]"); - index += 4; - string += String.fromCharCode(parseInt(hex, 16)); - } else { - var rep = ESCAPE[ch]; - if (rep) { - string += rep; - } else { - string += ch; - } - } - escape = false; - } else if (ch == '\\') { - escape = true; - } else if (ch == quote) { - index++; - tokens.push({index:start, text:rawString, string:string, json:true, - fn:function(){ - return (string.length == dateParseLength) - ? angular['String']['toDate'](string) - : string; - }}); - return; - } else { - string += ch; - } - index++; - } - throwError("Unterminated quote", start); - } -} - -///////////////////////////////////////// - -function parser(text, json){ - var ZERO = valueFn(0), - tokens = lex(text, json), - assignment = _assignment, - assignable = logicalOR, - functionCall = _functionCall, - fieldAccess = _fieldAccess, - objectIndex = _objectIndex, - filterChain = _filterChain, - functionIdent = _functionIdent, - pipeFunction = _pipeFunction; - if(json){ - // The extra level of aliasing is here, just in case the lexer misses something, so that - // we prevent any accidental execution in JSON. - assignment = logicalOR; - functionCall = - fieldAccess = - objectIndex = - assignable = - filterChain = - functionIdent = - pipeFunction = - function (){ throwError("is not valid json", {text:text, index:0}); }; - } - //TODO: Shouldn't all of the public methods have assertAllConsumed? - //TODO: I think these should be public as part of the parser api instead of scope.$eval(). - return { - assignable: assertConsumed(assignable), - primary: assertConsumed(primary), - statements: assertConsumed(statements), - validator: assertConsumed(validator), - formatter: assertConsumed(formatter), - filter: assertConsumed(filter) - }; - - function assertConsumed(fn) { - return function(){ - var value = fn(); - if (tokens.length !== 0) { - throwError("is an unexpected token", tokens[0]); - } - return value; - }; - } - - /////////////////////////////////// - function throwError(msg, token) { - throw Error("Syntax Error: Token '" + token.text + - "' " + msg + " at column " + - (token.index + 1) + " of the expression [" + - text + "] starting at [" + text.substring(token.index) + "]."); - } - - function peekToken() { - if (tokens.length === 0) - throw Error("Unexpected end of expression: " + text); - return tokens[0]; - } - - function peek(e1, e2, e3, e4) { - if (tokens.length > 0) { - var token = tokens[0]; - var t = token.text; - if (t==e1 || t==e2 || t==e3 || t==e4 || - (!e1 && !e2 && !e3 && !e4)) { - return token; - } - } - return false; - } - - function expect(e1, e2, e3, e4){ - var token = peek(e1, e2, e3, e4); - if (token) { - if (json && !token.json) { - index = token.index; - throwError("is not valid json", token); - } - tokens.shift(); - this.currentToken = token; - return token; - } - return false; - } - - function consume(e1){ - if (!expect(e1)) { - throwError("is unexpected, expecting [" + e1 + "]", peek()); - } - } - - function unaryFn(fn, right) { - return function(self) { - return fn(self, right(self)); - }; - } - - function binaryFn(left, fn, right) { - return function(self) { - return fn(self, left(self), right(self)); - }; - } - - function hasTokens () { - return tokens.length > 0; - } - - function statements(){ - var statements = []; - while(true) { - if (tokens.length > 0 && !peek('}', ')', ';', ']')) - statements.push(filterChain()); - if (!expect(';')) { - // optimize for the common case where there is only one statement. - // TODO(size): maybe we should not support multiple statements? - return statements.length == 1 - ? statements[0] - : function (self){ - var value; - for ( var i = 0; i < statements.length; i++) { - var statement = statements[i]; - if (statement) - value = statement(self); - } - return value; - }; - } - } - } - - function _filterChain(){ - var left = expression(); - var token; - while(true) { - if ((token = expect('|'))) { - left = binaryFn(left, token.fn, filter()); - } else { - return left; - } - } - } - - function filter(){ - return pipeFunction(angularFilter); - } - - function validator(){ - return pipeFunction(angularValidator); - } - - function formatter(){ - var token = expect(); - var formatter = angularFormatter[token.text]; - var argFns = []; - if (!formatter) throwError('is not a valid formatter.', token); - while(true) { - if ((token = expect(':'))) { - argFns.push(expression()); - } else { - return valueFn({ - format:invokeFn(formatter.format), - parse:invokeFn(formatter.parse) - }); - } - } - function invokeFn(fn){ - return function(self, input){ - var args = [input]; - for ( var i = 0; i < argFns.length; i++) { - args.push(argFns[i](self)); - } - return fn.apply(self, args); - }; - } - } - - function _pipeFunction(fnScope){ - var fn = functionIdent(fnScope); - var argsFn = []; - var token; - while(true) { - if ((token = expect(':'))) { - argsFn.push(expression()); - } else { - var fnInvoke = function(self, input){ - var args = [input]; - for ( var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](self)); - } - return fn.apply(self, args); - }; - return function(){ - return fnInvoke; - }; - } - } - } - - function expression(){ - return assignment(); - } - - function _assignment(){ - var left = logicalOR(); - var right; - var token; - if (token = expect('=')) { - if (!left.assign) { - throwError("implies assignment but [" + - text.substring(0, token.index) + "] can not be assigned to", token); - } - right = logicalOR(); - return function(self){ - return left.assign(self, right(self)); - }; - } else { - return left; - } - } - - function logicalOR(){ - var left = logicalAND(); - var token; - while(true) { - if ((token = expect('||'))) { - left = binaryFn(left, token.fn, logicalAND()); - } else { - return left; - } - } - } - - function logicalAND(){ - var left = equality(); - var token; - if ((token = expect('&&'))) { - left = binaryFn(left, token.fn, logicalAND()); - } - return left; - } - - function equality(){ - var left = relational(); - var token; - if ((token = expect('==','!='))) { - left = binaryFn(left, token.fn, equality()); - } - return left; - } - - function relational(){ - var left = additive(); - var token; - if (token = expect('<', '>', '<=', '>=')) { - left = binaryFn(left, token.fn, relational()); - } - return left; - } - - function additive(){ - var left = multiplicative(); - var token; - while(token = expect('+','-')) { - left = binaryFn(left, token.fn, multiplicative()); - } - return left; - } - - function multiplicative(){ - var left = unary(); - var token; - while(token = expect('*','/','%')) { - left = binaryFn(left, token.fn, unary()); - } - return left; - } - - function unary(){ - var token; - if (expect('+')) { - return primary(); - } else if (token = expect('-')) { - return binaryFn(ZERO, token.fn, unary()); - } else if (token = expect('!')) { - return unaryFn(token.fn, unary()); - } else { - return primary(); - } - } - - function _functionIdent(fnScope) { - var token = expect(); - var element = token.text.split('.'); - var instance = fnScope; - var key; - for ( var i = 0; i < element.length; i++) { - key = element[i]; - if (instance) - instance = instance[key]; - } - if (typeof instance != $function) { - throwError("should be a function", token); - } - return instance; - } - - function primary() { - var primary; - if (expect('(')) { - var expression = filterChain(); - consume(')'); - primary = expression; - } else if (expect('[')) { - primary = arrayDeclaration(); - } else if (expect('{')) { - primary = object(); - } else { - var token = expect(); - primary = token.fn; - if (!primary) { - throwError("not a primary expression", token); - } - } - var next; - while (next = expect('(', '[', '.')) { - if (next.text === '(') { - primary = functionCall(primary); - } else if (next.text === '[') { - primary = objectIndex(primary); - } else if (next.text === '.') { - primary = fieldAccess(primary); - } else { - throwError("IMPOSSIBLE"); - } - } - return primary; - } - - function _fieldAccess(object) { - var field = expect().text; - var getter = getterFn(field); - return extend(function (self){ - return getter(object(self)); - }, { - assign:function(self, value){ - return setter(object(self), field, value); - } - }); - } - - function _objectIndex(obj) { - var indexFn = expression(); - consume(']'); - return extend( - function (self){ - var o = obj(self); - var i = indexFn(self); - return (o) ? o[i] : undefined; - }, { - assign:function(self, value){ - return obj(self)[indexFn(self)] = value; - } - }); - } - - function _functionCall(fn) { - var argsFn = []; - if (peekToken().text != ')') { - do { - argsFn.push(expression()); - } while (expect(',')); - } - consume(')'); - return function (self){ - var args = []; - for ( var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](self)); - } - var fnPtr = fn(self) || noop; - // IE stupidity! - return fnPtr.apply - ? fnPtr.apply(self, args) - : fnPtr(args[0], args[1], args[2], args[3], args[4]); - }; - } - - // This is used with json array declaration - function arrayDeclaration () { - var elementFns = []; - if (peekToken().text != ']') { - do { - elementFns.push(expression()); - } while (expect(',')); - } - consume(']'); - return function (self){ - var array = []; - for ( var i = 0; i < elementFns.length; i++) { - array.push(elementFns[i](self)); - } - return array; - }; - } - - function object () { - var keyValues = []; - if (peekToken().text != '}') { - do { - var token = expect(), - key = token.string || token.text; - consume(":"); - var value = expression(); - keyValues.push({key:key, value:value}); - } while (expect(',')); - } - consume('}'); - return function (self){ - var object = {}; - for ( var i = 0; i < keyValues.length; i++) { - var keyValue = keyValues[i]; - var value = keyValue.value(self); - object[keyValue.key] = value; - } - return object; - }; - } - - function watchDecl () { - var anchorName = expect().text; - consume(":"); - var expressionFn; - if (peekToken().text == '{') { - consume("{"); - expressionFn = statements(); - consume("}"); - } else { - expressionFn = expression(); - } - return function(self) { - return {name:anchorName, fn:expressionFn}; - }; - } -} - - - - - -function Route(template, defaults) { - this.template = template = template + '#'; - this.defaults = defaults || {}; - var urlParams = this.urlParams = {}; - forEach(template.split(/\W/), function(param){ - if (param && template.match(new RegExp(":" + param + "\\W"))) { - urlParams[param] = true; - } - }); -} - -Route.prototype = { - url: function(params) { - var self = this, - url = this.template, - encodedVal; - - params = params || {}; - forEach(this.urlParams, function(_, urlParam){ - encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || ""); - url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1"); - }); - url = url.replace(/\/?#$/, ''); - var query = []; - forEachSorted(params, function(value, key){ - if (!self.urlParams[key]) { - query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); - } - }); - url = url.replace(/\/*$/, ''); - return url + (query.length ? '?' + query.join('&') : ''); - } -}; - -function ResourceFactory(xhr) { - this.xhr = xhr; -} - -ResourceFactory.DEFAULT_ACTIONS = { - 'get': {method:'GET'}, - 'save': {method:'POST'}, - 'query': {method:'GET', isArray:true}, - 'remove': {method:'DELETE'}, - 'delete': {method:'DELETE'} -}; - -ResourceFactory.prototype = { - route: function(url, paramDefaults, actions){ - var self = this; - var route = new Route(url); - actions = extend({}, ResourceFactory.DEFAULT_ACTIONS, actions); - function extractParams(data){ - var ids = {}; - forEach(paramDefaults || {}, function(value, key){ - ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; - }); - return ids; - } - - function Resource(value){ - copy(value || {}, this); - } - - forEach(actions, function(action, name){ - var isPostOrPut = action.method == 'POST' || action.method == 'PUT'; - Resource[name] = function (a1, a2, a3) { - var params = {}; - var data; - var callback = noop; - switch(arguments.length) { - case 3: callback = a3; - case 2: - if (isFunction(a2)) { - callback = a2; - //fallthrough - } else { - params = a1; - data = a2; - break; - } - case 1: - if (isFunction(a1)) callback = a1; - else if (isPostOrPut) data = a1; - else params = a1; - break; - case 0: break; - default: - throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments."; - } - - var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); - self.xhr( - action.method, - route.url(extend({}, action.params || {}, extractParams(data), params)), - data, - function(status, response, clear) { - if (200 <= status && status < 300) { - if (response) { - if (action.isArray) { - value.length = 0; - forEach(response, function(item){ - value.push(new Resource(item)); - }); - } else { - copy(response, value); - } - } - (callback||noop)(value); - } else { - throw {status: status, response:response, message: status + ": " + response}; - } - }, - action.verifyCache); - return value; - }; - - Resource.bind = function(additionalParamDefaults){ - return self.route(url, extend({}, paramDefaults, additionalParamDefaults), actions); - }; - - Resource.prototype['$' + name] = function(a1, a2){ - var params = extractParams(this); - var callback = noop; - switch(arguments.length) { - case 2: params = a1; callback = a2; - case 1: if (typeof a1 == $function) callback = a1; else params = a1; - case 0: break; - default: - throw "Expected between 1-2 arguments [params, callback], got " + arguments.length + " arguments."; - } - var data = isPostOrPut ? this : undefined; - Resource[name].call(this, params, data, callback); - }; - }); - return Resource; - } -}; -////////////////////////////// -// Browser -////////////////////////////// -var XHR = window.XMLHttpRequest || function () { - try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {} - try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {} - try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {} - throw new Error("This browser does not support XMLHttpRequest."); -}; - - -/** - * @private - * @name Browser - * - * @description - * Constructor for the object exposed as $browser service. - * - * This object has two goals: - * - * - hide all the global state in the browser caused by the window object - * - abstract away all the browser specific features and inconsistencies - * - * @param {object} window The global window object. - * @param {object} document jQuery wrapped document. - * @param {object} body jQuery wrapped document.body. - * @param {function()} XHR XMLHttpRequest constructor. - * @param {object} $log console.log or an object with the same interface. - */ -function Browser(window, document, body, XHR, $log) { - var self = this, - rawDocument = document[0], - location = window.location, - setTimeout = window.setTimeout; - - self.isMock = false; - - ////////////////////////////////////////////////////////////// - // XHR API - ////////////////////////////////////////////////////////////// - var idCounter = 0; - var outstandingRequestCount = 0; - var outstandingRequestCallbacks = []; - - - /** - * Executes the `fn` function (supports currying) and decrements the `outstandingRequestCallbacks` - * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. - */ - function completeOutstandingRequest(fn) { - try { - fn.apply(null, slice.call(arguments, 1)); - } finally { - outstandingRequestCount--; - if (outstandingRequestCount === 0) { - while(outstandingRequestCallbacks.length) { - try { - outstandingRequestCallbacks.pop()(); - } catch (e) { - $log.error(e); - } - } - } - } - } - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$browser#xhr - * @methodOf angular.service.$browser - * - * @param {string} method Requested method (get|post|put|delete|head|json) - * @param {string} url Requested url - * @param {?string} post Post data to send (null if nothing to post) - * @param {function(number, string)} callback Function that will be called on response - * @param {object=} header additional HTTP headers to send with XHR. - * Standard headers are: - *
Qty | Description | Cost | Total | |
---|---|---|---|---|
- | - | - | {{item.qty * item.cost | currency}} | -[X] | -
add item | -- | Total: | -{{invoice.items.$sum('qty*cost') | currency}} | -
Name | Phone |
---|---|
{{friend.name}} | -{{friend.phone}} | -
Name | Phone |
---|---|
{{friend.name}} | -{{friend.phone}} | -
people = {{people}}-
Number of items which have one point: {{ items.$count('points==1') }}
-Number of items which have more than one point: {{items.$count('points>1')}}
-Sorting predicate = {{predicate}}; reverse = {{reverse}}-
Name - (^) | -Phone Number | -Age | -
---|---|---|
{{friend.name}} | -{{friend.phone}} | -{{friend.age}} | -
Output: {{ numbers.$limitTo(limit) | json }}
-{{ obj | json }}-
Filter | -Source | -Rendered | -
html filter | -
- <div ng:bind="snippet | html">- |
- - - | -
no filter | -<div ng:bind="snippet"> |
- - |
unsafe html filter | -<div ng:bind="snippet | html:'unsafe'"> |
- - |
an html\nclick here\nsnippet
'); - }); - - it('should escape snippet without any filter', function() { - expect(using('#escaped-html').binding('snippet')). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); - - it('should inline raw snippet if filtered as unsafe', function() { - expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")). - toBe("an html\n" + - "click here\n" + - "snippet
"); - }); - - it('should update', function(){ - input('snippet').enter('new text'); - expect(using('#html-filter').binding('snippet | html')).toBe('new text'); - expect(using('#escaped-html').binding('snippet')).toBe("new <b>text</b>"); - expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new text'); - }); -Filter | -Source | -Rendered | -
linky filter | -
- <div ng:bind="snippet | linky">- |
- - - | -
no filter | -<div ng:bind="snippet"> |
- - |
data={{data}}-
value={{value}}-
value={{value}}-
value={{value}}-
value={{value|json}}-
- $location.update('http://www.angularjs.org/path#hash?search=x'); - $location.update({host: 'www.google.com', protocol: 'https'}); - $location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}}); -- * - * @param {string|Object} href Full href as a string or object with properties - */ - function update(href) { - if (isString(href)) { - extend(location, parseHref(href)); - } else { - if (isDefined(href.hash)) { - extend(href, isString(href.hash) ? parseHash(href.hash) : href.hash); - } - - extend(location, href); - - if (isDefined(href.hashPath || href.hashSearch)) { - location.hash = composeHash(location); - } - - location.href = composeHref(location); - } - } - - /** - * @workInProgress - * @ngdoc method - * @name angular.service.$location#updateHash - * @methodOf angular.service.$location - * - * @description - * Updates the hash fragment part of the url. - * - * @see update() - * - *
- scope.$location.updateHash('/hp') - ==> update({hashPath: '/hp'}) - scope.$location.updateHash({a: true, b: 'val'}) - ==> update({hashSearch: {a: true, b: 'val'}}) - scope.$location.updateHash('/hp', {a: true}) - ==> update({hashPath: '/hp', hashSearch: {a: true}}) -- * - * @param {string|Object} path A hashPath or hashSearch object - * @param {Object=} search A hashSearch object - */ - function updateHash(path, search) { - var hash = {}; - - if (isString(path)) { - hash.hashPath = path; - hash.hashSearch = search || {}; - } else - hash.hashSearch = path; - - hash.hash = composeHash(hash); - - update({hash: hash}); - } - - - // INNER METHODS - - /** - * Synchronizes all location object properties. - * - * User is allowed to change properties, so after property change, - * location object is not in consistent state. - * - * Properties are synced with the following precedence order: - * - * - `$location.href` - * - `$location.hash` - * - everything else - * - * Keep in mind that if the following code is executed: - * - * scope.$location.href = 'http://www.angularjs.org/path#a/b' - * - * immediately afterwards all other properties are still the old ones... - * - * This method checks the changes and update location to the consistent state - */ - function sync() { - if (!equals(location, lastLocation)) { - if (location.href != lastLocation.href) { - update(location.href); - return; - } - if (location.hash != lastLocation.hash) { - var hash = parseHash(location.hash); - updateHash(hash.hashPath, hash.hashSearch); - } else { - location.hash = composeHash(location); - location.href = composeHref(location); - } - update(location.href); - } - } - - - /** - * If location has changed, update the browser - * This method is called at the end of $eval() phase - */ - function updateBrowser() { - sync(); - - if ($browser.getUrl() != location.href) { - $browser.setUrl(location.href); - copy(location, lastLocation); - } - } - - /** - * Compose href string from a location object - * - * @param {Object} loc The location object with all properties - * @return {string} Composed href - */ - function composeHref(loc) { - var url = toKeyValue(loc.search); - var port = (loc.port == DEFAULT_PORTS[loc.protocol] ? null : loc.port); - - return loc.protocol + '://' + loc.host + - (port ? ':' + port : '') + loc.path + - (url ? '?' + url : '') + (loc.hash ? '#' + loc.hash : ''); - } - - /** - * Compose hash string from location object - * - * @param {Object} loc Object with hashPath and hashSearch properties - * @return {string} Hash string - */ - function composeHash(loc) { - var hashSearch = toKeyValue(loc.hashSearch); - //TODO: temporary fix for issue #158 - return escape(loc.hashPath).replace(/%21/gi, '!').replace(/%3A/gi, ':').replace(/%24/gi, '$') + - (hashSearch ? '?' + hashSearch : ''); - } - - /** - * Parse href string into location object - * - * @param {string} href - * @return {Object} The location object - */ - function parseHref(href) { - var loc = {}; - var match = URL_MATCH.exec(href); - - if (match) { - loc.href = href.replace(/#$/, ''); - loc.protocol = match[1]; - loc.host = match[3] || ''; - loc.port = match[5] || DEFAULT_PORTS[loc.protocol] || null; - loc.path = match[6] || ''; - loc.search = parseKeyValue(match[8]); - loc.hash = match[10] || ''; - - extend(loc, parseHash(loc.hash)); - } - - return loc; - } - - /** - * Parse hash string into object - * - * @param {string} hash - */ - function parseHash(hash) { - var h = {}; - var match = HASH_MATCH.exec(hash); - - if (match) { - h.hash = hash; - h.hashPath = unescape(match[1] || ''); - h.hashSearch = parseKeyValue(match[3]); - } - - return h; - } -}, ['$browser']); -/** - * @workInProgress - * @ngdoc service - * @name angular.service.$log - * @requires $window - * - * @description - * Simple service for logging. Default implementation writes the message - * into the browser's console (if present). - * - * The main purpose of this service is to simplify debugging and troubleshooting. - * - * @example -
Reload this page with open console, enter text and hit the log button...
- Message: - - - - - -o.priority)&&o.restrict.indexOf(g)!=-1)d.push(o),h=!0}catch(n){k(n)}return h}function $(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b, +function(b,g){g=="class"?(M(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):g=="style"?e.attr("style",e.attr("style")+";"+b):g.charAt(0)!="$"&&!a.hasOwnProperty(g)&&(a[g]=b,d[g]=c[g])})}function R(a,b,c,d,e,j,h){var i=[],k,o,p=c[0],t=a.shift(),s=v({},t,{controller:null,templateUrl:null,transclude:null,scope:null});c.html("");l.get(t.templateUrl,{cache:n}).success(function(l){var n,t,l=Fb(l);if(j){t=u("
+ * 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() { + + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } + + + 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)); } + }; + + /** + * @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#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#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#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 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'], 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; + *+ * + */ + 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.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 = ['getMilliseconds', '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; +})(); + + +/** + * @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 { + 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 | + *
+ // controller + function MyController($scope, $http) { + $http.get('/auth.py').success(function(data) { + $scope.user = data; + }); + + this.saveMessage = function(message) { + $scope.status = 'Saving...'; + $http.post('/add-msg.py', message).success(function(response) { + $scope.status = ''; + }).error(function() { + $scope.status = 'ERROR!'; + }); + }; + } + + // testing controller + var $httpBackend; + + beforeEach(inject(function($injector) { + $httpBackend = $injector.get('$httpBackend'); + + // backend definition common for all tests + $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'}); + })); + + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + + it('should fetch authentication token', function() { + $httpBackend.expectGET('/auth.py'); + var controller = scope.$new(MyController); + $httpBackend.flush(); + }); + + + it('should send msg to server', function() { + // 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, ''); + + var controller = scope.$new(MyController); + $httpBackend.flush(); + controller.saveMessage('message content'); + expect(controller.status).toBe('Saving...'); + $httpBackend.flush(); + expect(controller.status).toBe(''); + }); + + + it('should send auth header', function() { + $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, ''); + + var controller = scope.$new(MyController); + controller.saveMessage('whatever'); + $httpBackend.flush(); + }); ++ */ +angular.mock.$HttpBackendProvider = function() { + this.$get = [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($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) { + 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); + } + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) + throw Error('Expected ' + expectation + ' with different data\n' + + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); + + if (!expectation.matchHeaders(headers)) + throw Error('Expected ' + expectation + ' with different headers\n' + + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + + prettyPrint(headers)); + + expectations.shift(); + + if (expectation.response) { + responses.push(function() { + var response = expectation.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(response[0], response[1], xhr.getAllResponseHeaders()); + }); + 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)(function() { + var response = definition.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(response[0], response[1], xhr.getAllResponseHeaders()); + }); + } else if (definition.passThrough) { + $delegate(method, url, data, callback, headers); + } else throw Error('No response defined !'); + return; + } + } + throw wasExpected ? + Error('No response defined !') : + 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)=} 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` 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)=} data HTTP request body. + * @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)=} data HTTP request body. + * @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)=} 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 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)=} data HTTP request body. + * @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)=} data HTTP request body. + * @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)=} data HTTP request body. + * @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) { + 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.verifyExpectations); + *+ */ + $httpBackend.verifyNoOutstandingExpectation = function() { + if (expectations.length) { + throw 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.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" method. + */ + +/** + * @ngdoc method + * @name ngMock.$timeout#flush + * @methodOf ngMock.$timeout + * @description + * + * Flushes the queue of pending tasks. + */ + +/** + * + */ +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', function($delegate, $browser) { + $delegate.flush = function() { + $browser.defer.flush(); + }; + return $delegate; + }); +}); + + +/** + * @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 = ['$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).unbind(); + delete cache[key]; + } + } +}; + + +window.jstestdriver && (function(window) { + /** + * Global method to output any number of objects into JSTD console. Useful for debugging. + */ + window.dump = function() { + var args = []; + angular.forEach(arguments, function(arg) { + args.push(angular.mock.dump(arg)); + }); + jstestdriver.console.log.apply(jstestdriver.console, args); + if (window.console) { + window.console.log.apply(window.console, args); + } + }; +})(window); + + +window.jasmine && (function(window) { + + afterEach(function() { + var spec = getCurrentSpec(); + var injector = spec.$injector; + + spec.$injector = null; + spec.$modules = null; + + if (injector) { + injector.get('$rootElement').unbind(); + 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 getCurrentSpec() { + return jasmine.getEnv().currentSpec; + } + + function isSpecRunning() { + var spec = getCurrentSpec(); + return spec && spec.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 spec = getCurrentSpec(); + var modules = spec.$modules || []; + modules.unshift('ngMock'); + modules.unshift('ng'); + var injector = spec.$injector; + if (!injector) { + injector = spec.$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); diff --git a/src/test/js/lib/angular-mocks.js b/src/test/js/lib/angular-mocks.js deleted file mode 100644 index 0b5c35b..0000000 --- a/src/test/js/lib/angular-mocks.js +++ /dev/null @@ -1,418 +0,0 @@ -/** - * The MIT License - * - * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - - -/* - - NUGGGGGH MUST TONGUE WANGS - \ - ..... - C C / - /< / - ___ __________/_#__=o - /(- /(\_\________ \ - \ ) \ )_ \o \ - /|\ /|\ |' | - | _| - /o __\ - / ' | - / / | - /_/\______| - ( _( < - \ \ \ - \ \ | - \____\____\ - ____\_\__\_\ - /` /` o\ - |___ |_______|.. . b'ger - - - IN THE FINAL BUILD THIS FILE DOESN'T HAVE DIRECT ACCESS TO GLOBAL FUNCTIONS - DEFINED IN Angular.js YOU *MUST* REFER TO THEM VIA angular OBJECT - (e.g. angular.forEach(...)) AND MAKE SURE THAT THE GIVEN FUNCTION IS EXPORTED - TO THE angular NAMESPACE in AngularPublic.js - - */ - - -/** - * @workInProgress - * @ngdoc overview - * @name angular.mock - * @description - * - * The `angular.mock` object is a namespace for all built-in mock services that ship with angular. - * It automatically replaces real services if the `angular-mocks.js` file is loaded after - * `angular.js` and before any tests. - * - * Built-in mocks: - * - * * {@link angular.mock.service.$browser $browser } - A mock implementation of the browser. - * * {@link angular.mock.service.$exceptionHandler $exceptionHandler } - A mock implementation of the - * angular service exception handler. - * * {@link angular.mock.service.$log $log } - A mock implementation of the angular service log. - */ -angular.mock = {}; - - -/** - * @workInProgress - * @ngdoc service - * @name angular.mock.service.$browser - */ -function MockBrowser() { - var self = this, - expectations = {}, - requests = []; - - this.isMock = true; - self.url = "http://server"; - self.lastUrl = self.url; // used by url polling fn - self.pollFns = []; - - - // register url polling fn - - self.onHashChange = function(listener) { - self.pollFns.push( - function() { - if (self.lastUrl != self.url) { - self.lastUrl = self.url; - listener(); - } - } - ); - - return listener; - }; - - - self.xhr = function(method, url, data, callback, headers) { - headers = headers || {}; - if (data && angular.isObject(data)) data = angular.toJson(data); - if (data && angular.isString(data)) url += "|" + data; - var expect = expectations[method] || {}; - var expectation = expect[url]; - if (!expectation) { - throw new Error("Unexpected request for method '" + method + "' and url '" + url + "'."); - } - requests.push(function(){ - angular.forEach(expectation.headers, function(value, key){ - if (headers[key] !== value) { - throw new Error("Missing HTTP request header: " + key + ": " + value); - } - }); - callback(expectation.code, expectation.response); - }); - }; - self.xhr.expectations = expectations; - self.xhr.requests = requests; - self.xhr.expect = function(method, url, data, headers) { - if (data && angular.isObject(data)) data = angular.toJson(data); - if (data && angular.isString(data)) url += "|" + data; - var expect = expectations[method] || (expectations[method] = {}); - return { - respond: function(code, response) { - if (!angular.isNumber(code)) { - response = code; - code = 200; - } - expect[url] = {code:code, response:response, headers: headers || {}}; - } - }; - }; - self.xhr.expectGET = angular.bind(self, self.xhr.expect, 'GET'); - self.xhr.expectPOST = angular.bind(self, self.xhr.expect, 'POST'); - self.xhr.expectDELETE = angular.bind(self, self.xhr.expect, 'DELETE'); - self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT'); - self.xhr.expectJSON = angular.bind(self, self.xhr.expect, 'JSON'); - self.xhr.flush = function() { - if (requests.length == 0) { - throw new Error("No xhr requests to be flushed!"); - } - - while(requests.length) { - requests.pop()(); - } - }; - - self.cookieHash = {}; - self.lastCookieHash = {}; - self.deferredFns = []; - - self.defer = function(fn, delay) { - delay = delay || 0; - self.deferredFns.push({time:(self.defer.now + delay), fn:fn}); - self.deferredFns.sort(function(a,b){ return a.time - b.time;}); - }; - - self.defer.now = 0; - - 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; - } - } - - while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { - self.deferredFns.shift().fn(); - } - }; -} -MockBrowser.prototype = { - - poll: function poll(){ - angular.forEach(this.pollFns, function(pollFn){ - pollFn(); - }); - }, - - addPollFn: function(pollFn) { - this.pollFns.push(pollFn); - return pollFn; - }, - - hover: function(onHover) { - }, - - getUrl: function(){ - return this.url; - }, - - setUrl: function(url){ - this.url = 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; - } - }, - - addJs: function(){} -}; - -angular.service('$browser', function(){ - return new MockBrowser(); -}); - - -/** - * @workInProgress - * @ngdoc service - * @name angular.mock.service.$exceptionHandler - * - * @description - * Mock implementation of {@link angular.service.$exceptionHandler} that rethrows any error passed - * into `$exceptionHandler`. If any errors are 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. - * - * See {@link angular.mock} for more info on angular mocks. - */ -angular.service('$exceptionHandler', function() { - return function(e) { throw e;}; -}); - - -/** - * @workInProgress - * @ngdoc service - * @name angular.mock.service.$log - * - * @description - * Mock implementation of {@link angular.service.$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`. - * - * See {@link angular.mock} for more info on angular mocks. - */ -angular.service('$log', MockLogFactory); - -function MockLogFactory() { - var $log = { - log: function(){ $log.log.logs.push(arguments); }, - warn: function(){ $log.warn.logs.push(arguments); }, - info: function(){ $log.info.logs.push(arguments); }, - error: function(){ $log.error.logs.push(arguments); } - }; - - $log.log.logs = []; - $log.warn.logs = []; - $log.info.logs = []; - $log.error.logs = []; - - return $log; -} - - -/** - * Mock of the Date type which has its timezone specified via constroctor 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; - *- * - */ -function TzDate(offset, timestamp) { - if (angular.isString(timestamp)) { - var tsStr = timestamp; - - this.origDate = angular.String.toDate(timestamp); - - timestamp = this.origDate.getTime(); - if (isNaN(timestamp)) - throw { - name: "Illegal Argument", - message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" - }; - } else { - this.origDate = new Date(timestamp); - } - - var localOffset = new Date(timestamp).getTimezoneOffset(); - this.offsetDiff = localOffset*60*1000 - offset*1000*60*60; - this.date = new Date(timestamp + this.offsetDiff); - - this.getTime = function() { - return this.date.getTime() - this.offsetDiff; - }; - - this.toLocaleDateString = function() { - return this.date.toLocaleDateString(); - }; - - this.getFullYear = function() { - return this.date.getFullYear(); - }; - - this.getMonth = function() { - return this.date.getMonth(); - }; - - this.getDate = function() { - return this.date.getDate(); - }; - - this.getHours = function() { - return this.date.getHours(); - }; - - this.getMinutes = function() { - return this.date.getMinutes(); - }; - - this.getSeconds = function() { - return this.date.getSeconds(); - }; - - this.getTimezoneOffset = function() { - return offset * 60; - }; - - this.getUTCFullYear = function() { - return this.origDate.getUTCFullYear(); - }; - - this.getUTCMonth = function() { - return this.origDate.getUTCMonth(); - }; - - this.getUTCDate = function() { - return this.origDate.getUTCDate(); - }; - - this.getUTCHours = function() { - return this.origDate.getUTCHours(); - }; - - this.getUTCMinutes = function() { - return this.origDate.getUTCMinutes(); - }; - - this.getUTCSeconds = function() { - return this.origDate.getUTCSeconds(); - }; - - this.getDay = function() { - return this.origDate.getDay(); - }; - - //hide all methods not implemented in this mock that the Date prototype exposes - var unimplementedMethods = ['getMilliseconds', 'getTime', 'getUTCDay', - 'getUTCMilliseconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', - 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', - 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', - 'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString', - 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; - - angular.forEach(unimplementedMethods, function(methodName) { - this[methodName] = function() { - throw { - name: "MethodNotImplemented", - message: "Method '" + methodName + "' is not implemented in the TzDate mock" - }; - }; - }); -} - -//make "tzDateInstance instanceof Date" return true -TzDate.prototype = Date.prototype; diff --git a/src/test/js/spec/controllers-spec.js b/src/test/js/spec/controllers-spec.js index 1dea1b0..34e369a 100644 --- a/src/test/js/spec/controllers-spec.js +++ b/src/test/js/spec/controllers-spec.js @@ -1,21 +1,23 @@ describe('PianoCtrl', function() { - var scope, controller; + var scope, ctrl, $httpBackend; beforeEach(function(){ this.addMatchers({ toBeGreaterThanOrEqualTo: function(expected) { return this.actual >= expected; } }); - scope = angular.scope(); - controller = scope.$new(PianoCtrl); + inject(function(_$httpBackend_, $rootScope, $controller) { + $httpBackend = _$httpBackend_; + scope = $rootScope.$new(); + controller = $controller(PianoCtrl, {$scope: scope}); + }); }); it('should measure latency on each send', function() { - var xhr = scope.$service('$browser').xhr; - var data = {channel:0, command:144, note:77, velocity:60}; - xhr.expectPOST('/data/midi/send', data).respond(data); - controller.latency = -1; - controller.sendMidiCommand(controller.NOTE_ON, 77); - xhr.flush(); - expect(controller.latency).toBeGreaterThanOrEqualTo(0); + var data = {command:144, channel:0, note:77, velocity:60}; + $httpBackend.expectPOST('data/midi/send', data).respond(data); + scope.latency = -1; + scope.sendMidiCommand(scope.NOTE_ON, 77); + $httpBackend.flush(); + expect(scope.latency).toBeGreaterThanOrEqualTo(0); }); }) \ No newline at end of file