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('{{total}}
'),
- * scope = angular.compile(view)();
- *
- *
- * - 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:
- *
- * var original = angular.element('{{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`
- *
- *
- *
- * 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('| 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