diff options
51 files changed, 3951 insertions, 3818 deletions
| diff --git a/angularFiles.js b/angularFiles.js index 0a85a8ba..09b21977 100644 --- a/angularFiles.js +++ b/angularFiles.js @@ -38,12 +38,29 @@ angularFiles = {      'src/service/http.js',      'src/service/httpBackend.js',      'src/service/locale.js', -    'src/directives.js', -    'src/markups.js', -    'src/widgets.js', -    'src/widget/form.js', -    'src/widget/input.js', -    'src/widget/select.js' +    'src/directive/directives.js', +    'src/directive/a.js', +    'src/directive/booleanAttrDirs.js', +    'src/directive/form.js', +    'src/directive/input.js', +    'src/directive/ngBind.js', +    'src/directive/ngClass.js', +    'src/directive/ngCloak.js', +    'src/directive/ngController.js', +    'src/directive/ngEventDirs.js', +    'src/directive/ngInclude.js', +    'src/directive/ngInit.js', +    'src/directive/ngNonBindable.js', +    'src/directive/ngPluralize.js', +    'src/directive/ngRepeat.js', +    'src/directive/ngShowHide.js', +    'src/directive/ngStyle.js', +    'src/directive/ngSwitch.js', +    'src/directive/ngTransclude.js', +    'src/directive/ngView.js', +    'src/directive/script.js', +    'src/directive/select.js', +    'src/directive/style.js'    ],    'angularScenario': [ @@ -83,7 +100,7 @@ angularFiles = {      'test/*.js',      'test/service/*.js',      'test/service/filter/*.js', -    'test/widget/*.js', +    'test/directive/*.js',      'example/personalLog/test/*.js'    ], @@ -143,7 +160,7 @@ angularFiles = {      'test/jstd-scenario-adapter/*.js',      'test/*.js',      'test/service/*.js', -    'test/widget/*.js', +    'test/directive/*.js',      'example/personalLog/test/*.js'    ], diff --git a/src/directive/a.js b/src/directive/a.js new file mode 100644 index 00000000..bd56f3be --- /dev/null +++ b/src/directive/a.js @@ -0,0 +1,29 @@ +'use strict'; + +/* + * Modifies the default behavior of html A tag, so that the default action is prevented when href + * attribute is empty. + * + * The reasoning for this change is to allow easy creation of action links with ng:click without + * changing the location or causing page reloads, e.g.: + * <a href="" ng:click="model.$save()">Save</a> + */ +var htmlAnchorDirective = valueFn({ +  restrict: 'E', +  compile: function(element, attr) { +    // turn <a href ng:click="..">link</a> into a link in IE +    // but only if it doesn't have name attribute, in which case it's an anchor +    if (!attr.href) { +      attr.$set('href', ''); +    } + +    return function(scope, element) { +      element.bind('click', function(event){ +        // if we have no href url, then don't navigate anywhere. +        if (!element.attr('href')) { +          event.preventDefault(); +        } +      }); +    } +  } +}); diff --git a/src/markups.js b/src/directive/booleanAttrDirs.js index 36b03131..06b85823 100644 --- a/src/markups.js +++ b/src/directive/booleanAttrDirs.js @@ -1,6 +1,5 @@  'use strict'; -  /**   * @ngdoc directive   * @name angular.module.ng.$compileProvider.directive.ng:href @@ -267,3 +266,17 @@  * @param {template} template any string which can contain '{{}}' markup.  */ + +function ngAttributeAliasDirective(propName, attrName) { +  ngAttributeAliasDirectives[directiveNormalize('ng-' + attrName)] = valueFn( +    function(scope, element, attr) { +      attr.$observe(directiveNormalize('ng-' + attrName), function(value) { +        attr.$set(attrName, value); +      }); +    } +  ); +} + +var ngAttributeAliasDirectives = {}; +forEach(BOOLEAN_ATTR, ngAttributeAliasDirective); +ngAttributeAliasDirective(null, 'src'); diff --git a/src/directive/directives.js b/src/directive/directives.js new file mode 100644 index 00000000..123645f9 --- /dev/null +++ b/src/directive/directives.js @@ -0,0 +1,11 @@ +'use strict'; + +function ngDirective(directive) { +  if (isFunction(directive)) { +    directive = { +      link: directive +    } +  } +  directive.restrict = directive.restrict || 'AC'; +  return valueFn(directive); +}; diff --git a/src/widget/form.js b/src/directive/form.js index e3823f41..c3e6b21d 100644 --- a/src/widget/form.js +++ b/src/directive/form.js @@ -17,7 +17,7 @@   *   * @description   * `FormController` keeps track of all its widgets as well as state of them form, such as being valid/invalid or dirty/pristine. - *  + *   * Each {@link angular.module.ng.$compileProvider.directive.form form} directive creates an instance   * of `FormController`.   * diff --git a/src/widget/input.js b/src/directive/input.js index af446c6b..af446c6b 100644 --- a/src/widget/input.js +++ b/src/directive/input.js diff --git a/src/directive/ngBind.js b/src/directive/ngBind.js new file mode 100644 index 00000000..37fabb94 --- /dev/null +++ b/src/directive/ngBind.js @@ -0,0 +1,249 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:bind + * + * @description + * The `ng:bind` attribute tells Angular to replace the text content of the specified HTML element + * with the value of a given expression, and to update the text content when the value of that + * expression changes. + * + * Typically, you don't use `ng:bind` directly, but instead you use the double curly markup like + * `{{ expression }}` and let the Angular compiler transform it to + * `<span ng:bind="expression"></span>` when the template is compiled. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate. + * + * @example + * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. +   <doc:example> +     <doc:source> +       <script> +         function Ctrl($scope) { +           $scope.name = 'Whirled'; +         } +       </script> +       <div ng:controller="Ctrl"> +         Enter name: <input type="text" ng:model="name"> <br/> +         Hello <span ng:bind="name"></span>! +       </div> +     </doc:source> +     <doc:scenario> +       it('should check ng:bind', function() { +         expect(using('.doc-example-live').binding('name')).toBe('Whirled'); +         using('.doc-example-live').input('name').enter('world'); +         expect(using('.doc-example-live').binding('name')).toBe('world'); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngBindDirective = ngDirective(function(scope, element, attr) { +  element.addClass('ng-binding').data('$binding', attr.ngBind); +  scope.$watch(attr.ngBind, function(value) { +    element.text(value == undefined ? '' : value); +  }); +}); + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:bind-html-unsafe + * + * @description + * Creates a binding that will innerHTML the result of evaluating the `expression` into the current + * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if + * {@link angular.module.ng.$compileProvider.directive.ng:bind-html ng:bind-html} directive is too + * restrictive and when you absolutely trust the source of the content you are binding to. + * + * See {@link angular.module.ng.$sanitize $sanitize} docs for examples. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate. + */ +var ngBindHtmlUnsafeDirective = ngDirective(function(scope, element, attr) { +  element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); +  scope.$watch(attr.ngBindHtmlUnsafe, function(value) { +    element.html(value == undefined ? '' : value); +  }); +}); + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:bind-html + * + * @description + * Creates a binding that will sanitize the result of evaluating the `expression` with the + * {@link angular.module.ng.$sanitize $sanitize} service and innerHTML the result into the current + * element. + * + * See {@link angular.module.ng.$sanitize $sanitize} docs for examples. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate. + */ +var ngBindHtmlDirective = ['$sanitize', function($sanitize) { +  return function(scope, element, attr) { +    element.addClass('ng-binding').data('$binding', attr.ngBindHtml); +    scope.$watch(attr.ngBindHtml, function(value) { +      if (value = $sanitize(value)) { +        element.html(value); +      } +    }); +  } +}]; + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:bind-template + * + * @description + * The `ng:bind-template` attribute specifies that the element + * text should be replaced with the template in ng:bind-template. + * Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}` + * expressions. (This is required since some HTML elements + * can not have SPAN elements such as TITLE, or OPTION to name a few.) + * + * @element ANY + * @param {string} template of form + *   <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. + * + * @example + * Try it here: enter text in text box and watch the greeting change. +   <doc:example> +     <doc:source> +       <script> +         function Ctrl($scope) { +           $scope.salutation = 'Hello'; +           $scope.name = 'World'; +         } +       </script> +       <div ng:controller="Ctrl"> +        Salutation: <input type="text" ng:model="salutation"><br/> +        Name: <input type="text" ng:model="name"><br/> +        <pre ng:bind-template="{{salutation}} {{name}}!"></pre> +       </div> +     </doc:source> +     <doc:scenario> +       it('should check ng:bind', function() { +         expect(using('.doc-example-live').binding('salutation')). +           toBe('Hello'); +         expect(using('.doc-example-live').binding('name')). +           toBe('World'); +         using('.doc-example-live').input('salutation').enter('Greetings'); +         using('.doc-example-live').input('name').enter('user'); +         expect(using('.doc-example-live').binding('salutation')). +           toBe('Greetings'); +         expect(using('.doc-example-live').binding('name')). +           toBe('user'); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngBindTemplateDirective = ['$interpolate', function($interpolate) { +  return function(scope, element, attr) { +    // TODO: move this to scenario runner +    var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); +    element.addClass('ng-binding').data('$binding', interpolateFn); +    attr.$observe('ngBindTemplate', function(value) { +      element.text(value); +    }); +  } +}]; + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:bind-attr + * + * @description + * The `ng:bind-attr` attribute specifies that a + * {@link guide/dev_guide.templates.databinding databinding}  should be created between a particular + * element attribute and a given expression. Unlike `ng:bind`, the `ng:bind-attr` contains one or + * more JSON key value pairs; each pair specifies an attribute and the + * {@link guide/dev_guide.expressions expression} to which it will be mapped. + * + * Instead of writing `ng:bind-attr` statements in your HTML, you can use double-curly markup to + * specify an <tt ng:non-bindable>{{expression}}</tt> for the value of an attribute. + * At compile time, the attribute is translated into an + * `<span ng:bind-attr="{attr:expression}"></span>`. + * + * The following HTML snippet shows how to specify `ng:bind-attr`: + * <pre> + *   <a ng:bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'>Google</a> + * </pre> + * + * This is cumbersome, so as we mentioned using double-curly markup is a prefered way of creating + * this binding: + * <pre> + *   <a href="http://www.google.com/search?q={{query}}">Google</a> + * </pre> + * + * During compilation, the template with attribute markup gets translated to the ng:bind-attr form + * mentioned above. + * + * _Note_: You might want to consider using {@link angular.module.ng.$compileProvider.directive.ng:href ng:href} instead of + * `href` if the binding is present in the main application template (`index.html`) and you want to + * make sure that a user is not capable of clicking on raw/uncompiled link. + * + * + * @element ANY + * @param {string} attribute_json one or more JSON key-value pairs representing + *    the attributes to replace with expressions. Each key matches an attribute + *    which needs to be replaced. Each value is a text template of + *    the attribute with the embedded + *    <tt ng:non-bindable>{{expression}}</tt>s. Any number of + *    key-value pairs can be specified. + * + * @example + * Enter a search string in the Live Preview text box and then click "Google". The search executes instantly. +   <doc:example> +     <doc:source> +       <script> +         function Ctrl($scope) { +           $scope.query = 'AngularJS'; +         } +       </script> +       <div ng:controller="Ctrl"> +        Google for: +        <input type="text" ng:model="query"/> +        <a ng:bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'> +          Google +        </a> (ng:bind-attr) | +        <a href="http://www.google.com/search?q={{query}}">Google</a> +        (curly binding in attribute val) +       </div> +     </doc:source> +     <doc:scenario> +       it('should check ng:bind-attr', function() { +         expect(using('.doc-example-live').element('a').attr('href')). +           toBe('http://www.google.com/search?q=AngularJS'); +         using('.doc-example-live').input('query').enter('google'); +         expect(using('.doc-example-live').element('a').attr('href')). +           toBe('http://www.google.com/search?q=google'); +       }); +     </doc:scenario> +   </doc:example> + */ + +var ngBindAttrDirective = ['$interpolate', function($interpolate) { +  return function(scope, element, attr) { +    var lastValue = {}; +    var interpolateFns = {}; +    scope.$watch(function() { +      var values = scope.$eval(attr.ngBindAttr); +      for(var key in values) { +        var exp = values[key], +            fn = (interpolateFns[exp] || +              (interpolateFns[values[key]] = $interpolate(exp))), +            value = fn(scope); +        if (lastValue[key] !== value) { +          attr.$set(key, lastValue[key] = value); +        } +      } +    }); +  } +}]; diff --git a/src/directive/ngClass.js b/src/directive/ngClass.js new file mode 100644 index 00000000..2016d04f --- /dev/null +++ b/src/directive/ngClass.js @@ -0,0 +1,143 @@ +'use strict'; + +function classDirective(name, selector) { +  name = 'ngClass' + name; +  return ngDirective(function(scope, element, attr) { +    scope.$watch(attr[name], function(newVal, oldVal) { +      if (selector === true || scope.$index % 2 === selector) { +        if (oldVal && (newVal !== oldVal)) { +           if (isObject(oldVal) && !isArray(oldVal)) +             oldVal = map(oldVal, function(v, k) { if (v) return k }); +           element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal); +         } +         if (isObject(newVal) && !isArray(newVal)) +            newVal = map(newVal, function(v, k) { if (v) return k }); +         if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal);      } +    }, true); +  }); +} + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:class + * + * @description + * The `ng:class` allows you to set CSS class on HTML element dynamically by databinding an + * expression that represents all classes to be added. + * + * The directive won't add duplicate classes if a particular class was already set. + * + * When the expression changes, the previously added classes are removed and only then the classes + * new classes are added. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result + *   of the evaluation can be a string representing space delimited class + *   names, an array, or a map of class names to boolean values. + * + * @example +   <doc:example> +     <doc:source> +      <input type="button" value="set" ng:click="myVar='ng-invalid'"> +      <input type="button" value="clear" ng:click="myVar=''"> +      <br> +      <span ng:class="myVar">Sample Text     </span> +     </doc:source> +     <doc:scenario> +       it('should check ng:class', function() { +         expect(element('.doc-example-live span').prop('className')).not(). +           toMatch(/ng-invalid/); + +         using('.doc-example-live').element(':button:first').click(); + +         expect(element('.doc-example-live span').prop('className')). +           toMatch(/ng-invalid/); + +         using('.doc-example-live').element(':button:last').click(); + +         expect(element('.doc-example-live span').prop('className')).not(). +           toMatch(/ng-invalid/); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngClassDirective = classDirective('', true); + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:class-odd + * + * @description + * The `ng:class-odd` and `ng:class-even` works exactly as + * {@link angular.module.ng.$compileProvider.directive.ng:class ng:class}, except it works in conjunction with `ng:repeat` and + * takes affect only on odd (even) rows. + * + * This directive can be applied only within a scope of an + * {@link angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat}. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result + *   of the evaluation can be a string representing space delimited class names or an array. + * + * @example +   <doc:example> +     <doc:source> +        <ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']"> +          <li ng:repeat="name in names"> +           <span ng:class-odd="'ng-format-negative'" +                 ng:class-even="'ng-invalid'"> +             {{name}}       +           </span> +          </li> +        </ol> +     </doc:source> +     <doc:scenario> +       it('should check ng:class-odd and ng:class-even', function() { +         expect(element('.doc-example-live li:first span').prop('className')). +           toMatch(/ng-format-negative/); +         expect(element('.doc-example-live li:last span').prop('className')). +           toMatch(/ng-invalid/); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngClassOddDirective = classDirective('Odd', 0); + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:class-even + * + * @description + * The `ng:class-odd` and `ng:class-even` works exactly as + * {@link angular.module.ng.$compileProvider.directive.ng:class ng:class}, except it works in conjunction with `ng:repeat` and + * takes affect only on odd (even) rows. + * + * This directive can be applied only within a scope of an + * {@link angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat}. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result + *   of the evaluation can be a string representing space delimited class names or an array. + * + * @example +   <doc:example> +     <doc:source> +        <ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']"> +          <li ng:repeat="name in names"> +           <span ng:class-odd="'odd'" ng:class-even="'even'"> +             {{name}}       +           </span> +          </li> +        </ol> +     </doc:source> +     <doc:scenario> +       it('should check ng:class-odd and ng:class-even', function() { +         expect(element('.doc-example-live li:first span').prop('className')). +           toMatch(/odd/); +         expect(element('.doc-example-live li:last span').prop('className')). +           toMatch(/even/); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngClassEvenDirective = classDirective('Even', 1); diff --git a/src/directive/ngCloak.js b/src/directive/ngCloak.js new file mode 100644 index 00000000..b4fe6708 --- /dev/null +++ b/src/directive/ngCloak.js @@ -0,0 +1,61 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:cloak + * + * @description + * The `ng:cloak` directive is used to prevent the Angular html template from being briefly + * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this + * directive to avoid the undesirable flicker effect caused by the html template display. + * + * The directive can be applied to the `<body>` element, but typically a fine-grained application is + * prefered in order to benefit from progressive rendering of the browser view. + * + * `ng:cloak` works in cooperation with a css rule that is embedded within `angular.js` and + *  `angular.min.js` files. Following is the css rule: + * + * <pre> + * [ng\:cloak], .ng-cloak { + *   display: none; + * } + * </pre> + * + * When this css rule is loaded by the browser, all html elements (including their children) that + * are tagged with the `ng:cloak` directive are hidden. When Angular comes across this directive + * during the compilation of the template it deletes the `ng:cloak` element attribute, which + * makes the compiled element visible. + * + * For the best result, `angular.js` script must be loaded in the head section of the html file; + * alternatively, the css rule (above) must be included in the external stylesheet of the + * application. + * + * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they + * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css + * class `ng-cloak` in addition to `ng:cloak` directive as shown in the example below. + * + * @element ANY + * + * @example +   <doc:example> +     <doc:source> +        <div id="template1" ng:cloak>{{ 'hello' }}</div> +        <div id="template2" ng:cloak class="ng-cloak">{{ 'hello IE7' }}</div> +     </doc:source> +     <doc:scenario> +       it('should remove the template directive and css class', function() { +         expect(element('.doc-example-live #template1').attr('ng:cloak')). +           not().toBeDefined(); +         expect(element('.doc-example-live #template2').attr('ng:cloak')). +           not().toBeDefined(); +       }); +     </doc:scenario> +   </doc:example> + * + */ +var ngCloakDirective = ngDirective({ +  compile: function(element, attr) { +    attr.$set('ngCloak', undefined); +    element.removeClass('ng-cloak'); +  } +}); diff --git a/src/directive/ngController.js b/src/directive/ngController.js new file mode 100644 index 00000000..e17a97cd --- /dev/null +++ b/src/directive/ngController.js @@ -0,0 +1,103 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:controller + * + * @description + * The `ng:controller` directive assigns behavior to a scope. This is a key aspect of how angular + * supports the principles behind the Model-View-Controller design pattern. + * + * MVC components in angular: + * + * * Model — The Model is data in scope properties; scopes are attached to the DOM. + * * View — The template (HTML with data bindings) is rendered into the View. + * * Controller — The `ng:controller` directive specifies a Controller class; the class has + *   methods that typically express the business logic behind the application. + * + * Note that an alternative way to define controllers is via the `{@link angular.module.ng.$route}` + * service. + * + * @element ANY + * @scope + * @param {expression} expression Name of a globally accessible constructor function or an + *     {@link guide/dev_guide.expressions expression} that on the current scope evaluates to a + *     constructor function. + * + * @example + * Here is a simple form for editing user contact information. Adding, removing, clearing, and + * greeting are methods declared on the controller (see source tab). These methods can + * easily be called from the angular markup. Notice that the scope becomes the `this` for the + * controller's instance. This allows for easy access to the view data from the controller. Also + * notice that any changes to the data are automatically reflected in the View without the need + * for a manual update. +   <doc:example> +     <doc:source> +      <script type="text/javascript"> +        function SettingsController($scope) { +          $scope.name = "John Smith"; +          $scope.contacts = [ +            {type:'phone', value:'408 555 1212'}, +            {type:'email', value:'john.smith@example.org'} ]; + +          $scope.greet = function() { +           alert(this.name); +          }; + +          $scope.addContact = function() { +           this.contacts.push({type:'email', value:'yourname@example.org'}); +          }; + +          $scope.removeContact = function(contactToRemove) { +           var index = this.contacts.indexOf(contactToRemove); +           this.contacts.splice(index, 1); +          }; + +          $scope.clearContact = function(contact) { +           contact.type = 'phone'; +           contact.value = ''; +          }; +        } +      </script> +      <div ng:controller="SettingsController"> +        Name: <input type="text" ng:model="name"/> +        [ <a href="" ng:click="greet()">greet</a> ]<br/> +        Contact: +        <ul> +          <li ng:repeat="contact in contacts"> +            <select ng:model="contact.type"> +               <option>phone</option> +               <option>email</option> +            </select> +            <input type="text" ng:model="contact.value"/> +            [ <a href="" ng:click="clearContact(contact)">clear</a> +            | <a href="" ng:click="removeContact(contact)">X</a> ] +          </li> +          <li>[ <a href="" ng:click="addContact()">add</a> ]</li> +       </ul> +      </div> +     </doc:source> +     <doc:scenario> +       it('should check controller', function() { +         expect(element('.doc-example-live div>:input').val()).toBe('John Smith'); +         expect(element('.doc-example-live li:nth-child(1) input').val()) +           .toBe('408 555 1212'); +         expect(element('.doc-example-live li:nth-child(2) input').val()) +           .toBe('john.smith@example.org'); + +         element('.doc-example-live li:first a:contains("clear")').click(); +         expect(element('.doc-example-live li:first input').val()).toBe(''); + +         element('.doc-example-live li:last a:contains("add")').click(); +         expect(element('.doc-example-live li:nth-child(3) input').val()) +           .toBe('yourname@example.org'); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngControllerDirective = ['$controller', '$window', function($controller, $window) { +  return { +    scope: true, +    controller: '@' +  } +}]; diff --git a/src/directive/ngEventDirs.js b/src/directive/ngEventDirs.js new file mode 100644 index 00000000..0815229d --- /dev/null +++ b/src/directive/ngEventDirs.js @@ -0,0 +1,222 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:click + * + * @description + * The ng:click allows you to specify custom behavior when + * element is clicked. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * click. (Event object is available as `$event`) + * + * @example +   <doc:example> +     <doc:source> +      <button ng:click="count = count + 1" ng:init="count=0"> +        Increment +      </button> +      count: {{count}} +     </doc:source> +     <doc:scenario> +       it('should check ng:click', function() { +         expect(binding('count')).toBe('0'); +         element('.doc-example-live :button').click(); +         expect(binding('count')).toBe('1'); +       }); +     </doc:scenario> +   </doc:example> + */ +/* + * A directive that allows creation of custom onclick handlers that are defined as angular + * expressions and are compiled and executed within the current scope. + * + * Events that are handled via these handler are always configured not to propagate further. + */ +var ngEventDirectives = {}; +forEach( +  'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '), +  function(name) { +    var directiveName = directiveNormalize('ng-' + name); +    ngEventDirectives[directiveName] = ['$parse', function($parse) { +      return function(scope, element, attr) { +        var fn = $parse(attr[directiveName]); +        element.bind(lowercase(name), function(event) { +          scope.$apply(function() { +            fn(scope, {$event:event}); +          }); +        }); +      }; +    }]; +  } +); + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:dblclick + * + * @description + * The ng:dblclick allows you to specify custom behavior on dblclick event. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * dblclick. (Event object is available as `$event`) + * + * @example + * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} + */ + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:mousedown + * + * @description + * The ng:mousedown allows you to specify custom behavior on mousedown event. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * mousedown. (Event object is available as `$event`) + * + * @example + * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} + */ + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:mouseup + * + * @description + * Specify custom behavior on mouseup event. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * mouseup. (Event object is available as `$event`) + * + * @example + * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} + */ + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:mouseover + * + * @description + * Specify custom behavior on mouseover event. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * mouseover. (Event object is available as `$event`) + * + * @example + * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} + */ + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:mouseenter + * + * @description + * Specify custom behavior on mouseenter event. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * mouseenter. (Event object is available as `$event`) + * + * @example + * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} + */ + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:mouseleave + * + * @description + * Specify custom behavior on mouseleave event. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * mouseleave. (Event object is available as `$event`) + * + * @example + * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} + */ + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:mousemove + * + * @description + * Specify custom behavior on mousemove event. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * mousemove. (Event object is available as `$event`) + * + * @example + * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} + */ + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:submit + * + * @description + * Enables binding angular expressions to onsubmit events. + * + * Additionally it prevents the default action (which for form means sending the request to the + * server and reloading the current page). + * + * @element form + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. + * + * @example +   <doc:example> +     <doc:source> +      <script> +        function Ctrl($scope) { +          $scope.list = []; +          $scope.text = 'hello'; +          $scope.submit = function() { +            if (this.text) { +              this.list.push(this.text); +              this.text = ''; +            } +          }; +        } +      </script> +      <form ng:submit="submit()" ng:controller="Ctrl"> +        Enter text and hit enter: +        <input type="text" ng:model="text" name="text" /> +        <input type="submit" id="submit" value="Submit" /> +        <pre>list={{list}}</pre> +      </form> +     </doc:source> +     <doc:scenario> +       it('should check ng:submit', function() { +         expect(binding('list')).toBe('[]'); +         element('.doc-example-live #submit').click(); +         expect(binding('list')).toBe('["hello"]'); +         expect(input('text').val()).toBe(''); +       }); +       it('should ignore empty strings', function() { +         expect(binding('list')).toBe('[]'); +         element('.doc-example-live #submit').click(); +         element('.doc-example-live #submit').click(); +         expect(binding('list')).toBe('["hello"]'); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngSubmitDirective = ngDirective(function(scope, element, attrs) { +  element.bind('submit', function() { +    scope.$apply(attrs.ngSubmit); +  }); +}); diff --git a/src/directive/ngInclude.js b/src/directive/ngInclude.js new file mode 100644 index 00000000..08b14488 --- /dev/null +++ b/src/directive/ngInclude.js @@ -0,0 +1,119 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:include + * @restrict EA + * + * @description + * Fetches, compiles and includes an external HTML fragment. + * + * Keep in mind that Same Origin Policy applies to included resources + * (e.g. ng:include won't work for file:// access). + * + * @scope + * + * @param {string} src angular expression evaluating to URL. If the source is a string constant, + *                 make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. + * @param {Scope=} [scope=new_child_scope] optional expression which evaluates to an + *                 instance of angular.module.ng.$rootScope.Scope to set the HTML fragment to. + * @param {string=} onload Expression to evaluate when a new partial is loaded. + * + * @param {string=} autoscroll Whether `ng:include` should call {@link angular.module.ng.$anchorScroll + *                  $anchorScroll} to scroll the viewport after the content is loaded. + * + *                  - If the attribute is not set, disable scrolling. + *                  - If the attribute is set without value, enable scrolling. + *                  - Otherwise enable scrolling only if the expression evaluates to truthy value. + * + * @example +    <doc:example> +      <doc:source jsfiddle="false"> +       <script> +         function Ctrl($scope) { +           $scope.templates = +             [ { name: 'template1.html', url: 'examples/ng-include/template1.html'} +             , { name: 'template2.html', url: 'examples/ng-include/template2.html'} ]; +           $scope.template = $scope.templates[0]; +         } +       </script> +       <div ng:controller="Ctrl"> +         <select ng:model="template" ng:options="t.name for t in templates"> +          <option value="">(blank)</option> +         </select> +         url of the template: <tt><a href="{{template.url}}">{{template.url}}</a></tt> +         <hr/> +         <div ng-include src="template.url"></div> +       </div> +      </doc:source> +      <doc:scenario> +        it('should load template1.html', function() { +         expect(element('.doc-example-live [ng-include]').text()). +           toBe('Content of template1.html\n'); +        }); +        it('should load template2.html', function() { +         select('template').option('1'); +         expect(element('.doc-example-live [ng-include]').text()). +           toBe('Content of template2.html\n'); +        }); +        it('should change to blank', function() { +         select('template').option(''); +         expect(element('.doc-example-live [ng-include]').text()).toEqual(''); +        }); +      </doc:scenario> +    </doc:example> + */ +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', +                  function($http,   $templateCache,   $anchorScroll,   $compile) { +  return { +    restrict: 'EA', +    compile: function(element, attr) { +      var srcExp = attr.src, +          scopeExp = attr.scope || '', +          autoScrollExp = attr.autoscroll; + +      return function(scope, element, attr) { +        var changeCounter = 0, +            childScope; + +        function incrementChange() { changeCounter++;} +        scope.$watch(srcExp, incrementChange); +        scope.$watch(function() { +          var includeScope = scope.$eval(scopeExp); +          if (includeScope) return includeScope.$id; +        }, incrementChange); +        scope.$watch(function() {return changeCounter;}, function(newChangeCounter) { +           var src = scope.$eval(srcExp), +               useScope = scope.$eval(scopeExp); + +          function clearContent() { +            // if this callback is still desired +            if (newChangeCounter === changeCounter) { +              if (childScope) childScope.$destroy(); +              childScope = null; +              element.html(''); +            } +          } + +           if (src) { +             $http.get(src, {cache: $templateCache}).success(function(response) { +               // if this callback is still desired +               if (newChangeCounter === changeCounter) { +                 element.html(response); +                 if (childScope) childScope.$destroy(); +                 childScope = useScope ? useScope : scope.$new(); +                 $compile(element.contents())(childScope); +                 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { +                   $anchorScroll(); +                 } +                 scope.$emit('$contentLoaded'); +               } +             }).error(clearContent); +           } else { +             clearContent(); +           } +        }); +      }; +    } +  } +}]; diff --git a/src/directive/ngInit.js b/src/directive/ngInit.js new file mode 100644 index 00000000..cdab1cdb --- /dev/null +++ b/src/directive/ngInit.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:init + * + * @description + * The `ng:init` attribute specifies initialization tasks to be executed + *  before the template enters execution mode during bootstrap. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. + * + * @example +   <doc:example> +     <doc:source> +    <div ng:init="greeting='Hello'; person='World'"> +      {{greeting}} {{person}}! +    </div> +     </doc:source> +     <doc:scenario> +       it('should check greeting', function() { +         expect(binding('greeting')).toBe('Hello'); +         expect(binding('person')).toBe('World'); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngInitDirective = ngDirective({ +  compile: function() { +    return { +      pre: function(scope, element, attrs) { +        scope.$eval(attrs.ngInit); +      } +    } +  } +}); diff --git a/src/directive/ngNonBindable.js b/src/directive/ngNonBindable.js new file mode 100644 index 00000000..b9857afa --- /dev/null +++ b/src/directive/ngNonBindable.js @@ -0,0 +1,34 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:non-bindable + * + * @description + * Sometimes it is necessary to write code which looks like bindings but which should be left alone + * by angular. Use `ng:non-bindable` to make angular ignore a chunk of HTML. + * + * Note: `ng:non-bindable` looks like a directive, but is actually an attribute widget. + * + * @element ANY + * + * @example + * In this example there are two location where a simple binding (`{{}}`) is present, but the one + * wrapped in `ng:non-bindable` is left alone. + * + * @example +    <doc:example> +      <doc:source> +        <div>Normal: {{1 + 2}}</div> +        <div ng:non-bindable>Ignored: {{1 + 2}}</div> +      </doc:source> +      <doc:scenario> +       it('should check ng:non-bindable', function() { +         expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); +         expect(using('.doc-example-live').element('div:last').text()). +           toMatch(/1 \+ 2/); +       }); +      </doc:scenario> +    </doc:example> + */ +var ngNonBindableDirective = ngDirective({ terminal: true }); diff --git a/src/directive/ngPluralize.js b/src/directive/ngPluralize.js new file mode 100644 index 00000000..4a6f4a7e --- /dev/null +++ b/src/directive/ngPluralize.js @@ -0,0 +1,204 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:pluralize + * @restrict EA + * + * @description + * # Overview + * ng:pluralize is a widget that displays messages according to en-US localization rules. + * These rules are bundled with angular.js and the rules can be overridden + * (see {@link guide/dev_guide.i18n Angular i18n} dev guide). You configure ng:pluralize by + * specifying the mappings between + * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * plural categories} and the strings to be displayed. + * + * # Plural categories and explicit number rules + * There are two + * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * plural categories} in Angular's default en-US locale: "one" and "other". + * + * While a pural category may match many numbers (for example, in en-US locale, "other" can match + * any number that is not 1), an explicit number rule can only match one number. For example, the + * explicit number rule for "3" matches the number 3. You will see the use of plural categories + * and explicit number rules throughout later parts of this documentation. + * + * # Configuring ng:pluralize + * You configure ng:pluralize by providing 2 attributes: `count` and `when`. + * You can also provide an optional attribute, `offset`. + * + * The value of the `count` attribute can be either a string or an {@link guide/dev_guide.expressions + * Angular expression}; these are evaluated on the current scope for its binded value. + * + * The `when` attribute specifies the mappings between plural categories and the actual + * string to be displayed. The value of the attribute should be a JSON object so that Angular + * can interpret it correctly. + * + * The following example shows how to configure ng:pluralize: + * + * <pre> + * <ng:pluralize count="personCount" +                 when="{'0': 'Nobody is viewing.', + *                      'one': '1 person is viewing.', + *                      'other': '{} people are viewing.'}"> + * </ng:pluralize> + *</pre> + * + * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not + * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" + * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for + * other numbers, for example 12, so that instead of showing "12 people are viewing", you can + * show "a dozen people are viewing". + * + * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted + * into pluralized strings. In the previous example, Angular will replace `{}` with + * <span ng:non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder + * for <span ng:non-bindable>{{numberExpression}}</span>. + * + * # Configuring ng:pluralize with offset + * The `offset` attribute allows further customization of pluralized text, which can result in + * a better user experience. For example, instead of the message "4 people are viewing this document", + * you might display "John, Kate and 2 others are viewing this document". + * The offset attribute allows you to offset a number by any desired value. + * Let's take a look at an example: + * + * <pre> + * <ng:pluralize count="personCount" offset=2 + *               when="{'0': 'Nobody is viewing.', + *                      '1': '{{person1}} is viewing.', + *                      '2': '{{person1}} and {{person2}} are viewing.', + *                      'one': '{{person1}}, {{person2}} and one other person are viewing.', + *                      'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> + * </ng:pluralize> + * </pre> + * + * Notice that we are still using two plural categories(one, other), but we added + * three explicit number rules 0, 1 and 2. + * When one person, perhaps John, views the document, "John is viewing" will be shown. + * When three people view the document, no explicit number rule is found, so + * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. + * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" + * is shown. + * + * Note that when you specify offsets, you must provide explicit number rules for + * numbers from 0 up to and including the offset. If you use an offset of 3, for example, + * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for + * plural categories "one" and "other". + * + * @param {string|expression} count The variable to be bounded to. + * @param {string} when The mapping between plural category to its correspoding strings. + * @param {number=} offset Offset to deduct from the total number. + * + * @example +    <doc:example> +      <doc:source> +        <script> +          function Ctrl($scope) { +            $scope.person1 = 'Igor'; +            $scope.person2 = 'Misko'; +            $scope.personCount = 1; +          } +        </script> +        <div ng:controller="Ctrl"> +          Person 1:<input type="text" ng:model="person1" value="Igor" /><br/> +          Person 2:<input type="text" ng:model="person2" value="Misko" /><br/> +          Number of People:<input type="text" ng:model="personCount" value="1" /><br/> + +          <!--- Example with simple pluralization rules for en locale ---> +          Without Offset: +          <ng-pluralize count="personCount" +                        when="{'0': 'Nobody is viewing.', +                               'one': '1 person is viewing.', +                               'other': '{} people are viewing.'}"> +          </ng-pluralize><br> + +          <!--- Example with offset ---> +          With Offset(2): +          <ng-pluralize count="personCount" offset=2 +                        when="{'0': 'Nobody is viewing.', +                               '1': '{{person1}} is viewing.', +                               '2': '{{person1}} and {{person2}} are viewing.', +                               'one': '{{person1}}, {{person2}} and one other person are viewing.', +                               'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> +          </ng-pluralize> +        </div> +      </doc:source> +      <doc:scenario> +        it('should show correct pluralized string', function() { +          expect(element('.doc-example-live ng-pluralize:first').text()). +                                             toBe('1 person is viewing.'); +          expect(element('.doc-example-live ng-pluralize:last').text()). +                                                toBe('Igor is viewing.'); + +          using('.doc-example-live').input('personCount').enter('0'); +          expect(element('.doc-example-live ng-pluralize:first').text()). +                                               toBe('Nobody is viewing.'); +          expect(element('.doc-example-live ng-pluralize:last').text()). +                                              toBe('Nobody is viewing.'); + +          using('.doc-example-live').input('personCount').enter('2'); +          expect(element('.doc-example-live ng-pluralize:first').text()). +                                            toBe('2 people are viewing.'); +          expect(element('.doc-example-live ng-pluralize:last').text()). +                              toBe('Igor and Misko are viewing.'); + +          using('.doc-example-live').input('personCount').enter('3'); +          expect(element('.doc-example-live ng-pluralize:first').text()). +                                            toBe('3 people are viewing.'); +          expect(element('.doc-example-live ng-pluralize:last').text()). +                              toBe('Igor, Misko and one other person are viewing.'); + +          using('.doc-example-live').input('personCount').enter('4'); +          expect(element('.doc-example-live ng-pluralize:first').text()). +                                            toBe('4 people are viewing.'); +          expect(element('.doc-example-live ng-pluralize:last').text()). +                              toBe('Igor, Misko and 2 other people are viewing.'); +        }); + +        it('should show data-binded names', function() { +          using('.doc-example-live').input('personCount').enter('4'); +          expect(element('.doc-example-live ng-pluralize:last').text()). +              toBe('Igor, Misko and 2 other people are viewing.'); + +          using('.doc-example-live').input('person1').enter('Di'); +          using('.doc-example-live').input('person2').enter('Vojta'); +          expect(element('.doc-example-live ng-pluralize:last').text()). +              toBe('Di, Vojta and 2 other people are viewing.'); +        }); +      </doc:scenario> +    </doc:example> + */ +var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { +  var BRACE = /{}/g; +  return { +    restrict: 'EA', +    link: function(scope, element, attr) { +      var numberExp = attr.count, +          whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs +          offset = attr.offset || 0, +          whens = scope.$eval(whenExp), +          whensExpFns = {}; + +      forEach(whens, function(expression, key) { +        whensExpFns[key] = +          $interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}')); +      }); + +      scope.$watch(function() { +        var value = parseFloat(scope.$eval(numberExp)); + +        if (!isNaN(value)) { +          //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, +          //check it against pluralization rules in $locale service +          if (!whens[value]) value = $locale.pluralCat(value - offset); +           return whensExpFns[value](scope, element, true); +        } else { +          return ''; +        } +      }, function(newVal) { +        element.text(newVal); +      }); +    } +  }; +}]; diff --git a/src/directive/ngRepeat.js b/src/directive/ngRepeat.js new file mode 100644 index 00000000..7cab7c40 --- /dev/null +++ b/src/directive/ngRepeat.js @@ -0,0 +1,182 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:repeat + * + * @description + * The `ng:repeat` widget instantiates a template once per item from a collection. Each template + * instance gets its own scope, where the given loop variable is set to the current collection item, + * and `$index` is set to the item index or key. + * + * Special properties are exposed on the local scope of each template instance, including: + * + *   * `$index` – `{number}` – iterator offset of the repeated element (0..length-1) + *   * `$position` – `{string}` – position of the repeated element in the iterator. One of: + *        * `'first'`, + *        * `'middle'` + *        * `'last'` + * + * Note: Although `ng:repeat` looks like a directive, it is actually an attribute widget. + * + * @element ANY + * @scope + * @priority 1000 + * @param {string} repeat_expression The expression indicating how to enumerate a collection. Two + *   formats are currently supported: + * + *   * `variable in expression` – where variable is the user defined loop variable and `expression` + *     is a scope expression giving the collection to enumerate. + * + *     For example: `track in cd.tracks`. + * + *   * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, + *     and `expression` is the scope expression giving the collection to enumerate. + * + *     For example: `(name, age) in {'adam':10, 'amalie':12}`. + * + * @example + * This example initializes the scope to a list of names and + * then uses `ng:repeat` to display every person: +    <doc:example> +      <doc:source> +        <div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]"> +          I have {{friends.length}} friends. They are: +          <ul> +            <li ng:repeat="friend in friends"> +              [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. +            </li> +          </ul> +        </div> +      </doc:source> +      <doc:scenario> +         it('should check ng:repeat', function() { +           var r = using('.doc-example-live').repeater('ul li'); +           expect(r.count()).toBe(2); +           expect(r.row(0)).toEqual(["1","John","25"]); +           expect(r.row(1)).toEqual(["2","Mary","28"]); +         }); +      </doc:scenario> +    </doc:example> + */ +var ngRepeatDirective = ngDirective({ +  transclude: 'element', +  priority: 1000, +  terminal: true, +  compile: function(element, attr, linker) { +    return function(scope, iterStartElement, attr){ +      var expression = attr.ngRepeat; +      var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), +        lhs, rhs, valueIdent, keyIdent; +      if (! match) { +        throw Error("Expected ng:repeat in form of '_item_ in _collection_' but got '" + +          expression + "'."); +      } +      lhs = match[1]; +      rhs = match[2]; +      match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); +      if (!match) { +        throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + +          keyValue + "'."); +      } +      valueIdent = match[3] || match[1]; +      keyIdent = match[2]; + +      // Store a list of elements from previous run. This is a hash where key is the item from the +      // iterator, and the value is an array of objects with following properties. +      //   - scope: bound scope +      //   - element: previous element. +      //   - index: position +      // We need an array of these objects since the same object can be returned from the iterator. +      // We expect this to be a rare case. +      var lastOrder = new HashQueueMap(); +      scope.$watch(function(scope){ +        var index, length, +            collection = scope.$eval(rhs), +            collectionLength = size(collection, true), +            childScope, +            // Same as lastOrder but it has the current state. It will become the +            // lastOrder on the next iteration. +            nextOrder = new HashQueueMap(), +            key, value, // key/value of iteration +            array, last,       // last object information {scope, element, index} +            cursor = iterStartElement;     // current position of the node + +        if (!isArray(collection)) { +          // if object, extract keys, sort them and use to determine order of iteration over obj props +          array = []; +          for(key in collection) { +            if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { +              array.push(key); +            } +          } +          array.sort(); +        } else { +          array = collection || []; +        } + +        // we are not using forEach for perf reasons (trying to avoid #call) +        for (index = 0, length = array.length; index < length; index++) { +          key = (collection === array) ? index : array[index]; +          value = collection[key]; +          last = lastOrder.shift(value); +          if (last) { +            // if we have already seen this object, then we need to reuse the +            // associated scope/element +            childScope = last.scope; +            nextOrder.push(value, last); + +            if (index === last.index) { +              // do nothing +              cursor = last.element; +            } else { +              // existing item which got moved +              last.index = index; +              // This may be a noop, if the element is next, but I don't know of a good way to +              // figure this out,  since it would require extra DOM access, so let's just hope that +              // the browsers realizes that it is noop, and treats it as such. +              cursor.after(last.element); +              cursor = last.element; +            } +          } else { +            // new item which we don't know about +            childScope = scope.$new(); +          } + +          childScope[valueIdent] = value; +          if (keyIdent) childScope[keyIdent] = key; +          childScope.$index = index; +          childScope.$position = index === 0 ? +              'first' : +              (index == collectionLength - 1 ? 'last' : 'middle'); + +          if (!last) { +            linker(childScope, function(clone){ +              cursor.after(clone); +              last = { +                  scope: childScope, +                  element: (cursor = clone), +                  index: index +                }; +              nextOrder.push(value, last); +            }); +          } +        } + +        //shrink children +        for (key in lastOrder) { +          if (lastOrder.hasOwnProperty(key)) { +            array = lastOrder[key]; +            while(array.length) { +              value = array.pop(); +              value.element.remove(); +              value.scope.$destroy(); +            } +          } +        } + +        lastOrder = nextOrder; +      }); +    }; +  } +}); diff --git a/src/directive/ngShowHide.js b/src/directive/ngShowHide.js new file mode 100644 index 00000000..0060ec80 --- /dev/null +++ b/src/directive/ngShowHide.js @@ -0,0 +1,80 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:show + * + * @description + * The `ng:show` and `ng:hide` directives show or hide a portion of the DOM tree (HTML) + * conditionally. + * + * @element ANY + * @param {expression} expression If the {@link guide/dev_guide.expressions expression} is truthy + *     then the element is shown or hidden respectively. + * + * @example +   <doc:example> +     <doc:source> +        Click me: <input type="checkbox" ng:model="checked"><br/> +        Show: <span ng:show="checked">I show up when your checkbox is checked.</span> <br/> +        Hide: <span ng:hide="checked">I hide when your checkbox is checked.</span> +     </doc:source> +     <doc:scenario> +       it('should check ng:show / ng:hide', function() { +         expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); +         expect(element('.doc-example-live span:last:visible').count()).toEqual(1); + +         input('checked').check(); + +         expect(element('.doc-example-live span:first:visible').count()).toEqual(1); +         expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); +       }); +     </doc:scenario> +   </doc:example> + */ +//TODO(misko): refactor to remove element from the DOM +var ngShowDirective = ngDirective(function(scope, element, attr){ +  scope.$watch(attr.ngShow, function(value){ +    element.css('display', toBoolean(value) ? '' : 'none'); +  }); +}); + + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:hide + * + * @description + * The `ng:hide` and `ng:show` directives hide or show a portion + * of the HTML conditionally. + * + * @element ANY + * @param {expression} expression If the {@link guide/dev_guide.expressions expression} truthy then + *     the element is shown or hidden respectively. + * + * @example +   <doc:example> +     <doc:source> +        Click me: <input type="checkbox" ng:model="checked"><br/> +        Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/> +        Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span> +     </doc:source> +     <doc:scenario> +       it('should check ng:show / ng:hide', function() { +         expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); +         expect(element('.doc-example-live span:last:visible').count()).toEqual(1); + +         input('checked').check(); + +         expect(element('.doc-example-live span:first:visible').count()).toEqual(1); +         expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); +       }); +     </doc:scenario> +   </doc:example> + */ +//TODO(misko): refactor to remove element from the DOM +var ngHideDirective = ngDirective(function(scope, element, attr){ +  scope.$watch(attr.ngHide, function(value){ +    element.css('display', toBoolean(value) ? 'none' : ''); +  }); +}); diff --git a/src/directive/ngStyle.js b/src/directive/ngStyle.js new file mode 100644 index 00000000..cd2c1e3c --- /dev/null +++ b/src/directive/ngStyle.js @@ -0,0 +1,42 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:style + * + * @description + * The ng:style allows you to set CSS style on an HTML element conditionally. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} which evals to an + *      object whose keys are CSS style names and values are corresponding values for those CSS + *      keys. + * + * @example +   <doc:example> +     <doc:source> +        <input type="button" value="set" ng:click="myStyle={color:'red'}"> +        <input type="button" value="clear" ng:click="myStyle={}"> +        <br/> +        <span ng:style="myStyle">Sample Text</span> +        <pre>myStyle={{myStyle}}</pre> +     </doc:source> +     <doc:scenario> +       it('should check ng:style', function() { +         expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); +         element('.doc-example-live :button[value=set]').click(); +         expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)'); +         element('.doc-example-live :button[value=clear]').click(); +         expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); +       }); +     </doc:scenario> +   </doc:example> + */ +var ngStyleDirective = ngDirective(function(scope, element, attr) { +  scope.$watch(attr.ngStyle, function(newStyles, oldStyles) { +    if (oldStyles && (newStyles !== oldStyles)) { +      forEach(oldStyles, function(val, style) { element.css(style, '');}); +    } +    if (newStyles) element.css(newStyles); +  }, true); +}); diff --git a/src/directive/ngSwitch.js b/src/directive/ngSwitch.js new file mode 100644 index 00000000..0b2475f3 --- /dev/null +++ b/src/directive/ngSwitch.js @@ -0,0 +1,110 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:switch + * @restrict EA + * + * @description + * Conditionally change the DOM structure. + * + * @usageContent + * <any ng:switch-when="matchValue1">...</any> + *   <any ng:switch-when="matchValue2">...</any> + *   ... + *   <any ng:switch-default>...</any> + * + * @scope + * @param {*} on expression to match against <tt>ng:switch-when</tt>. + * @paramDescription + * On child elments add: + * + * * `ng:switch-when`: the case statement to match against. If match then this + *   case will be displayed. + * * `ng:switch-default`: the default case when no other casses match. + * + * @example +    <doc:example> +      <doc:source> +        <script> +          function Ctrl($scope) { +            $scope.items = ['settings', 'home', 'other']; +            $scope.selection = $scope.items[0]; +          } +        </script> +        <div ng:controller="Ctrl"> +          <select ng:model="selection" ng:options="item for item in items"> +          </select> +          <tt>selection={{selection}}</tt> +          <hr/> +          <ng:switch on="selection" > +            <div ng:switch-when="settings">Settings Div</div> +            <span ng:switch-when="home">Home Span</span> +            <span ng:switch-default>default</span> +          </ng:switch> +        </div> +      </doc:source> +      <doc:scenario> +        it('should start in settings', function() { +         expect(element('.doc-example-live ng\\:switch').text()).toMatch(/Settings Div/); +        }); +        it('should change to home', function() { +         select('selection').option('home'); +         expect(element('.doc-example-live ng\\:switch').text()).toMatch(/Home Span/); +        }); +        it('should select deafault', function() { +         select('selection').option('other'); +         expect(element('.doc-example-live ng\\:switch').text()).toMatch(/default/); +        }); +      </doc:scenario> +    </doc:example> + */ +var NG_SWITCH = 'ng-switch'; +var ngSwitchDirective = valueFn({ +  restrict: 'EA', +  compile: function(element, attr) { +    var watchExpr = attr.ngSwitch || attr.on, +        cases = {}; + +    element.data(NG_SWITCH, cases); +    return function(scope, element){ +      var selectedTransclude, +          selectedElement; + +      scope.$watch(watchExpr, function(value) { +        if (selectedElement) { +          selectedElement.remove(); +          selectedElement = null; +        } +        if ((selectedTransclude = cases['!' + value] || cases['?'])) { +          scope.$eval(attr.change); +          selectedTransclude(scope.$new(), function(caseElement, scope) { +            selectedElement = caseElement; +            element.append(caseElement); +            element.bind('$destroy', bind(scope, scope.$destroy)); +          }); +        } +      }); +    }; +  } +}); + +var ngSwitchWhenDirective = ngDirective({ +  transclude: 'element', +  priority: 500, +  compile: function(element, attrs, transclude) { +    var cases = element.inheritedData(NG_SWITCH); +    assertArg(cases); +    cases['!' + attrs.ngSwitchWhen] = transclude; +  } +}); + +var ngSwitchDefaultDirective = ngDirective({ +  transclude: 'element', +  priority: 500, +  compile: function(element, attrs, transclude) { +    var cases = element.inheritedData(NG_SWITCH); +    assertArg(cases); +    cases['?'] = transclude; +  } +}); diff --git a/src/directive/ngTransclude.js b/src/directive/ngTransclude.js new file mode 100644 index 00000000..16587f30 --- /dev/null +++ b/src/directive/ngTransclude.js @@ -0,0 +1,58 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:transclude + * + * @description + * Insert the transcluded DOM here. + * + * @element ANY + * + * @example +   <doc:example module="transclude"> +     <doc:source> +       <script> +         function Ctrl($scope) { +           $scope.title = 'Lorem Ipsum'; +           $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; +         } + +         angular.module('transclude', []) +          .directive('pane', function(){ +             return { +               restrict: 'E', +               transclude: true, +               scope: 'isolate', +               locals: { title:'bind' }, +               template: '<div style="border: 1px solid black;">' + +                           '<div style="background-color: gray">{{title}}</div>' + +                           '<div ng-transclude></div>' + +                         '</div>' +             }; +         }); +       </script> +       <div ng:controller="Ctrl"> +         <input ng:model="title"><br> +         <textarea ng:model="text"></textarea> <br/> +         <pane title="{{title}}">{{text}}</pane> +       </div> +     </doc:source> +     <doc:scenario> +        it('should have transcluded', function() { +          input('title').enter('TITLE'); +          input('text').enter('TEXT'); +          expect(binding('title')).toEqual('TITLE'); +          expect(binding('text')).toEqual('TEXT'); +        }); +     </doc:scenario> +   </doc:example> + * + */ +var ngTranscludeDirective = ngDirective({ +  controller: ['$transclude', '$element', function($transclude, $element) { +    $transclude(function(clone) { +      $element.append(clone); +    }); +  }] +}); diff --git a/src/directive/ngView.js b/src/directive/ngView.js new file mode 100644 index 00000000..3c589354 --- /dev/null +++ b/src/directive/ngView.js @@ -0,0 +1,169 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.ng:view + * @restrict ECA + * + * @description + * # Overview + * `ng:view` is a directive that complements the {@link angular.module.ng.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * @scope + * @example +    <doc:example module="ngView"> +      <doc:source> +        <script type="text/ng-template" id="examples/book.html"> +          controller: {{name}}<br /> +          Book Id: {{params.bookId}}<br /> +        </script> + +        <script type="text/ng-template" id="examples/chapter.html"> +          controller: {{name}}<br /> +          Book Id: {{params.bookId}}<br /> +          Chapter Id: {{params.chapterId}} +        </script> + +        <script> +          angular.module('ngView', [], function($routeProvider, $locationProvider) { +            $routeProvider.when('/Book/:bookId', { +              template: 'examples/book.html', +              controller: BookCntl +            }); +            $routeProvider.when('/Book/:bookId/ch/:chapterId', { +              template: 'examples/chapter.html', +              controller: ChapterCntl +            }); + +            // configure html5 to get links working on jsfiddle +            $locationProvider.html5Mode(true); +          }); + +          function MainCntl($scope, $route, $routeParams, $location) { +            $scope.$route = $route; +            $scope.$location = $location; +            $scope.$routeParams = $routeParams; +          } + +          function BookCntl($scope, $routeParams) { +            $scope.name = "BookCntl"; +            $scope.params = $routeParams; +          } + +          function ChapterCntl($scope, $routeParams) { +            $scope.name = "ChapterCntl"; +            $scope.params = $routeParams; +          } +        </script> + +        <div ng:controller="MainCntl"> +          Choose: +          <a href="/Book/Moby">Moby</a> | +          <a href="/Book/Moby/ch/1">Moby: Ch1</a> | +          <a href="/Book/Gatsby">Gatsby</a> | +          <a href="/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | +          <a href="/Book/Scarlet">Scarlet Letter</a><br/> + +          <ng:view></ng:view> +          <hr /> + +          <pre>$location.path() = {{$location.path()}}</pre> +          <pre>$route.current.template = {{$route.current.template}}</pre> +          <pre>$route.current.params = {{$route.current.params}}</pre> +          <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> +          <pre>$routeParams = {{$routeParams}}</pre> +        </div> +      </doc:source> +      <doc:scenario> +        it('should load and compile correct template', function() { +          element('a:contains("Moby: Ch1")').click(); +          var content = element('.doc-example-live ng\\:view').text(); +          expect(content).toMatch(/controller\: ChapterCntl/); +          expect(content).toMatch(/Book Id\: Moby/); +          expect(content).toMatch(/Chapter Id\: 1/); + +          element('a:contains("Scarlet")').click(); +          content = element('.doc-example-live ng\\:view').text(); +          expect(content).toMatch(/controller\: BookCntl/); +          expect(content).toMatch(/Book Id\: Scarlet/); +        }); +      </doc:scenario> +    </doc:example> + */ +var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile', +                       '$controller', +               function($http,   $templateCache,   $route,   $anchorScroll,   $compile, +                        $controller) { +  return { +    restrict: 'ECA', +    terminal: true, +    link: function(scope, element) { +      var changeCounter = 0, +          lastScope; + +      scope.$on('$afterRouteChange', function(event, next, previous) { +        changeCounter++; +      }); + +      scope.$watch(function() {return changeCounter;}, function(newChangeCounter) { +        var template = $route.current && $route.current.template; + +        function destroyLastScope() { +          if (lastScope) { +            lastScope.$destroy(); +            lastScope = null; +          } +        } + +        function clearContent() { +          // ignore callback if another route change occured since +          if (newChangeCounter == changeCounter) { +            element.html(''); +          } +          destroyLastScope(); +        } + +        if (template) { +          $http.get(template, {cache: $templateCache}).success(function(response) { +            // ignore callback if another route change occured since +            if (newChangeCounter == changeCounter) { +              element.html(response); +              destroyLastScope(); + +              var link = $compile(element.contents()), +                  current = $route.current; + +              lastScope = current.scope = scope.$new(); +              if (current.controller) { +                $controller(current.controller, {$scope: lastScope}); +              } + +              link(lastScope); +              lastScope.$emit('$contentLoaded'); + +              // $anchorScroll might listen on event... +              $anchorScroll(); +            } +          }).error(clearContent); +        } else { +          clearContent(); +        } +      }); +    } +  }; +}]; + + +var onloadDirective = valueFn({ +  restrict: 'AC', +  link: function(scope, elm, attr) { +    var onloadExp = attr.onload || ''; //workaround for jquery bug #7537) + +    scope.$on('$contentLoaded', function(event) { +      scope.$eval(onloadExp); +    }); +  } +}); diff --git a/src/directive/script.js b/src/directive/script.js new file mode 100644 index 00000000..f0affaf9 --- /dev/null +++ b/src/directive/script.js @@ -0,0 +1,42 @@ +'use strict'; + +/** + * @ngdoc directive + * @name angular.module.ng.$compileProvider.directive.script + * + * @description + * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the + * template can be used by `ng:include`, `ng:view` or directive templates. + * + * @restrict E + * + * @example +  <doc:example> +    <doc:source> +      <script type="text/ng-template" id="/tpl.html"> +        Content of the template. +      </script> + +      <a ng:click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> +      <div id="tpl-content" ng-include src="currentTpl"></div> +    </doc:source> +    <doc:scenario> +      it('should load template defined inside script tag', function() { +        element('#tpl-link').click(); +        expect(element('#tpl-content').text()).toMatch(/Content of the template/); +      }); +    </doc:scenario> +  </doc:example> + */ +var scriptDirective = ['$templateCache', function($templateCache) { +  return { +    restrict: 'E', +    terminal: true, +    compile: function(element, attr) { +      if (attr.type == 'text/ng-template') { +        var templateUrl = attr.id; +        $templateCache.put(templateUrl, element.text()); +      } +    } +  }; +}]; diff --git a/src/widget/select.js b/src/directive/select.js index 5ed1367f..5ed1367f 100644 --- a/src/widget/select.js +++ b/src/directive/select.js diff --git a/src/directive/style.js b/src/directive/style.js new file mode 100644 index 00000000..68ea1465 --- /dev/null +++ b/src/directive/style.js @@ -0,0 +1,6 @@ +'use strict'; + +var styleDirective = valueFn({ +  restrict: 'E', +  terminal: true +}); diff --git a/src/directives.js b/src/directives.js deleted file mode 100644 index 9b231850..00000000 --- a/src/directives.js +++ /dev/null @@ -1,1031 +0,0 @@ -'use strict'; - -function ngDirective(directive) { -  if (isFunction(directive)) { -    directive = { -      link: directive -    } -  } -  directive.restrict = directive.restrict || 'AC'; -  return valueFn(directive); -}; - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:init - * - * @description - * The `ng:init` attribute specifies initialization tasks to be executed - *  before the template enters execution mode during bootstrap. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. - * - * @example -   <doc:example> -     <doc:source> -    <div ng:init="greeting='Hello'; person='World'"> -      {{greeting}} {{person}}! -    </div> -     </doc:source> -     <doc:scenario> -       it('should check greeting', function() { -         expect(binding('greeting')).toBe('Hello'); -         expect(binding('person')).toBe('World'); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngInitDirective = ngDirective({ -  compile: function() { -    return { -      pre: function(scope, element, attrs) { -        scope.$eval(attrs.ngInit); -      } -    } -  } -}); - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:controller - * - * @description - * The `ng:controller` directive assigns behavior to a scope. This is a key aspect of how angular - * supports the principles behind the Model-View-Controller design pattern. - * - * MVC components in angular: - * - * * Model — The Model is data in scope properties; scopes are attached to the DOM. - * * View — The template (HTML with data bindings) is rendered into the View. - * * Controller — The `ng:controller` directive specifies a Controller class; the class has - *   methods that typically express the business logic behind the application. - * - * Note that an alternative way to define controllers is via the `{@link angular.module.ng.$route}` - * service. - * - * @element ANY - * @scope - * @param {expression} expression Name of a globally accessible constructor function or an - *     {@link guide/dev_guide.expressions expression} that on the current scope evaluates to a - *     constructor function. - * - * @example - * Here is a simple form for editing user contact information. Adding, removing, clearing, and - * greeting are methods declared on the controller (see source tab). These methods can - * easily be called from the angular markup. Notice that the scope becomes the `this` for the - * controller's instance. This allows for easy access to the view data from the controller. Also - * notice that any changes to the data are automatically reflected in the View without the need - * for a manual update. -   <doc:example> -     <doc:source> -      <script type="text/javascript"> -        function SettingsController($scope) { -          $scope.name = "John Smith"; -          $scope.contacts = [ -            {type:'phone', value:'408 555 1212'}, -            {type:'email', value:'john.smith@example.org'} ]; - -          $scope.greet = function() { -           alert(this.name); -          }; - -          $scope.addContact = function() { -           this.contacts.push({type:'email', value:'yourname@example.org'}); -          }; - -          $scope.removeContact = function(contactToRemove) { -           var index = this.contacts.indexOf(contactToRemove); -           this.contacts.splice(index, 1); -          }; - -          $scope.clearContact = function(contact) { -           contact.type = 'phone'; -           contact.value = ''; -          }; -        } -      </script> -      <div ng:controller="SettingsController"> -        Name: <input type="text" ng:model="name"/> -        [ <a href="" ng:click="greet()">greet</a> ]<br/> -        Contact: -        <ul> -          <li ng:repeat="contact in contacts"> -            <select ng:model="contact.type"> -               <option>phone</option> -               <option>email</option> -            </select> -            <input type="text" ng:model="contact.value"/> -            [ <a href="" ng:click="clearContact(contact)">clear</a> -            | <a href="" ng:click="removeContact(contact)">X</a> ] -          </li> -          <li>[ <a href="" ng:click="addContact()">add</a> ]</li> -       </ul> -      </div> -     </doc:source> -     <doc:scenario> -       it('should check controller', function() { -         expect(element('.doc-example-live div>:input').val()).toBe('John Smith'); -         expect(element('.doc-example-live li:nth-child(1) input').val()) -           .toBe('408 555 1212'); -         expect(element('.doc-example-live li:nth-child(2) input').val()) -           .toBe('john.smith@example.org'); - -         element('.doc-example-live li:first a:contains("clear")').click(); -         expect(element('.doc-example-live li:first input').val()).toBe(''); - -         element('.doc-example-live li:last a:contains("add")').click(); -         expect(element('.doc-example-live li:nth-child(3) input').val()) -           .toBe('yourname@example.org'); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngControllerDirective = ['$controller', '$window', function($controller, $window) { -  return { -    scope: true, -    controller: '@' -  } -}]; - - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:bind - * - * @description - * The `ng:bind` attribute tells Angular to replace the text content of the specified HTML element - * with the value of a given expression, and to update the text content when the value of that - * expression changes. - * - * Typically, you don't use `ng:bind` directly, but instead you use the double curly markup like - * `{{ expression }}` and let the Angular compiler transform it to - * `<span ng:bind="expression"></span>` when the template is compiled. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate. - * - * @example - * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. -   <doc:example> -     <doc:source> -       <script> -         function Ctrl($scope) { -           $scope.name = 'Whirled'; -         } -       </script> -       <div ng:controller="Ctrl"> -         Enter name: <input type="text" ng:model="name"> <br/> -         Hello <span ng:bind="name"></span>! -       </div> -     </doc:source> -     <doc:scenario> -       it('should check ng:bind', function() { -         expect(using('.doc-example-live').binding('name')).toBe('Whirled'); -         using('.doc-example-live').input('name').enter('world'); -         expect(using('.doc-example-live').binding('name')).toBe('world'); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngBindDirective = ngDirective(function(scope, element, attr) { -  element.addClass('ng-binding').data('$binding', attr.ngBind); -  scope.$watch(attr.ngBind, function(value) { -    element.text(value == undefined ? '' : value); -  }); -}); - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:bind-html-unsafe - * - * @description - * Creates a binding that will innerHTML the result of evaluating the `expression` into the current - * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if - * {@link angular.module.ng.$compileProvider.directive.ng:bind-html ng:bind-html} directive is too - * restrictive and when you absolutely trust the source of the content you are binding to. - * - * See {@link angular.module.ng.$sanitize $sanitize} docs for examples. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate. - */ -var ngBindHtmlUnsafeDirective = ngDirective(function(scope, element, attr) { -  element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); -  scope.$watch(attr.ngBindHtmlUnsafe, function(value) { -    element.html(value == undefined ? '' : value); -  }); -}); - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:bind-html - * - * @description - * Creates a binding that will sanitize the result of evaluating the `expression` with the - * {@link angular.module.ng.$sanitize $sanitize} service and innerHTML the result into the current - * element. - * - * See {@link angular.module.ng.$sanitize $sanitize} docs for examples. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate. - */ -var ngBindHtmlDirective = ['$sanitize', function($sanitize) { -  return function(scope, element, attr) { -    element.addClass('ng-binding').data('$binding', attr.ngBindHtml); -    scope.$watch(attr.ngBindHtml, function(value) { -      if (value = $sanitize(value)) { -        element.html(value); -      } -    }); -  } -}]; - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:bind-template - * - * @description - * The `ng:bind-template` attribute specifies that the element - * text should be replaced with the template in ng:bind-template. - * Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}` - * expressions. (This is required since some HTML elements - * can not have SPAN elements such as TITLE, or OPTION to name a few.) - * - * @element ANY - * @param {string} template of form - *   <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. - * - * @example - * Try it here: enter text in text box and watch the greeting change. -   <doc:example> -     <doc:source> -       <script> -         function Ctrl($scope) { -           $scope.salutation = 'Hello'; -           $scope.name = 'World'; -         } -       </script> -       <div ng:controller="Ctrl"> -        Salutation: <input type="text" ng:model="salutation"><br/> -        Name: <input type="text" ng:model="name"><br/> -        <pre ng:bind-template="{{salutation}} {{name}}!"></pre> -       </div> -     </doc:source> -     <doc:scenario> -       it('should check ng:bind', function() { -         expect(using('.doc-example-live').binding('salutation')). -           toBe('Hello'); -         expect(using('.doc-example-live').binding('name')). -           toBe('World'); -         using('.doc-example-live').input('salutation').enter('Greetings'); -         using('.doc-example-live').input('name').enter('user'); -         expect(using('.doc-example-live').binding('salutation')). -           toBe('Greetings'); -         expect(using('.doc-example-live').binding('name')). -           toBe('user'); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngBindTemplateDirective = ['$interpolate', function($interpolate) { -  return function(scope, element, attr) { -    // TODO: move this to scenario runner -    var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); -    element.addClass('ng-binding').data('$binding', interpolateFn); -    attr.$observe('ngBindTemplate', function(value) { -      element.text(value); -    }); -  } -}]; - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:bind-attr - * - * @description - * The `ng:bind-attr` attribute specifies that a - * {@link guide/dev_guide.templates.databinding databinding}  should be created between a particular - * element attribute and a given expression. Unlike `ng:bind`, the `ng:bind-attr` contains one or - * more JSON key value pairs; each pair specifies an attribute and the - * {@link guide/dev_guide.expressions expression} to which it will be mapped. - * - * Instead of writing `ng:bind-attr` statements in your HTML, you can use double-curly markup to - * specify an <tt ng:non-bindable>{{expression}}</tt> for the value of an attribute. - * At compile time, the attribute is translated into an - * `<span ng:bind-attr="{attr:expression}"></span>`. - * - * The following HTML snippet shows how to specify `ng:bind-attr`: - * <pre> - *   <a ng:bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'>Google</a> - * </pre> - * - * This is cumbersome, so as we mentioned using double-curly markup is a prefered way of creating - * this binding: - * <pre> - *   <a href="http://www.google.com/search?q={{query}}">Google</a> - * </pre> - * - * During compilation, the template with attribute markup gets translated to the ng:bind-attr form - * mentioned above. - * - * _Note_: You might want to consider using {@link angular.module.ng.$compileProvider.directive.ng:href ng:href} instead of - * `href` if the binding is present in the main application template (`index.html`) and you want to - * make sure that a user is not capable of clicking on raw/uncompiled link. - * - * - * @element ANY - * @param {string} attribute_json one or more JSON key-value pairs representing - *    the attributes to replace with expressions. Each key matches an attribute - *    which needs to be replaced. Each value is a text template of - *    the attribute with the embedded - *    <tt ng:non-bindable>{{expression}}</tt>s. Any number of - *    key-value pairs can be specified. - * - * @example - * Enter a search string in the Live Preview text box and then click "Google". The search executes instantly. -   <doc:example> -     <doc:source> -       <script> -         function Ctrl($scope) { -           $scope.query = 'AngularJS'; -         } -       </script> -       <div ng:controller="Ctrl"> -        Google for: -        <input type="text" ng:model="query"/> -        <a ng:bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'> -          Google -        </a> (ng:bind-attr) | -        <a href="http://www.google.com/search?q={{query}}">Google</a> -        (curly binding in attribute val) -       </div> -     </doc:source> -     <doc:scenario> -       it('should check ng:bind-attr', function() { -         expect(using('.doc-example-live').element('a').attr('href')). -           toBe('http://www.google.com/search?q=AngularJS'); -         using('.doc-example-live').input('query').enter('google'); -         expect(using('.doc-example-live').element('a').attr('href')). -           toBe('http://www.google.com/search?q=google'); -       }); -     </doc:scenario> -   </doc:example> - */ - -var ngBindAttrDirective = ['$interpolate', function($interpolate) { -  return function(scope, element, attr) { -    var lastValue = {}; -    var interpolateFns = {}; -    scope.$watch(function() { -      var values = scope.$eval(attr.ngBindAttr); -      for(var key in values) { -        var exp = values[key], -            fn = (interpolateFns[exp] || -              (interpolateFns[values[key]] = $interpolate(exp))), -            value = fn(scope); -        if (lastValue[key] !== value) { -          attr.$set(key, lastValue[key] = value); -        } -      } -    }); -  } -}]; - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:click - * - * @description - * The ng:click allows you to specify custom behavior when - * element is clicked. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon - * click. (Event object is available as `$event`) - * - * @example -   <doc:example> -     <doc:source> -      <button ng:click="count = count + 1" ng:init="count=0"> -        Increment -      </button> -      count: {{count}} -     </doc:source> -     <doc:scenario> -       it('should check ng:click', function() { -         expect(binding('count')).toBe('0'); -         element('.doc-example-live :button').click(); -         expect(binding('count')).toBe('1'); -       }); -     </doc:scenario> -   </doc:example> - */ -/* - * A directive that allows creation of custom onclick handlers that are defined as angular - * expressions and are compiled and executed within the current scope. - * - * Events that are handled via these handler are always configured not to propagate further. - */ -var ngEventDirectives = {}; -forEach( -  'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '), -  function(name) { -    var directiveName = directiveNormalize('ng-' + name); -    ngEventDirectives[directiveName] = ['$parse', function($parse) { -      return function(scope, element, attr) { -        var fn = $parse(attr[directiveName]); -        element.bind(lowercase(name), function(event) { -          scope.$apply(function() { -            fn(scope, {$event:event}); -          }); -        }); -      }; -    }]; -  } -); - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:dblclick - * - * @description - * The ng:dblclick allows you to specify custom behavior on dblclick event. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon - * dblclick. (Event object is available as `$event`) - * - * @example - * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} - */ - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:mousedown - * - * @description - * The ng:mousedown allows you to specify custom behavior on mousedown event. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon - * mousedown. (Event object is available as `$event`) - * - * @example - * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} - */ - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:mouseup - * - * @description - * Specify custom behavior on mouseup event. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon - * mouseup. (Event object is available as `$event`) - * - * @example - * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} - */ - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:mouseover - * - * @description - * Specify custom behavior on mouseover event. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon - * mouseover. (Event object is available as `$event`) - * - * @example - * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} - */ - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:mouseenter - * - * @description - * Specify custom behavior on mouseenter event. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon - * mouseenter. (Event object is available as `$event`) - * - * @example - * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} - */ - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:mouseleave - * - * @description - * Specify custom behavior on mouseleave event. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon - * mouseleave. (Event object is available as `$event`) - * - * @example - * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} - */ - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:mousemove - * - * @description - * Specify custom behavior on mousemove event. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon - * mousemove. (Event object is available as `$event`) - * - * @example - * See {@link angular.module.ng.$compileProvider.directive.ng:click ng:click} - */ - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:submit - * - * @description - * Enables binding angular expressions to onsubmit events. - * - * Additionally it prevents the default action (which for form means sending the request to the - * server and reloading the current page). - * - * @element form - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. - * - * @example -   <doc:example> -     <doc:source> -      <script> -        function Ctrl($scope) { -          $scope.list = []; -          $scope.text = 'hello'; -          $scope.submit = function() { -            if (this.text) { -              this.list.push(this.text); -              this.text = ''; -            } -          }; -        } -      </script> -      <form ng:submit="submit()" ng:controller="Ctrl"> -        Enter text and hit enter: -        <input type="text" ng:model="text" name="text" /> -        <input type="submit" id="submit" value="Submit" /> -        <pre>list={{list}}</pre> -      </form> -     </doc:source> -     <doc:scenario> -       it('should check ng:submit', function() { -         expect(binding('list')).toBe('[]'); -         element('.doc-example-live #submit').click(); -         expect(binding('list')).toBe('["hello"]'); -         expect(input('text').val()).toBe(''); -       }); -       it('should ignore empty strings', function() { -         expect(binding('list')).toBe('[]'); -         element('.doc-example-live #submit').click(); -         element('.doc-example-live #submit').click(); -         expect(binding('list')).toBe('["hello"]'); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngSubmitDirective = ngDirective(function(scope, element, attrs) { -  element.bind('submit', function() { -    scope.$apply(attrs.ngSubmit); -  }); -}); - - -function classDirective(name, selector) { -  name = 'ngClass' + name; -  return ngDirective(function(scope, element, attr) { -    scope.$watch(attr[name], function(newVal, oldVal) { -      if (selector === true || scope.$index % 2 === selector) { -        if (oldVal && (newVal !== oldVal)) { -           if (isObject(oldVal) && !isArray(oldVal)) -             oldVal = map(oldVal, function(v, k) { if (v) return k }); -           element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal); -         } -         if (isObject(newVal) && !isArray(newVal)) -            newVal = map(newVal, function(v, k) { if (v) return k }); -         if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal);      } -    }, true); -  }); -} - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:class - * - * @description - * The `ng:class` allows you to set CSS class on HTML element dynamically by databinding an - * expression that represents all classes to be added. - * - * The directive won't add duplicate classes if a particular class was already set. - * - * When the expression changes, the previously added classes are removed and only then the classes - * new classes are added. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result - *   of the evaluation can be a string representing space delimited class - *   names, an array, or a map of class names to boolean values. - * - * @example -   <doc:example> -     <doc:source> -      <input type="button" value="set" ng:click="myVar='ng-invalid'"> -      <input type="button" value="clear" ng:click="myVar=''"> -      <br> -      <span ng:class="myVar">Sample Text     </span> -     </doc:source> -     <doc:scenario> -       it('should check ng:class', function() { -         expect(element('.doc-example-live span').prop('className')).not(). -           toMatch(/ng-invalid/); - -         using('.doc-example-live').element(':button:first').click(); - -         expect(element('.doc-example-live span').prop('className')). -           toMatch(/ng-invalid/); - -         using('.doc-example-live').element(':button:last').click(); - -         expect(element('.doc-example-live span').prop('className')).not(). -           toMatch(/ng-invalid/); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngClassDirective = classDirective('', true); - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:class-odd - * - * @description - * The `ng:class-odd` and `ng:class-even` works exactly as - * {@link angular.module.ng.$compileProvider.directive.ng:class ng:class}, except it works in conjunction with `ng:repeat` and - * takes affect only on odd (even) rows. - * - * This directive can be applied only within a scope of an - * {@link angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat}. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result - *   of the evaluation can be a string representing space delimited class names or an array. - * - * @example -   <doc:example> -     <doc:source> -        <ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']"> -          <li ng:repeat="name in names"> -           <span ng:class-odd="'ng-format-negative'" -                 ng:class-even="'ng-invalid'"> -             {{name}}       -           </span> -          </li> -        </ol> -     </doc:source> -     <doc:scenario> -       it('should check ng:class-odd and ng:class-even', function() { -         expect(element('.doc-example-live li:first span').prop('className')). -           toMatch(/ng-format-negative/); -         expect(element('.doc-example-live li:last span').prop('className')). -           toMatch(/ng-invalid/); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngClassOddDirective = classDirective('Odd', 0); - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:class-even - * - * @description - * The `ng:class-odd` and `ng:class-even` works exactly as - * {@link angular.module.ng.$compileProvider.directive.ng:class ng:class}, except it works in conjunction with `ng:repeat` and - * takes affect only on odd (even) rows. - * - * This directive can be applied only within a scope of an - * {@link angular.module.ng.$compileProvider.directive.ng:repeat ng:repeat}. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} to eval. The result - *   of the evaluation can be a string representing space delimited class names or an array. - * - * @example -   <doc:example> -     <doc:source> -        <ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']"> -          <li ng:repeat="name in names"> -           <span ng:class-odd="'odd'" ng:class-even="'even'"> -             {{name}}       -           </span> -          </li> -        </ol> -     </doc:source> -     <doc:scenario> -       it('should check ng:class-odd and ng:class-even', function() { -         expect(element('.doc-example-live li:first span').prop('className')). -           toMatch(/odd/); -         expect(element('.doc-example-live li:last span').prop('className')). -           toMatch(/even/); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngClassEvenDirective = classDirective('Even', 1); - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:show - * - * @description - * The `ng:show` and `ng:hide` directives show or hide a portion of the DOM tree (HTML) - * conditionally. - * - * @element ANY - * @param {expression} expression If the {@link guide/dev_guide.expressions expression} is truthy - *     then the element is shown or hidden respectively. - * - * @example -   <doc:example> -     <doc:source> -        Click me: <input type="checkbox" ng:model="checked"><br/> -        Show: <span ng:show="checked">I show up when your checkbox is checked.</span> <br/> -        Hide: <span ng:hide="checked">I hide when your checkbox is checked.</span> -     </doc:source> -     <doc:scenario> -       it('should check ng:show / ng:hide', function() { -         expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); -         expect(element('.doc-example-live span:last:visible').count()).toEqual(1); - -         input('checked').check(); - -         expect(element('.doc-example-live span:first:visible').count()).toEqual(1); -         expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); -       }); -     </doc:scenario> -   </doc:example> - */ -//TODO(misko): refactor to remove element from the DOM -var ngShowDirective = ngDirective(function(scope, element, attr){ -  scope.$watch(attr.ngShow, function(value){ -    element.css('display', toBoolean(value) ? '' : 'none'); -  }); -}); - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:hide - * - * @description - * The `ng:hide` and `ng:show` directives hide or show a portion - * of the HTML conditionally. - * - * @element ANY - * @param {expression} expression If the {@link guide/dev_guide.expressions expression} truthy then - *     the element is shown or hidden respectively. - * - * @example -   <doc:example> -     <doc:source> -        Click me: <input type="checkbox" ng:model="checked"><br/> -        Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/> -        Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span> -     </doc:source> -     <doc:scenario> -       it('should check ng:show / ng:hide', function() { -         expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); -         expect(element('.doc-example-live span:last:visible').count()).toEqual(1); - -         input('checked').check(); - -         expect(element('.doc-example-live span:first:visible').count()).toEqual(1); -         expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); -       }); -     </doc:scenario> -   </doc:example> - */ -//TODO(misko): refactor to remove element from the DOM -var ngHideDirective = ngDirective(function(scope, element, attr){ -  scope.$watch(attr.ngHide, function(value){ -    element.css('display', toBoolean(value) ? 'none' : ''); -  }); -}); - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:style - * - * @description - * The ng:style allows you to set CSS style on an HTML element conditionally. - * - * @element ANY - * @param {expression} expression {@link guide/dev_guide.expressions Expression} which evals to an - *      object whose keys are CSS style names and values are corresponding values for those CSS - *      keys. - * - * @example -   <doc:example> -     <doc:source> -        <input type="button" value="set" ng:click="myStyle={color:'red'}"> -        <input type="button" value="clear" ng:click="myStyle={}"> -        <br/> -        <span ng:style="myStyle">Sample Text</span> -        <pre>myStyle={{myStyle}}</pre> -     </doc:source> -     <doc:scenario> -       it('should check ng:style', function() { -         expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); -         element('.doc-example-live :button[value=set]').click(); -         expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)'); -         element('.doc-example-live :button[value=clear]').click(); -         expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); -       }); -     </doc:scenario> -   </doc:example> - */ -var ngStyleDirective = ngDirective(function(scope, element, attr) { -  scope.$watch(attr.ngStyle, function(newStyles, oldStyles) { -    if (oldStyles && (newStyles !== oldStyles)) { -      forEach(oldStyles, function(val, style) { element.css(style, '');}); -    } -    if (newStyles) element.css(newStyles); -  }, true); -}); - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:cloak - * - * @description - * The `ng:cloak` directive is used to prevent the Angular html template from being briefly - * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this - * directive to avoid the undesirable flicker effect caused by the html template display. - * - * The directive can be applied to the `<body>` element, but typically a fine-grained application is - * prefered in order to benefit from progressive rendering of the browser view. - * - * `ng:cloak` works in cooperation with a css rule that is embedded within `angular.js` and - *  `angular.min.js` files. Following is the css rule: - * - * <pre> - * [ng\:cloak], .ng-cloak { - *   display: none; - * } - * </pre> - * - * When this css rule is loaded by the browser, all html elements (including their children) that - * are tagged with the `ng:cloak` directive are hidden. When Angular comes across this directive - * during the compilation of the template it deletes the `ng:cloak` element attribute, which - * makes the compiled element visible. - * - * For the best result, `angular.js` script must be loaded in the head section of the html file; - * alternatively, the css rule (above) must be included in the external stylesheet of the - * application. - * - * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they - * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css - * class `ng-cloak` in addition to `ng:cloak` directive as shown in the example below. - * - * @element ANY - * - * @example -   <doc:example> -     <doc:source> -        <div id="template1" ng:cloak>{{ 'hello' }}</div> -        <div id="template2" ng:cloak class="ng-cloak">{{ 'hello IE7' }}</div> -     </doc:source> -     <doc:scenario> -       it('should remove the template directive and css class', function() { -         expect(element('.doc-example-live #template1').attr('ng:cloak')). -           not().toBeDefined(); -         expect(element('.doc-example-live #template2').attr('ng:cloak')). -           not().toBeDefined(); -       }); -     </doc:scenario> -   </doc:example> - * - */ -var ngCloakDirective = ngDirective({ -  compile: function(element, attr) { -    attr.$set('ngCloak', undefined); -    element.removeClass('ng-cloak'); -  } -}); - -function ngAttributeAliasDirective(propName, attrName) { -  ngAttributeAliasDirectives[directiveNormalize('ng-' + attrName)] = valueFn( -    function(scope, element, attr) { -      attr.$observe(directiveNormalize('ng-' + attrName), function(value) { -        attr.$set(attrName, value); -      }); -    } -  ); -} -var ngAttributeAliasDirectives = {}; -forEach(BOOLEAN_ATTR, ngAttributeAliasDirective); -ngAttributeAliasDirective(null, 'src'); - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:transclude - * - * @description - * Insert the transcluded DOM here. - * - * @element ANY - * - * @example -   <doc:example module="transclude"> -     <doc:source> -       <script> -         function Ctrl($scope) { -           $scope.title = 'Lorem Ipsum'; -           $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; -         } - -         angular.module('transclude', []) -          .directive('pane', function(){ -             return { -               restrict: 'E', -               transclude: true, -               scope: 'isolate', -               locals: { title:'bind' }, -               template: '<div style="border: 1px solid black;">' + -                           '<div style="background-color: gray">{{title}}</div>' + -                           '<div ng-transclude></div>' + -                         '</div>' -             }; -         }); -       </script> -       <div ng:controller="Ctrl"> -         <input ng:model="title"><br> -         <textarea ng:model="text"></textarea> <br/> -         <pane title="{{title}}">{{text}}</pane> -       </div> -     </doc:source> -     <doc:scenario> -        it('should have transcluded', function() { -          input('title').enter('TITLE'); -          input('text').enter('TEXT'); -          expect(binding('title')).toEqual('TITLE'); -          expect(binding('text')).toEqual('TEXT'); -        }); -     </doc:scenario> -   </doc:example> - * - */ -var ngTranscludeDirective = ngDirective({ -  controller: ['$transclude', '$element', function($transclude, $element) { -    $transclude(function(clone) { -      $element.append(clone); -    }); -  }] -}); - - -var styleDirective = valueFn({ -  restrict: 'E', -  terminal: true -}); - - -var onloadDirective = valueFn({ -  restrict: 'AC', -  link: function(scope, elm, attr) { -    var onloadExp = attr.onload || ''; //workaround for jquery bug #7537) - -    scope.$on('$contentLoaded', function(event) { -      scope.$eval(onloadExp); -    }); -  } -}); diff --git a/src/widgets.js b/src/widgets.js index 235fbc2b..e69de29b 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -1,876 +0,0 @@ -'use strict'; - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:include - * @restrict EA - * - * @description - * Fetches, compiles and includes an external HTML fragment. - * - * Keep in mind that Same Origin Policy applies to included resources - * (e.g. ng:include won't work for file:// access). - * - * @scope - * - * @param {string} src angular expression evaluating to URL. If the source is a string constant, - *                 make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. - * @param {Scope=} [scope=new_child_scope] optional expression which evaluates to an - *                 instance of angular.module.ng.$rootScope.Scope to set the HTML fragment to. - * @param {string=} onload Expression to evaluate when a new partial is loaded. - * - * @param {string=} autoscroll Whether `ng:include` should call {@link angular.module.ng.$anchorScroll - *                  $anchorScroll} to scroll the viewport after the content is loaded. - * - *                  - If the attribute is not set, disable scrolling. - *                  - If the attribute is set without value, enable scrolling. - *                  - Otherwise enable scrolling only if the expression evaluates to truthy value. - * - * @example -    <doc:example> -      <doc:source jsfiddle="false"> -       <script> -         function Ctrl($scope) { -           $scope.templates = -             [ { name: 'template1.html', url: 'examples/ng-include/template1.html'} -             , { name: 'template2.html', url: 'examples/ng-include/template2.html'} ]; -           $scope.template = $scope.templates[0]; -         } -       </script> -       <div ng:controller="Ctrl"> -         <select ng:model="template" ng:options="t.name for t in templates"> -          <option value="">(blank)</option> -         </select> -         url of the template: <tt><a href="{{template.url}}">{{template.url}}</a></tt> -         <hr/> -         <div ng-include src="template.url"></div> -       </div> -      </doc:source> -      <doc:scenario> -        it('should load template1.html', function() { -         expect(element('.doc-example-live [ng-include]').text()). -           toBe('Content of template1.html\n'); -        }); -        it('should load template2.html', function() { -         select('template').option('1'); -         expect(element('.doc-example-live [ng-include]').text()). -           toBe('Content of template2.html\n'); -        }); -        it('should change to blank', function() { -         select('template').option(''); -         expect(element('.doc-example-live [ng-include]').text()).toEqual(''); -        }); -      </doc:scenario> -    </doc:example> - */ -var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', -                  function($http,   $templateCache,   $anchorScroll,   $compile) { -  return { -    restrict: 'EA', -    compile: function(element, attr) { -      var srcExp = attr.src, -          scopeExp = attr.scope || '', -          autoScrollExp = attr.autoscroll; - -      return function(scope, element, attr) { -        var changeCounter = 0, -            childScope; - -        function incrementChange() { changeCounter++;} -        scope.$watch(srcExp, incrementChange); -        scope.$watch(function() { -          var includeScope = scope.$eval(scopeExp); -          if (includeScope) return includeScope.$id; -        }, incrementChange); -        scope.$watch(function() {return changeCounter;}, function(newChangeCounter) { -           var src = scope.$eval(srcExp), -               useScope = scope.$eval(scopeExp); - -          function clearContent() { -            // if this callback is still desired -            if (newChangeCounter === changeCounter) { -              if (childScope) childScope.$destroy(); -              childScope = null; -              element.html(''); -            } -          } - -           if (src) { -             $http.get(src, {cache: $templateCache}).success(function(response) { -               // if this callback is still desired -               if (newChangeCounter === changeCounter) { -                 element.html(response); -                 if (childScope) childScope.$destroy(); -                 childScope = useScope ? useScope : scope.$new(); -                 $compile(element.contents())(childScope); -                 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { -                   $anchorScroll(); -                 } -                 scope.$emit('$contentLoaded'); -               } -             }).error(clearContent); -           } else { -             clearContent(); -           } -        }); -      }; -    } -  } -}]; - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:switch - * @restrict EA - * - * @description - * Conditionally change the DOM structure. - * - * @usageContent - * <any ng:switch-when="matchValue1">...</any> - *   <any ng:switch-when="matchValue2">...</any> - *   ... - *   <any ng:switch-default>...</any> - * - * @scope - * @param {*} on expression to match against <tt>ng:switch-when</tt>. - * @paramDescription - * On child elments add: - * - * * `ng:switch-when`: the case statement to match against. If match then this - *   case will be displayed. - * * `ng:switch-default`: the default case when no other casses match. - * - * @example -    <doc:example> -      <doc:source> -        <script> -          function Ctrl($scope) { -            $scope.items = ['settings', 'home', 'other']; -            $scope.selection = $scope.items[0]; -          } -        </script> -        <div ng:controller="Ctrl"> -          <select ng:model="selection" ng:options="item for item in items"> -          </select> -          <tt>selection={{selection}}</tt> -          <hr/> -          <ng:switch on="selection" > -            <div ng:switch-when="settings">Settings Div</div> -            <span ng:switch-when="home">Home Span</span> -            <span ng:switch-default>default</span> -          </ng:switch> -        </div> -      </doc:source> -      <doc:scenario> -        it('should start in settings', function() { -         expect(element('.doc-example-live ng\\:switch').text()).toMatch(/Settings Div/); -        }); -        it('should change to home', function() { -         select('selection').option('home'); -         expect(element('.doc-example-live ng\\:switch').text()).toMatch(/Home Span/); -        }); -        it('should select deafault', function() { -         select('selection').option('other'); -         expect(element('.doc-example-live ng\\:switch').text()).toMatch(/default/); -        }); -      </doc:scenario> -    </doc:example> - */ -var NG_SWITCH = 'ng-switch'; -var ngSwitchDirective = valueFn({ -  restrict: 'EA', -  compile: function(element, attr) { -    var watchExpr = attr.ngSwitch || attr.on, -        cases = {}; - -    element.data(NG_SWITCH, cases); -    return function(scope, element){ -      var selectedTransclude, -          selectedElement; - -      scope.$watch(watchExpr, function(value) { -        if (selectedElement) { -          selectedElement.remove(); -          selectedElement = null; -        } -        if ((selectedTransclude = cases['!' + value] || cases['?'])) { -          scope.$eval(attr.change); -          selectedTransclude(scope.$new(), function(caseElement, scope) { -            selectedElement = caseElement; -            element.append(caseElement); -            element.bind('$destroy', bind(scope, scope.$destroy)); -          }); -        } -      }); -    }; -  } -}); - -var ngSwitchWhenDirective = ngDirective({ -  transclude: 'element', -  priority: 500, -  compile: function(element, attrs, transclude) { -    var cases = element.inheritedData(NG_SWITCH); -    assertArg(cases); -    cases['!' + attrs.ngSwitchWhen] = transclude; -  } -}); - -var ngSwitchDefaultDirective = ngDirective({ -  transclude: 'element', -  priority: 500, -  compile: function(element, attrs, transclude) { -    var cases = element.inheritedData(NG_SWITCH); -    assertArg(cases); -    cases['?'] = transclude; -  } -}); - - -/* - * Modifies the default behavior of html A tag, so that the default action is prevented when href - * attribute is empty. - * - * The reasoning for this change is to allow easy creation of action links with ng:click without - * changing the location or causing page reloads, e.g.: - * <a href="" ng:click="model.$save()">Save</a> - */ -var htmlAnchorDirective = valueFn({ -  restrict: 'E', -  compile: function(element, attr) { -    // turn <a href ng:click="..">link</a> into a link in IE -    // but only if it doesn't have name attribute, in which case it's an anchor -    if (!attr.href) { -      attr.$set('href', ''); -    } - -    return function(scope, element) { -      element.bind('click', function(event){ -        // if we have no href url, then don't navigate anywhere. -        if (!element.attr('href')) { -          event.preventDefault(); -        } -      }); -    } -  } -}); - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:repeat - * - * @description - * The `ng:repeat` widget instantiates a template once per item from a collection. Each template - * instance gets its own scope, where the given loop variable is set to the current collection item, - * and `$index` is set to the item index or key. - * - * Special properties are exposed on the local scope of each template instance, including: - * - *   * `$index` – `{number}` – iterator offset of the repeated element (0..length-1) - *   * `$position` – `{string}` – position of the repeated element in the iterator. One of: - *        * `'first'`, - *        * `'middle'` - *        * `'last'` - * - * Note: Although `ng:repeat` looks like a directive, it is actually an attribute widget. - * - * @element ANY - * @scope - * @priority 1000 - * @param {string} repeat_expression The expression indicating how to enumerate a collection. Two - *   formats are currently supported: - * - *   * `variable in expression` – where variable is the user defined loop variable and `expression` - *     is a scope expression giving the collection to enumerate. - * - *     For example: `track in cd.tracks`. - * - *   * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, - *     and `expression` is the scope expression giving the collection to enumerate. - * - *     For example: `(name, age) in {'adam':10, 'amalie':12}`. - * - * @example - * This example initializes the scope to a list of names and - * then uses `ng:repeat` to display every person: -    <doc:example> -      <doc:source> -        <div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]"> -          I have {{friends.length}} friends. They are: -          <ul> -            <li ng:repeat="friend in friends"> -              [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. -            </li> -          </ul> -        </div> -      </doc:source> -      <doc:scenario> -         it('should check ng:repeat', function() { -           var r = using('.doc-example-live').repeater('ul li'); -           expect(r.count()).toBe(2); -           expect(r.row(0)).toEqual(["1","John","25"]); -           expect(r.row(1)).toEqual(["2","Mary","28"]); -         }); -      </doc:scenario> -    </doc:example> - */ -var ngRepeatDirective = ngDirective({ -  transclude: 'element', -  priority: 1000, -  terminal: true, -  compile: function(element, attr, linker) { -    return function(scope, iterStartElement, attr){ -      var expression = attr.ngRepeat; -      var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), -        lhs, rhs, valueIdent, keyIdent; -      if (! match) { -        throw Error("Expected ng:repeat in form of '_item_ in _collection_' but got '" + -          expression + "'."); -      } -      lhs = match[1]; -      rhs = match[2]; -      match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); -      if (!match) { -        throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + -          keyValue + "'."); -      } -      valueIdent = match[3] || match[1]; -      keyIdent = match[2]; - -      // Store a list of elements from previous run. This is a hash where key is the item from the -      // iterator, and the value is an array of objects with following properties. -      //   - scope: bound scope -      //   - element: previous element. -      //   - index: position -      // We need an array of these objects since the same object can be returned from the iterator. -      // We expect this to be a rare case. -      var lastOrder = new HashQueueMap(); -      scope.$watch(function(scope){ -        var index, length, -            collection = scope.$eval(rhs), -            collectionLength = size(collection, true), -            childScope, -            // Same as lastOrder but it has the current state. It will become the -            // lastOrder on the next iteration. -            nextOrder = new HashQueueMap(), -            key, value, // key/value of iteration -            array, last,       // last object information {scope, element, index} -            cursor = iterStartElement;     // current position of the node - -        if (!isArray(collection)) { -          // if object, extract keys, sort them and use to determine order of iteration over obj props -          array = []; -          for(key in collection) { -            if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { -              array.push(key); -            } -          } -          array.sort(); -        } else { -          array = collection || []; -        } - -        // we are not using forEach for perf reasons (trying to avoid #call) -        for (index = 0, length = array.length; index < length; index++) { -          key = (collection === array) ? index : array[index]; -          value = collection[key]; -          last = lastOrder.shift(value); -          if (last) { -            // if we have already seen this object, then we need to reuse the -            // associated scope/element -            childScope = last.scope; -            nextOrder.push(value, last); - -            if (index === last.index) { -              // do nothing -              cursor = last.element; -            } else { -              // existing item which got moved -              last.index = index; -              // This may be a noop, if the element is next, but I don't know of a good way to -              // figure this out,  since it would require extra DOM access, so let's just hope that -              // the browsers realizes that it is noop, and treats it as such. -              cursor.after(last.element); -              cursor = last.element; -            } -          } else { -            // new item which we don't know about -            childScope = scope.$new(); -          } - -          childScope[valueIdent] = value; -          if (keyIdent) childScope[keyIdent] = key; -          childScope.$index = index; -          childScope.$position = index === 0 ? -              'first' : -              (index == collectionLength - 1 ? 'last' : 'middle'); - -          if (!last) { -            linker(childScope, function(clone){ -              cursor.after(clone); -              last = { -                  scope: childScope, -                  element: (cursor = clone), -                  index: index -                }; -              nextOrder.push(value, last); -            }); -          } -        } - -        //shrink children -        for (key in lastOrder) { -          if (lastOrder.hasOwnProperty(key)) { -            array = lastOrder[key]; -            while(array.length) { -              value = array.pop(); -              value.element.remove(); -              value.scope.$destroy(); -            } -          } -        } - -        lastOrder = nextOrder; -      }); -    }; -  } -}); - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:non-bindable - * - * @description - * Sometimes it is necessary to write code which looks like bindings but which should be left alone - * by angular. Use `ng:non-bindable` to make angular ignore a chunk of HTML. - * - * Note: `ng:non-bindable` looks like a directive, but is actually an attribute widget. - * - * @element ANY - * - * @example - * In this example there are two location where a simple binding (`{{}}`) is present, but the one - * wrapped in `ng:non-bindable` is left alone. - * - * @example -    <doc:example> -      <doc:source> -        <div>Normal: {{1 + 2}}</div> -        <div ng:non-bindable>Ignored: {{1 + 2}}</div> -      </doc:source> -      <doc:scenario> -       it('should check ng:non-bindable', function() { -         expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); -         expect(using('.doc-example-live').element('div:last').text()). -           toMatch(/1 \+ 2/); -       }); -      </doc:scenario> -    </doc:example> - */ -var ngNonBindableDirective = ngDirective({ terminal: true }); - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:view - * @restrict ECA - * - * @description - * # Overview - * `ng:view` is a directive that complements the {@link angular.module.ng.$route $route} service by - * including the rendered template of the current route into the main layout (`index.html`) file. - * Every time the current route changes, the included view changes with it according to the - * configuration of the `$route` service. - * - * @scope - * @example -    <doc:example module="ngView"> -      <doc:source> -        <script type="text/ng-template" id="examples/book.html"> -          controller: {{name}}<br /> -          Book Id: {{params.bookId}}<br /> -        </script> - -        <script type="text/ng-template" id="examples/chapter.html"> -          controller: {{name}}<br /> -          Book Id: {{params.bookId}}<br /> -          Chapter Id: {{params.chapterId}} -        </script> - -        <script> -          angular.module('ngView', [], function($routeProvider, $locationProvider) { -            $routeProvider.when('/Book/:bookId', { -              template: 'examples/book.html', -              controller: BookCntl -            }); -            $routeProvider.when('/Book/:bookId/ch/:chapterId', { -              template: 'examples/chapter.html', -              controller: ChapterCntl -            }); - -            // configure html5 to get links working on jsfiddle -            $locationProvider.html5Mode(true); -          }); - -          function MainCntl($scope, $route, $routeParams, $location) { -            $scope.$route = $route; -            $scope.$location = $location; -            $scope.$routeParams = $routeParams; -          } - -          function BookCntl($scope, $routeParams) { -            $scope.name = "BookCntl"; -            $scope.params = $routeParams; -          } - -          function ChapterCntl($scope, $routeParams) { -            $scope.name = "ChapterCntl"; -            $scope.params = $routeParams; -          } -        </script> - -        <div ng:controller="MainCntl"> -          Choose: -          <a href="/Book/Moby">Moby</a> | -          <a href="/Book/Moby/ch/1">Moby: Ch1</a> | -          <a href="/Book/Gatsby">Gatsby</a> | -          <a href="/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | -          <a href="/Book/Scarlet">Scarlet Letter</a><br/> - -          <ng:view></ng:view> -          <hr /> - -          <pre>$location.path() = {{$location.path()}}</pre> -          <pre>$route.current.template = {{$route.current.template}}</pre> -          <pre>$route.current.params = {{$route.current.params}}</pre> -          <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> -          <pre>$routeParams = {{$routeParams}}</pre> -        </div> -      </doc:source> -      <doc:scenario> -        it('should load and compile correct template', function() { -          element('a:contains("Moby: Ch1")').click(); -          var content = element('.doc-example-live ng\\:view').text(); -          expect(content).toMatch(/controller\: ChapterCntl/); -          expect(content).toMatch(/Book Id\: Moby/); -          expect(content).toMatch(/Chapter Id\: 1/); - -          element('a:contains("Scarlet")').click(); -          content = element('.doc-example-live ng\\:view').text(); -          expect(content).toMatch(/controller\: BookCntl/); -          expect(content).toMatch(/Book Id\: Scarlet/); -        }); -      </doc:scenario> -    </doc:example> - */ -var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile', -                       '$controller', -               function($http,   $templateCache,   $route,   $anchorScroll,   $compile, -                        $controller) { -  return { -    restrict: 'ECA', -    terminal: true, -    link: function(scope, element) { -      var changeCounter = 0, -          lastScope; - -      scope.$on('$afterRouteChange', function(event, next, previous) { -        changeCounter++; -      }); - -      scope.$watch(function() {return changeCounter;}, function(newChangeCounter) { -        var template = $route.current && $route.current.template; - -        function destroyLastScope() { -          if (lastScope) { -            lastScope.$destroy(); -            lastScope = null; -          } -        } - -        function clearContent() { -          // ignore callback if another route change occured since -          if (newChangeCounter == changeCounter) { -            element.html(''); -          } -          destroyLastScope(); -        } - -        if (template) { -          $http.get(template, {cache: $templateCache}).success(function(response) { -            // ignore callback if another route change occured since -            if (newChangeCounter == changeCounter) { -              element.html(response); -              destroyLastScope(); - -              var link = $compile(element.contents()), -                  current = $route.current; - -              lastScope = current.scope = scope.$new(); -              if (current.controller) { -                $controller(current.controller, {$scope: lastScope}); -              } - -              link(lastScope); -              lastScope.$emit('$contentLoaded'); - -              // $anchorScroll might listen on event... -              $anchorScroll(); -            } -          }).error(clearContent); -        } else { -          clearContent(); -        } -      }); -    } -  }; -}]; - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.ng:pluralize - * @restrict EA - * - * @description - * # Overview - * ng:pluralize is a widget that displays messages according to en-US localization rules. - * These rules are bundled with angular.js and the rules can be overridden - * (see {@link guide/dev_guide.i18n Angular i18n} dev guide). You configure ng:pluralize by - * specifying the mappings between - * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html - * plural categories} and the strings to be displayed. - * - * # Plural categories and explicit number rules - * There are two - * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html - * plural categories} in Angular's default en-US locale: "one" and "other". - * - * While a pural category may match many numbers (for example, in en-US locale, "other" can match - * any number that is not 1), an explicit number rule can only match one number. For example, the - * explicit number rule for "3" matches the number 3. You will see the use of plural categories - * and explicit number rules throughout later parts of this documentation. - * - * # Configuring ng:pluralize - * You configure ng:pluralize by providing 2 attributes: `count` and `when`. - * You can also provide an optional attribute, `offset`. - * - * The value of the `count` attribute can be either a string or an {@link guide/dev_guide.expressions - * Angular expression}; these are evaluated on the current scope for its binded value. - * - * The `when` attribute specifies the mappings between plural categories and the actual - * string to be displayed. The value of the attribute should be a JSON object so that Angular - * can interpret it correctly. - * - * The following example shows how to configure ng:pluralize: - * - * <pre> - * <ng:pluralize count="personCount" -                 when="{'0': 'Nobody is viewing.', - *                      'one': '1 person is viewing.', - *                      'other': '{} people are viewing.'}"> - * </ng:pluralize> - *</pre> - * - * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not - * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" - * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for - * other numbers, for example 12, so that instead of showing "12 people are viewing", you can - * show "a dozen people are viewing". - * - * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted - * into pluralized strings. In the previous example, Angular will replace `{}` with - * <span ng:non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder - * for <span ng:non-bindable>{{numberExpression}}</span>. - * - * # Configuring ng:pluralize with offset - * The `offset` attribute allows further customization of pluralized text, which can result in - * a better user experience. For example, instead of the message "4 people are viewing this document", - * you might display "John, Kate and 2 others are viewing this document". - * The offset attribute allows you to offset a number by any desired value. - * Let's take a look at an example: - * - * <pre> - * <ng:pluralize count="personCount" offset=2 - *               when="{'0': 'Nobody is viewing.', - *                      '1': '{{person1}} is viewing.', - *                      '2': '{{person1}} and {{person2}} are viewing.', - *                      'one': '{{person1}}, {{person2}} and one other person are viewing.', - *                      'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> - * </ng:pluralize> - * </pre> - * - * Notice that we are still using two plural categories(one, other), but we added - * three explicit number rules 0, 1 and 2. - * When one person, perhaps John, views the document, "John is viewing" will be shown. - * When three people view the document, no explicit number rule is found, so - * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. - * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" - * is shown. - * - * Note that when you specify offsets, you must provide explicit number rules for - * numbers from 0 up to and including the offset. If you use an offset of 3, for example, - * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for - * plural categories "one" and "other". - * - * @param {string|expression} count The variable to be bounded to. - * @param {string} when The mapping between plural category to its correspoding strings. - * @param {number=} offset Offset to deduct from the total number. - * - * @example -    <doc:example> -      <doc:source> -        <script> -          function Ctrl($scope) { -            $scope.person1 = 'Igor'; -            $scope.person2 = 'Misko'; -            $scope.personCount = 1; -          } -        </script> -        <div ng:controller="Ctrl"> -          Person 1:<input type="text" ng:model="person1" value="Igor" /><br/> -          Person 2:<input type="text" ng:model="person2" value="Misko" /><br/> -          Number of People:<input type="text" ng:model="personCount" value="1" /><br/> - -          <!--- Example with simple pluralization rules for en locale ---> -          Without Offset: -          <ng-pluralize count="personCount" -                        when="{'0': 'Nobody is viewing.', -                               'one': '1 person is viewing.', -                               'other': '{} people are viewing.'}"> -          </ng-pluralize><br> - -          <!--- Example with offset ---> -          With Offset(2): -          <ng-pluralize count="personCount" offset=2 -                        when="{'0': 'Nobody is viewing.', -                               '1': '{{person1}} is viewing.', -                               '2': '{{person1}} and {{person2}} are viewing.', -                               'one': '{{person1}}, {{person2}} and one other person are viewing.', -                               'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> -          </ng-pluralize> -        </div> -      </doc:source> -      <doc:scenario> -        it('should show correct pluralized string', function() { -          expect(element('.doc-example-live ng-pluralize:first').text()). -                                             toBe('1 person is viewing.'); -          expect(element('.doc-example-live ng-pluralize:last').text()). -                                                toBe('Igor is viewing.'); - -          using('.doc-example-live').input('personCount').enter('0'); -          expect(element('.doc-example-live ng-pluralize:first').text()). -                                               toBe('Nobody is viewing.'); -          expect(element('.doc-example-live ng-pluralize:last').text()). -                                              toBe('Nobody is viewing.'); - -          using('.doc-example-live').input('personCount').enter('2'); -          expect(element('.doc-example-live ng-pluralize:first').text()). -                                            toBe('2 people are viewing.'); -          expect(element('.doc-example-live ng-pluralize:last').text()). -                              toBe('Igor and Misko are viewing.'); - -          using('.doc-example-live').input('personCount').enter('3'); -          expect(element('.doc-example-live ng-pluralize:first').text()). -                                            toBe('3 people are viewing.'); -          expect(element('.doc-example-live ng-pluralize:last').text()). -                              toBe('Igor, Misko and one other person are viewing.'); - -          using('.doc-example-live').input('personCount').enter('4'); -          expect(element('.doc-example-live ng-pluralize:first').text()). -                                            toBe('4 people are viewing.'); -          expect(element('.doc-example-live ng-pluralize:last').text()). -                              toBe('Igor, Misko and 2 other people are viewing.'); -        }); - -        it('should show data-binded names', function() { -          using('.doc-example-live').input('personCount').enter('4'); -          expect(element('.doc-example-live ng-pluralize:last').text()). -              toBe('Igor, Misko and 2 other people are viewing.'); - -          using('.doc-example-live').input('person1').enter('Di'); -          using('.doc-example-live').input('person2').enter('Vojta'); -          expect(element('.doc-example-live ng-pluralize:last').text()). -              toBe('Di, Vojta and 2 other people are viewing.'); -        }); -      </doc:scenario> -    </doc:example> - */ -var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { -  var BRACE = /{}/g; -  return { -    restrict: 'EA', -    link: function(scope, element, attr) { -      var numberExp = attr.count, -          whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs -          offset = attr.offset || 0, -          whens = scope.$eval(whenExp), -          whensExpFns = {}; - -      forEach(whens, function(expression, key) { -        whensExpFns[key] = -          $interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}')); -      }); - -      scope.$watch(function() { -        var value = parseFloat(scope.$eval(numberExp)); - -        if (!isNaN(value)) { -          //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, -          //check it against pluralization rules in $locale service -          if (!whens[value]) value = $locale.pluralCat(value - offset); -           return whensExpFns[value](scope, element, true); -        } else { -          return ''; -        } -      }, function(newVal) { -        element.text(newVal); -      }); -    } -  }; -}]; - - -/** - * @ngdoc directive - * @name angular.module.ng.$compileProvider.directive.script - * - * @description - * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the - * template can be used by `ng:include`, `ng:view` or directive templates. - * - * @restrict E - * - * @example -  <doc:example> -    <doc:source> -      <script type="text/ng-template" id="/tpl.html"> -        Content of the template. -      </script> - -      <a ng:click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> -      <div id="tpl-content" ng-include src="currentTpl"></div> -    </doc:source> -    <doc:scenario> -      it('should load template defined inside script tag', function() { -        element('#tpl-link').click(); -        expect(element('#tpl-content').text()).toMatch(/Content of the template/); -      }); -    </doc:scenario> -  </doc:example> - */ -var scriptDirective = ['$templateCache', function($templateCache) { -  return { -    restrict: 'E', -    terminal: true, -    compile: function(element, attr) { -      if (attr.type == 'text/ng-template') { -        var templateUrl = attr.id; -        $templateCache.put(templateUrl, element.text()); -      } -    } -  }; -}]; diff --git a/test/directive/aSpec.js b/test/directive/aSpec.js new file mode 100644 index 00000000..8aa2449d --- /dev/null +++ b/test/directive/aSpec.js @@ -0,0 +1,46 @@ +'use strict'; + +describe('a', function() { +  var element; + + +  afterEach(function(){ +    dealoc(element); +  }); + + +  it('should prevent default action to be executed when href is empty', +      inject(function($rootScope, $compile) { +    var orgLocation = document.location.href, +        preventDefaultCalled = false, +        event; + +    element = $compile('<a href="">empty link</a>')($rootScope); + +    if (msie < 9) { + +      event = document.createEventObject(); +      expect(event.returnValue).not.toBeDefined(); +      element[0].fireEvent('onclick', event); +      expect(event.returnValue).toEqual(false); + +    } else { + +      event = document.createEvent('MouseEvent'); +      event.initMouseEvent( +        'click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + +      event.preventDefaultOrg = event.preventDefault; +      event.preventDefault = function() { +        preventDefaultCalled = true; +        if (this.preventDefaultOrg) this.preventDefaultOrg(); +      }; + +      element[0].dispatchEvent(event); + +      expect(preventDefaultCalled).toEqual(true); +    } + +    expect(document.location.href).toEqual(orgLocation); +  })); +}); diff --git a/test/markupSpec.js b/test/directive/booleanAttrDirSpecs.js index 6f8e518e..2c47d81a 100644 --- a/test/markupSpec.js +++ b/test/directive/booleanAttrDirSpecs.js @@ -1,88 +1,12 @@  'use strict'; -describe("markups", function() { +describe('boolean attr directives', function() {    var element;    afterEach(function() {      dealoc(element);    }); -  it('should translate {{}} in text', inject(function($rootScope, $compile) { -    element = $compile('<div>hello {{name}}!</div>')($rootScope) -    $rootScope.$digest(); -    expect(sortedHtml(element)).toEqual('<div>hello !</div>'); -    $rootScope.name = 'Misko'; -    $rootScope.$digest(); -    expect(sortedHtml(element)).toEqual('<div>hello Misko!</div>'); -  })); - -  it('should translate {{}} in terminal nodes', inject(function($rootScope, $compile) { -    element = $compile('<select ng:model="x"><option value="">Greet {{name}}!</option></select>')($rootScope) -    $rootScope.$digest(); -    expect(sortedHtml(element).replace(' selected="true"', '')). -      toEqual('<select ng:model="x">' + -                '<option>Greet !</option>' + -              '</select>'); -    $rootScope.name = 'Misko'; -    $rootScope.$digest(); -    expect(sortedHtml(element).replace(' selected="true"', '')). -      toEqual('<select ng:model="x">' + -                '<option>Greet Misko!</option>' + -              '</select>'); -  })); - -  it('should translate {{}} in attributes', inject(function($rootScope, $compile) { -    element = $compile('<div src="http://server/{{path}}.png"/>')($rootScope) -    $rootScope.path = 'a/b'; -    $rootScope.$digest(); -    expect(element.attr('src')).toEqual("http://server/a/b.png"); -  })); - -  describe('OPTION value', function() { -    beforeEach(function() { -      this.addMatchers({ -        toHaveValue: function(expected){ -          this.message = function() { -            return 'Expected "' + this.actual.html() + '" to have value="' + expected + '".'; -          }; - -          var value; -          htmlParser(this.actual.html(), { -            start:function(tag, attrs){ -              value = attrs.value; -            }, -            end:noop, -            chars:noop -          }); -          return trim(value) == trim(expected); -        } -      }); -    }); - - -    it('should populate value attribute on OPTION', inject(function($rootScope, $compile) { -      element = $compile('<select ng:model="x"><option>abc</option></select>')($rootScope) -      expect(element).toHaveValue('abc'); -    })); - -    it('should ignore value if already exists', inject(function($rootScope, $compile) { -      element = $compile('<select ng:model="x"><option value="abc">xyz</option></select>')($rootScope) -      expect(element).toHaveValue('abc'); -    })); - -    it('should set value even if newlines present', inject(function($rootScope, $compile) { -      element = $compile('<select ng:model="x"><option attr="\ntext\n" \n>\nabc\n</option></select>')($rootScope) -      expect(element).toHaveValue('\nabc\n'); -    })); - -    it('should set value even if self closing HTML', inject(function($rootScope, $compile) { -      // IE removes the \n from option, which makes this test pointless -      if (msie) return; -      element = $compile('<select ng:model="x"><option>\n</option></select>')($rootScope) -      expect(element).toHaveValue('\n'); -    })); - -  });    it('should bind href', inject(function($rootScope, $compile) {      element = $compile('<a ng:href="{{url}}"></a>')($rootScope) @@ -91,6 +15,7 @@ describe("markups", function() {      expect(element.attr('href')).toEqual('http://server');    })); +    it('should bind disabled', inject(function($rootScope, $compile) {      element = $compile('<button ng:disabled="{{isDisabled}}">Button</button>')($rootScope)      $rootScope.isDisabled = false; @@ -101,6 +26,7 @@ describe("markups", function() {      expect(element.attr('disabled')).toBeTruthy();    })); +    it('should bind checked', inject(function($rootScope, $compile) {      element = $compile('<input type="checkbox" ng:checked="{{isChecked}}" />')($rootScope)      $rootScope.isChecked = false; @@ -111,6 +37,7 @@ describe("markups", function() {      expect(element.attr('checked')).toBeTruthy();    })); +    it('should bind selected', inject(function($rootScope, $compile) {      element = $compile('<select><option value=""></option><option ng:selected="{{isSelected}}">Greetings!</option></select>')($rootScope)      jqLite(document.body).append(element) @@ -122,6 +49,7 @@ describe("markups", function() {      expect(element.children()[1].selected).toBeTruthy();    })); +    it('should bind readonly', inject(function($rootScope, $compile) {      element = $compile('<input type="text" ng:readonly="{{isReadonly}}" />')($rootScope)      $rootScope.isReadonly=false; @@ -132,6 +60,7 @@ describe("markups", function() {      expect(element.attr('readOnly')).toBeTruthy();    })); +    it('should bind multiple', inject(function($rootScope, $compile) {      element = $compile('<select ng:multiple="{{isMultiple}}"></select>')($rootScope)      $rootScope.isMultiple=false; @@ -142,6 +71,7 @@ describe("markups", function() {      expect(element.attr('multiple')).toBeTruthy();    })); +    it('should bind src', inject(function($rootScope, $compile) {      element = $compile('<div ng:src="{{url}}" />')($rootScope)      $rootScope.url = 'http://localhost/'; @@ -149,6 +79,7 @@ describe("markups", function() {      expect(element.attr('src')).toEqual('http://localhost/');    })); +    it('should bind href and merge with other attrs', inject(function($rootScope, $compile) {      element = $compile('<a ng:href="{{url}}" rel="{{rel}}"></a>')($rootScope);      $rootScope.url = 'http://server'; @@ -158,6 +89,7 @@ describe("markups", function() {      expect(element.attr('rel')).toEqual('REL');    })); +    it('should bind Text with no Bindings', inject(function($compile, $rootScope) {      forEach(['checked', 'disabled', 'multiple', 'readonly', 'selected'], function(name) {        element = $compile('<div ng:' + name + '="some"></div>')($rootScope) @@ -177,4 +109,3 @@ describe("markups", function() {      dealoc(element);    }));  }); - diff --git a/test/widget/formSpec.js b/test/directive/formSpec.js index 6387241f..6387241f 100644 --- a/test/widget/formSpec.js +++ b/test/directive/formSpec.js diff --git a/test/widget/inputSpec.js b/test/directive/inputSpec.js index d9f6b7b3..d9f6b7b3 100644 --- a/test/widget/inputSpec.js +++ b/test/directive/inputSpec.js diff --git a/test/directive/ngBindSpec.js b/test/directive/ngBindSpec.js new file mode 100644 index 00000000..e8c07494 --- /dev/null +++ b/test/directive/ngBindSpec.js @@ -0,0 +1,113 @@ +'use strict'; + +describe('ng:bind-*', function() { +  var element; + + +  afterEach(function() { +    dealoc(element); +  }); + + +  describe('ng:bind', function() { + +    it('should set text', inject(function($rootScope, $compile) { +      element = $compile('<div ng:bind="a"></div>')($rootScope); +      expect(element.text()).toEqual(''); +      $rootScope.a = 'misko'; +      $rootScope.$digest(); +      expect(element.hasClass('ng-binding')).toEqual(true); +      expect(element.text()).toEqual('misko'); +    })); + +    it('should set text to blank if undefined', inject(function($rootScope, $compile) { +      element = $compile('<div ng:bind="a"></div>')($rootScope); +      $rootScope.a = 'misko'; +      $rootScope.$digest(); +      expect(element.text()).toEqual('misko'); +      $rootScope.a = undefined; +      $rootScope.$digest(); +      expect(element.text()).toEqual(''); +      $rootScope.a = null; +      $rootScope.$digest(); +      expect(element.text()).toEqual(''); +    })); + +    it('should set html', inject(function($rootScope, $compile) { +      element = $compile('<div ng:bind-html="html"></div>')($rootScope); +      $rootScope.html = '<div unknown>hello</div>'; +      $rootScope.$digest(); +      expect(lowercase(element.html())).toEqual('<div>hello</div>'); +    })); + +    it('should set unsafe html', inject(function($rootScope, $compile) { +      element = $compile('<div ng:bind-html-unsafe="html"></div>')($rootScope); +      $rootScope.html = '<div onclick="">hello</div>'; +      $rootScope.$digest(); +      expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>'); +    })); + +    it('should suppress rendering of falsy values', inject(function($rootScope, $compile) { +      element = $compile('<div>{{ null }}{{ undefined }}{{ "" }}-{{ 0 }}{{ false }}</div>')($rootScope); +      $rootScope.$digest(); +      expect(element.text()).toEqual('-0false'); +    })); + +    it('should render object as JSON ignore $$', inject(function($rootScope, $compile) { +      element = $compile('<div>{{ {key:"value", $$key:"hide"}  }}</div>')($rootScope); +      $rootScope.$digest(); +      expect(fromJson(element.text())).toEqual({key:'value'}); +    })); +  }); + + +  describe('ng:bind-template', function() { + +    it('should ng:bind-template', inject(function($rootScope, $compile) { +      element = $compile('<div ng:bind-template="Hello {{name}}!"></div>')($rootScope); +      $rootScope.name = 'Misko'; +      $rootScope.$digest(); +      expect(element.hasClass('ng-binding')).toEqual(true); +      expect(element.text()).toEqual('Hello Misko!'); +    })); + +    it('should render object as JSON ignore $$', inject(function($rootScope, $compile) { +      element = $compile('<pre>{{ {key:"value", $$key:"hide"}  }}</pre>')($rootScope); +      $rootScope.$digest(); +      expect(fromJson(element.text())).toEqual({key:'value'}); +    })); +  }); + + +  describe('ng:bind-attr', function() { +    it('should bind attributes', inject(function($rootScope, $compile) { +      element = $compile('<div ng:bind-attr="{src:\'http://localhost/mysrc\', alt:\'myalt\'}"/>')($rootScope); +      $rootScope.$digest(); +      expect(element.attr('src')).toEqual('http://localhost/mysrc'); +      expect(element.attr('alt')).toEqual('myalt'); +    })); + +    it('should not pretty print JSON in attributes', inject(function($rootScope, $compile) { +      element = $compile('<img alt="{{ {a:1} }}"/>')($rootScope); +      $rootScope.$digest(); +      expect(element.attr('alt')).toEqual('{"a":1}'); +    })); + +    it('should remove special attributes on false', inject(function($rootScope, $compile) { +      element = $compile('<input ng:bind-attr="{disabled:\'{{disabled}}\', readonly:\'{{readonly}}\', checked:\'{{checked}}\'}"/>')($rootScope); +      var input = element[0]; +      expect(input.disabled).toEqual(false); +      expect(input.readOnly).toEqual(false); +      expect(input.checked).toEqual(false); + +      $rootScope.disabled = true; +      $rootScope.readonly = true; +      $rootScope.checked = true; +      $rootScope.$digest(); + +      expect(input.disabled).toEqual(true); +      expect(input.readOnly).toEqual(true); +      expect(input.checked).toEqual(true); +    })); +  }); +}); diff --git a/test/directive/ngClassSpec.js b/test/directive/ngClassSpec.js new file mode 100644 index 00000000..a8b6b17e --- /dev/null +++ b/test/directive/ngClassSpec.js @@ -0,0 +1,204 @@ +'use strict'; + +describe('ng:class', function() { +  var element; + + +  afterEach(function() { +    dealoc(element); +  }); + + +  it('should add new and remove old classes dynamically', inject(function($rootScope, $compile) { +    element = $compile('<div class="existing" ng:class="dynClass"></div>')($rootScope); +    $rootScope.dynClass = 'A'; +    $rootScope.$digest(); +    expect(element.hasClass('existing')).toBe(true); +    expect(element.hasClass('A')).toBe(true); + +    $rootScope.dynClass = 'B'; +    $rootScope.$digest(); +    expect(element.hasClass('existing')).toBe(true); +    expect(element.hasClass('A')).toBe(false); +    expect(element.hasClass('B')).toBe(true); + +    delete $rootScope.dynClass; +    $rootScope.$digest(); +    expect(element.hasClass('existing')).toBe(true); +    expect(element.hasClass('A')).toBe(false); +    expect(element.hasClass('B')).toBe(false); +  })); + + +  it('should support adding multiple classes via an array', inject(function($rootScope, $compile) { +    element = $compile('<div class="existing" ng:class="[\'A\', \'B\']"></div>')($rootScope); +    $rootScope.$digest(); +    expect(element.hasClass('existing')).toBeTruthy(); +    expect(element.hasClass('A')).toBeTruthy(); +    expect(element.hasClass('B')).toBeTruthy(); +  })); + + +  it('should support adding multiple classes conditionally via a map of class names to boolean' + +      'expressions', inject(function($rootScope, $compile) { +    var element = $compile( +        '<div class="existing" ' + +            'ng:class="{A: conditionA, B: conditionB(), AnotB: conditionA&&!conditionB}">' + +        '</div>')($rootScope); +    $rootScope.conditionA = true; +    $rootScope.$digest(); +    expect(element.hasClass('existing')).toBeTruthy(); +    expect(element.hasClass('A')).toBeTruthy(); +    expect(element.hasClass('B')).toBeFalsy(); +    expect(element.hasClass('AnotB')).toBeTruthy(); + +    $rootScope.conditionB = function() { return true }; +    $rootScope.$digest(); +    expect(element.hasClass('existing')).toBeTruthy(); +    expect(element.hasClass('A')).toBeTruthy(); +    expect(element.hasClass('B')).toBeTruthy(); +    expect(element.hasClass('AnotB')).toBeFalsy(); +  })); + + +  it('should support adding multiple classes via a space delimited string', inject(function($rootScope, $compile) { +    element = $compile('<div class="existing" ng:class="\'A B\'"></div>')($rootScope); +    $rootScope.$digest(); +    expect(element.hasClass('existing')).toBeTruthy(); +    expect(element.hasClass('A')).toBeTruthy(); +    expect(element.hasClass('B')).toBeTruthy(); +  })); + + +  it('should preserve class added post compilation with pre-existing classes', inject(function($rootScope, $compile) { +    element = $compile('<div class="existing" ng:class="dynClass"></div>')($rootScope); +    $rootScope.dynClass = 'A'; +    $rootScope.$digest(); +    expect(element.hasClass('existing')).toBe(true); + +    // add extra class, change model and eval +    element.addClass('newClass'); +    $rootScope.dynClass = 'B'; +    $rootScope.$digest(); + +    expect(element.hasClass('existing')).toBe(true); +    expect(element.hasClass('B')).toBe(true); +    expect(element.hasClass('newClass')).toBe(true); +  })); + + +  it('should preserve class added post compilation without pre-existing classes"', inject(function($rootScope, $compile) { +    element = $compile('<div ng:class="dynClass"></div>')($rootScope); +    $rootScope.dynClass = 'A'; +    $rootScope.$digest(); +    expect(element.hasClass('A')).toBe(true); + +    // add extra class, change model and eval +    element.addClass('newClass'); +    $rootScope.dynClass = 'B'; +    $rootScope.$digest(); + +    expect(element.hasClass('B')).toBe(true); +    expect(element.hasClass('newClass')).toBe(true); +  })); + + +  it('should preserve other classes with similar name"', inject(function($rootScope, $compile) { +    element = $compile('<div class="ui-panel ui-selected" ng:class="dynCls"></div>')($rootScope); +    $rootScope.dynCls = 'panel'; +    $rootScope.$digest(); +    $rootScope.dynCls = 'foo'; +    $rootScope.$digest(); +    expect(element[0].className).toBe('ui-panel ui-selected ng-scope foo'); +  })); + + +  it('should not add duplicate classes', inject(function($rootScope, $compile) { +    element = $compile('<div class="panel bar" ng:class="dynCls"></div>')($rootScope); +    $rootScope.dynCls = 'panel'; +    $rootScope.$digest(); +    expect(element[0].className).toBe('panel bar ng-scope'); +  })); + + +  it('should remove classes even if it was specified via class attribute', inject(function($rootScope, $compile) { +    element = $compile('<div class="panel bar" ng:class="dynCls"></div>')($rootScope); +    $rootScope.dynCls = 'panel'; +    $rootScope.$digest(); +    $rootScope.dynCls = 'window'; +    $rootScope.$digest(); +    expect(element[0].className).toBe('bar ng-scope window'); +  })); + + +  it('should remove classes even if they were added by another code', inject(function($rootScope, $compile) { +    element = $compile('<div ng:class="dynCls"></div>')($rootScope); +    $rootScope.dynCls = 'foo'; +    $rootScope.$digest(); +    element.addClass('foo'); +    $rootScope.dynCls = ''; +    $rootScope.$digest(); +  })); + + +  it('should convert undefined and null values to an empty string', inject(function($rootScope, $compile) { +    element = $compile('<div ng:class="dynCls"></div>')($rootScope); +    $rootScope.dynCls = [undefined, null]; +    $rootScope.$digest(); +  })); + + +  it('should ng:class odd/even', inject(function($rootScope, $compile) { +    element = $compile('<ul><li ng:repeat="i in [0,1]" class="existing" ng:class-odd="\'odd\'" ng:class-even="\'even\'"></li><ul>')($rootScope); +    $rootScope.$digest(); +    var e1 = jqLite(element[0].childNodes[1]); +    var e2 = jqLite(element[0].childNodes[2]); +    expect(e1.hasClass('existing')).toBeTruthy(); +    expect(e1.hasClass('odd')).toBeTruthy(); +    expect(e2.hasClass('existing')).toBeTruthy(); +    expect(e2.hasClass('even')).toBeTruthy(); +  })); + + +  it('should allow both ng:class and ng:class-odd/even on the same element', inject(function($rootScope, $compile) { +    element = $compile('<ul>' + +      '<li ng:repeat="i in [0,1]" ng:class="\'plainClass\'" ' + +      'ng:class-odd="\'odd\'" ng:class-even="\'even\'"></li>' + +      '<ul>')($rootScope); +    $rootScope.$apply(); +    var e1 = jqLite(element[0].childNodes[1]); +    var e2 = jqLite(element[0].childNodes[2]); + +    expect(e1.hasClass('plainClass')).toBeTruthy(); +    expect(e1.hasClass('odd')).toBeTruthy(); +    expect(e1.hasClass('even')).toBeFalsy(); +    expect(e2.hasClass('plainClass')).toBeTruthy(); +    expect(e2.hasClass('even')).toBeTruthy(); +    expect(e2.hasClass('odd')).toBeFalsy(); +  })); + + +  it('should allow both ng:class and ng:class-odd/even with multiple classes', inject(function($rootScope, $compile) { +    element = $compile('<ul>' + +      '<li ng:repeat="i in [0,1]" ng:class="[\'A\', \'B\']" ' + +      'ng:class-odd="[\'C\', \'D\']" ng:class-even="[\'E\', \'F\']"></li>' + +      '<ul>')($rootScope); +    $rootScope.$apply(); +    var e1 = jqLite(element[0].childNodes[1]); +    var e2 = jqLite(element[0].childNodes[2]); + +    expect(e1.hasClass('A')).toBeTruthy(); +    expect(e1.hasClass('B')).toBeTruthy(); +    expect(e1.hasClass('C')).toBeTruthy(); +    expect(e1.hasClass('D')).toBeTruthy(); +    expect(e1.hasClass('E')).toBeFalsy(); +    expect(e1.hasClass('F')).toBeFalsy(); + +    expect(e2.hasClass('A')).toBeTruthy(); +    expect(e2.hasClass('B')).toBeTruthy(); +    expect(e2.hasClass('E')).toBeTruthy(); +    expect(e2.hasClass('F')).toBeTruthy(); +    expect(e2.hasClass('C')).toBeFalsy(); +    expect(e2.hasClass('D')).toBeFalsy(); +  })); +}); diff --git a/test/directive/ngClickSpec.js b/test/directive/ngClickSpec.js new file mode 100644 index 00000000..cb52901c --- /dev/null +++ b/test/directive/ngClickSpec.js @@ -0,0 +1,26 @@ +'use strict'; + +describe('ng:click', function() { +  var element; + +  afterEach(function() { +    dealoc(element); +  }); + +  it('should get called on a click', inject(function($rootScope, $compile) { +    element = $compile('<div ng:click="clicked = true"></div>')($rootScope); +    $rootScope.$digest(); +    expect($rootScope.clicked).toBeFalsy(); + +    browserTrigger(element, 'click'); +    expect($rootScope.clicked).toEqual(true); +  })); + +  it('should pass event object', inject(function($rootScope, $compile) { +    element = $compile('<div ng:click="event = $event"></div>')($rootScope); +    $rootScope.$digest(); + +    browserTrigger(element, 'click'); +    expect($rootScope.event).toBeDefined(); +  })); +}); diff --git a/test/directive/ngCloakSpec.js b/test/directive/ngCloakSpec.js new file mode 100644 index 00000000..bd911f35 --- /dev/null +++ b/test/directive/ngCloakSpec.js @@ -0,0 +1,49 @@ +'use strict'; + +describe('ng:cloak', function() { +  var element; + + +  afterEach(function() { +    dealoc(element); +  }); + + +  it('should get removed when an element is compiled', inject(function($rootScope, $compile) { +    element = jqLite('<div ng:cloak></div>'); +    expect(element.attr('ng:cloak')).toBe(''); +    $compile(element); +    expect(element.attr('ng:cloak')).toBeUndefined(); +  })); + + +  it('should remove ng-cloak class from a compiled element with attribute', inject( +      function($rootScope, $compile) { +    element = jqLite('<div ng:cloak class="foo ng-cloak bar"></div>'); + +    expect(element.hasClass('foo')).toBe(true); +    expect(element.hasClass('ng-cloak')).toBe(true); +    expect(element.hasClass('bar')).toBe(true); + +    $compile(element); + +    expect(element.hasClass('foo')).toBe(true); +    expect(element.hasClass('ng-cloak')).toBe(false); +    expect(element.hasClass('bar')).toBe(true); +  })); + + +  it('should remove ng-cloak class from a compiled element', inject(function($rootScope, $compile) { +    element = jqLite('<div class="foo ng-cloak bar"></div>'); + +    expect(element.hasClass('foo')).toBe(true); +    expect(element.hasClass('ng-cloak')).toBe(true); +    expect(element.hasClass('bar')).toBe(true); + +    $compile(element); + +    expect(element.hasClass('foo')).toBe(true); +    expect(element.hasClass('ng-cloak')).toBe(false); +    expect(element.hasClass('bar')).toBe(true); +  })); +}); diff --git a/test/directive/ngControllerSpec.js b/test/directive/ngControllerSpec.js new file mode 100644 index 00000000..3ab6b929 --- /dev/null +++ b/test/directive/ngControllerSpec.js @@ -0,0 +1,65 @@ +'use strict'; + +describe('ng:controller', function() { +  var element; + +  beforeEach(inject(function($window) { +    $window.Greeter = function($scope) { +      // private stuff (not exported to scope) +      this.prefix = 'Hello '; + +      // public stuff (exported to scope) +      var ctrl = this; +      $scope.name = 'Misko'; +      $scope.greet = function(name) { +        return ctrl.prefix + name + ctrl.suffix; +      }; + +      $scope.protoGreet = bind(this, this.protoGreet); +    }; +    $window.Greeter.prototype = { +      suffix: '!', +      protoGreet: function(name) { +        return this.prefix + name + this.suffix; +      } +    }; + +    $window.Child = function($scope) { +      $scope.name = 'Adam'; +    }; +  })); + +  afterEach(function() { +    dealoc(element); +  }); + + +  it('should instantiate controller and bind methods', inject(function($compile, $rootScope) { +    element = $compile('<div ng:controller="Greeter">{{greet(name)}}</div>')($rootScope); +    $rootScope.$digest(); +    expect(element.text()).toBe('Hello Misko!'); +  })); + + +  it('should allow nested controllers', inject(function($compile, $rootScope) { +    element = $compile('<div ng:controller="Greeter"><div ng:controller="Child">{{greet(name)}}</div></div>')($rootScope); +    $rootScope.$digest(); +    expect(element.text()).toBe('Hello Adam!'); +    dealoc(element); + +    element = $compile('<div ng:controller="Greeter"><div ng:controller="Child">{{protoGreet(name)}}</div></div>')($rootScope); +    $rootScope.$digest(); +    expect(element.text()).toBe('Hello Adam!'); +  })); + + +  it('should instantiate controller defined on scope', inject(function($compile, $rootScope) { +    $rootScope.Greeter = function($scope) { +      $scope.name = 'Vojta'; +    }; + +    element = $compile('<div ng:controller="Greeter">{{name}}</div>')($rootScope); +    $rootScope.$digest(); +    expect(element.text()).toBe('Vojta'); +  })); +}); diff --git a/test/directive/ngEventDirsSpec.js b/test/directive/ngEventDirsSpec.js new file mode 100644 index 00000000..2b14bb33 --- /dev/null +++ b/test/directive/ngEventDirsSpec.js @@ -0,0 +1,25 @@ +'use strict'; + +describe('event directives', function() { +  var element; + + +  afterEach(function() { +    dealoc(element); +  }); + + +  describe('ng:submit', function() { + +    it('should get called on form submit', inject(function($rootScope, $compile) { +      element = $compile('<form action="" ng:submit="submitted = true">' + +        '<input type="submit"/>' + +        '</form>')($rootScope); +      $rootScope.$digest(); +      expect($rootScope.submitted).not.toBeDefined(); + +      browserTrigger(element.children()[0]); +      expect($rootScope.submitted).toEqual(true); +    })); +  }); +}); diff --git a/test/directive/ngIncludeSpec.js b/test/directive/ngIncludeSpec.js new file mode 100644 index 00000000..79215096 --- /dev/null +++ b/test/directive/ngIncludeSpec.js @@ -0,0 +1,274 @@ +'use strict'; + +describe('ng:include', function() { +  var element; + + +  afterEach(function(){ +    dealoc(element); +  }); + + +  function putIntoCache(url, content) { +    return function($templateCache) { +      $templateCache.put(url, [200, content, {}]); +    }; +  } + + +  it('should include on external file', inject(putIntoCache('myUrl', '{{name}}'), +      function($rootScope, $compile, $browser) { +    element = jqLite('<ng:include src="url" scope="childScope"></ng:include>'); +    element = $compile(element)($rootScope); +    $rootScope.childScope = $rootScope.$new(); +    $rootScope.childScope.name = 'misko'; +    $rootScope.url = 'myUrl'; +    $rootScope.$digest(); +    expect(element.text()).toEqual('misko'); +  })); + + +  it('should remove previously included text if a falsy value is bound to src', inject( +        putIntoCache('myUrl', '{{name}}'), +        function($rootScope, $compile, $browser) { +    element = jqLite('<ng:include src="url" scope="childScope"></ng:include>'); +    element = $compile(element)($rootScope); +    $rootScope.childScope = $rootScope.$new(); +    $rootScope.childScope.name = 'igor'; +    $rootScope.url = 'myUrl'; +    $rootScope.$digest(); + +    expect(element.text()).toEqual('igor'); + +    $rootScope.url = undefined; +    $rootScope.$digest(); + +    expect(element.text()).toEqual(''); +  })); + + +  it('should allow this for scope', inject(putIntoCache('myUrl', '{{"abc"}}'), +        function($rootScope, $compile, $browser) { +    element = jqLite('<ng:include src="url" scope="this"></ng:include>'); +    element = $compile(element)($rootScope); +    $rootScope.url = 'myUrl'; +    $rootScope.$digest(); + +    // TODO(misko): because we are using scope==this, the eval gets registered +    // during the flush phase and hence does not get called. +    // I don't think passing 'this' makes sense. Does having scope on ng:include makes sense? +    // should we make scope="this" illegal? +    $rootScope.$digest(); + +    expect(element.text()).toEqual('abc'); +  })); + + +  it('should fire $contentLoaded event after linking the content', inject( +      function($rootScope, $compile, $templateCache) { +    var contentLoadedSpy = jasmine.createSpy('content loaded').andCallFake(function() { +      expect(element.text()).toBe('partial content'); +    }); + +    $templateCache.put('url', [200, 'partial content', {}]); +    $rootScope.$on('$contentLoaded', contentLoadedSpy); + +    element = $compile('<ng:include src="\'url\'"></ng:include>')($rootScope); +    $rootScope.$digest(); + +    expect(contentLoadedSpy).toHaveBeenCalledOnce(); +  })); + + +  it('should evaluate onload expression when a partial is loaded', inject( +      putIntoCache('myUrl', 'my partial'), +      function($rootScope, $compile, $browser) { +    element = jqLite('<ng:include src="url" onload="loaded = true"></ng:include>'); +    element = $compile(element)($rootScope); + +    expect($rootScope.loaded).not.toBeDefined(); + +    $rootScope.url = 'myUrl'; +    $rootScope.$digest(); + +    expect(element.text()).toEqual('my partial'); +    expect($rootScope.loaded).toBe(true); +  })); + + +  it('should destroy old scope', inject(putIntoCache('myUrl', 'my partial'), +        function($rootScope, $compile, $browser) { +    element = jqLite('<ng:include src="url"></ng:include>'); +    element = $compile(element)($rootScope); + +    expect($rootScope.$$childHead).toBeFalsy(); + +    $rootScope.url = 'myUrl'; +    $rootScope.$digest(); +    expect($rootScope.$$childHead).toBeTruthy(); + +    $rootScope.url = null; +    $rootScope.$digest(); +    expect($rootScope.$$childHead).toBeFalsy(); +  })); + + +  it('should do xhr request and cache it', +      inject(function($rootScope, $httpBackend, $compile, $browser) { +    element = $compile('<ng:include src="url"></ng:include>')($rootScope); +    $httpBackend.expect('GET', 'myUrl').respond('my partial'); + +    $rootScope.url = 'myUrl'; +    $rootScope.$digest(); +    $httpBackend.flush(); +    expect(element.text()).toEqual('my partial'); + +    $rootScope.url = null; +    $rootScope.$digest(); +    expect(element.text()).toEqual(''); + +    $rootScope.url = 'myUrl'; +    $rootScope.$digest(); +    expect(element.text()).toEqual('my partial'); +    dealoc($rootScope); +  })); + + +  it('should clear content when error during xhr request', +      inject(function($httpBackend, $compile, $rootScope) { +    element = $compile('<ng:include src="url">content</ng:include>')($rootScope); +    $httpBackend.expect('GET', 'myUrl').respond(404, ''); + +    $rootScope.url = 'myUrl'; +    $rootScope.$digest(); +    $httpBackend.flush(); + +    expect(element.text()).toBe(''); +  })); + + +  it('should be async even if served from cache', inject( +        putIntoCache('myUrl', 'my partial'), +        function($rootScope, $compile, $browser) { +    element = $compile('<ng:include src="url"></ng:include>')($rootScope); + +    $rootScope.url = 'myUrl'; + +    var called = 0; +    // we want to assert only during first watch +    $rootScope.$watch(function() { +      if (!called++) expect(element.text()).toBe(''); +    }); + +    $rootScope.$digest(); +    expect(element.text()).toBe('my partial'); +  })); + + +  it('should discard pending xhr callbacks if a new template is requested before the current ' + +      'finished loading', inject(function($rootScope, $compile, $httpBackend) { +    element = jqLite("<ng:include src='templateUrl'></ng:include>"); +    var log = []; + +    $rootScope.templateUrl = 'myUrl1'; +    $rootScope.logger = function(msg) { +      log.push(msg); +    } +    $compile(element)($rootScope); +    expect(log.join('; ')).toEqual(''); + +    $httpBackend.expect('GET', 'myUrl1').respond('<div>{{logger("url1")}}</div>'); +    $rootScope.$digest(); +    expect(log.join('; ')).toEqual(''); +    $rootScope.templateUrl = 'myUrl2'; +    $httpBackend.expect('GET', 'myUrl2').respond('<div>{{logger("url2")}}</div>'); +    $rootScope.$digest(); +    $httpBackend.flush(); // now that we have two requests pending, flush! + +    expect(log.join('; ')).toEqual('url2; url2'); // it's here twice because we go through at +                                                  // least two digest cycles +  })); + + +  it('should compile only the content', inject(function($compile, $rootScope, $templateCache) { +    // regression + +    var onload = jasmine.createSpy('$contentLoaded'); +    $rootScope.$on('$contentLoaded', onload); +    $templateCache.put('tpl.html', [200, 'partial {{tpl}}', {}]); + +    element = $compile('<div><div ng:repeat="i in [1]">' + +        '<ng:include src="tpl"></ng:include></div></div>')($rootScope); +    expect(onload).not.toHaveBeenCalled(); + +    $rootScope.$apply(function() { +      $rootScope.tpl = 'tpl.html'; +    }); +    expect(onload).toHaveBeenCalledOnce(); +  })); + + +  describe('autoscoll', function() { +    var autoScrollSpy; + +    function spyOnAnchorScroll() { +      return function($provide) { +        autoScrollSpy = jasmine.createSpy('$anchorScroll'); +        $provide.value('$anchorScroll', autoScrollSpy); +      }; +    } + +    function compileAndLink(tpl) { +      return function($compile, $rootScope) { +        element = $compile(tpl)($rootScope); +      }; +    } + +    function changeTplAndValueTo(template, value) { +      return function($rootScope, $browser) { +        $rootScope.$apply(function() { +          $rootScope.tpl = template; +          $rootScope.value = value; +        }); +      }; +    } + +    beforeEach(module(spyOnAnchorScroll())); +    beforeEach(inject( +        putIntoCache('template.html', 'CONTENT'), +        putIntoCache('another.html', 'CONTENT'))); + + +    it('should call $anchorScroll if autoscroll attribute is present', inject( +        compileAndLink('<ng:include src="tpl" autoscroll></ng:include>'), +        changeTplAndValueTo('template.html'), function() { +      expect(autoScrollSpy).toHaveBeenCalledOnce(); +    })); + + +    it('should call $anchorScroll if autoscroll evaluates to true', inject( +        compileAndLink('<ng:include src="tpl" autoscroll="value"></ng:include>'), +        changeTplAndValueTo('template.html', true), +        changeTplAndValueTo('another.html', 'some-string'), +        changeTplAndValueTo('template.html', 100), function() { +      expect(autoScrollSpy).toHaveBeenCalled(); +      expect(autoScrollSpy.callCount).toBe(3); +    })); + + +    it('should not call $anchorScroll if autoscroll attribute is not present', inject( +        compileAndLink('<ng:include src="tpl"></ng:include>'), +        changeTplAndValueTo('template.html'), function() { +      expect(autoScrollSpy).not.toHaveBeenCalled(); +    })); + + +    it('should not call $anchorScroll if autoscroll evaluates to false', inject( +        compileAndLink('<ng:include src="tpl" autoscroll="value"></ng:include>'), +        changeTplAndValueTo('template.html', false), +        changeTplAndValueTo('template.html', undefined), +        changeTplAndValueTo('template.html', null), function() { +      expect(autoScrollSpy).not.toHaveBeenCalled(); +    })); +  }); +}); diff --git a/test/directive/ngInitSpec.js b/test/directive/ngInitSpec.js new file mode 100644 index 00000000..b096c7fd --- /dev/null +++ b/test/directive/ngInitSpec.js @@ -0,0 +1,16 @@ +'use strict'; + +describe('ng:init', function() { +  var element; + + +  afterEach(function() { +    dealoc(element); +  }); + + +  it("should ng:init", inject(function($rootScope, $compile) { +    element = $compile('<div ng:init="a=123"></div>')($rootScope); +    expect($rootScope.a).toEqual(123); +  })); +}); diff --git a/test/directive/ngNonBindableSpec.js b/test/directive/ngNonBindableSpec.js new file mode 100644 index 00000000..c974948d --- /dev/null +++ b/test/directive/ngNonBindableSpec.js @@ -0,0 +1,20 @@ +'use strict'; + + +describe('ng:non-bindable', function() { +  var element; + + +  afterEach(function(){ +    dealoc(element); +  }); + + +  it('should prevent compilation of the owning element and its children', +      inject(function($rootScope, $compile) { +    element = $compile('<div ng:non-bindable><span ng:bind="name"></span></div>')($rootScope); +    $rootScope.name =  'misko'; +    $rootScope.$digest(); +    expect(element.text()).toEqual(''); +  })); +}); diff --git a/test/directive/ngPluralizeSpec.js b/test/directive/ngPluralizeSpec.js new file mode 100644 index 00000000..8046f45f --- /dev/null +++ b/test/directive/ngPluralizeSpec.js @@ -0,0 +1,136 @@ +'use strict'; + +describe('ng:pluralize', function() { +  var element; + + +  afterEach(function(){ +    dealoc(element); +  }); + + +  describe('deal with pluralized strings without offset', function() { +     beforeEach(inject(function($rootScope, $compile) { +        element = $compile( +          '<ng:pluralize count="email"' + +                         "when=\"{'0': 'You have no new email'," + +                                 "'one': 'You have one new email'," + +                                 "'other': 'You have {} new emails'}\">" + +          '</ng:pluralize>')($rootScope); +      })); + + +      it('should show single/plural strings', inject(function($rootScope) { +        $rootScope.email = 0; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have no new email'); + +        $rootScope.email = '0'; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have no new email'); + +        $rootScope.email = 1; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have one new email'); + +        $rootScope.email = 0.01; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have 0.01 new emails'); + +        $rootScope.email = '0.1'; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have 0.1 new emails'); + +        $rootScope.email = 2; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have 2 new emails'); + +        $rootScope.email = -0.1; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have -0.1 new emails'); + +        $rootScope.email = '-0.01'; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have -0.01 new emails'); + +        $rootScope.email = -2; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have -2 new emails'); +      })); + + +      it('should show single/plural strings with mal-formed inputs', inject(function($rootScope) { +        $rootScope.email = ''; +        $rootScope.$digest(); +        expect(element.text()).toBe(''); + +        $rootScope.email = null; +        $rootScope.$digest(); +        expect(element.text()).toBe(''); + +        $rootScope.email = undefined; +        $rootScope.$digest(); +        expect(element.text()).toBe(''); + +        $rootScope.email = 'a3'; +        $rootScope.$digest(); +        expect(element.text()).toBe(''); + +        $rootScope.email = '011'; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have 11 new emails'); + +        $rootScope.email = '-011'; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have -11 new emails'); + +        $rootScope.email = '1fff'; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have one new email'); + +        $rootScope.email = '0aa22'; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have no new email'); + +        $rootScope.email = '000001'; +        $rootScope.$digest(); +        expect(element.text()).toBe('You have one new email'); +      })); +  }); + + +  describe('deal with pluralized strings with offset', function() { +    it('should show single/plural strings with offset', inject(function($rootScope, $compile) { +      element = $compile( +        "<ng:pluralize count=\"viewCount\"  offset=2 " + +            "when=\"{'0': 'Nobody is viewing.'," + +                    "'1': '{{p1}} is viewing.'," + +                    "'2': '{{p1}} and {{p2}} are viewing.'," + +                    "'one': '{{p1}}, {{p2}} and one other person are viewing.'," + +                    "'other': '{{p1}}, {{p2}} and {} other people are viewing.'}\">" + +        "</ng:pluralize>")($rootScope); +      $rootScope.p1 = 'Igor'; +      $rootScope.p2 = 'Misko'; + +      $rootScope.viewCount = 0; +      $rootScope.$digest(); +      expect(element.text()).toBe('Nobody is viewing.'); + +      $rootScope.viewCount = 1; +      $rootScope.$digest(); +      expect(element.text()).toBe('Igor is viewing.'); + +      $rootScope.viewCount = 2; +      $rootScope.$digest(); +      expect(element.text()).toBe('Igor and Misko are viewing.'); + +      $rootScope.viewCount = 3; +      $rootScope.$digest(); +      expect(element.text()).toBe('Igor, Misko and one other person are viewing.'); + +      $rootScope.viewCount = 4; +      $rootScope.$digest(); +      expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.'); +    })); +  }); +}); diff --git a/test/directive/ngRepeatSpec.js b/test/directive/ngRepeatSpec.js new file mode 100644 index 00000000..8400edaa --- /dev/null +++ b/test/directive/ngRepeatSpec.js @@ -0,0 +1,282 @@ +'use strict'; + +describe('ng:repeat', function() { +  var element; + + +  afterEach(function(){ +    dealoc(element); +  }); + + +  it('should ng:repeat over array', inject(function($rootScope, $compile) { +    element = $compile( +      '<ul>' + +        '<li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li>' + +      '</ul>')($rootScope); + +    Array.prototype.extraProperty = "should be ignored"; +    // INIT +    $rootScope.items = ['misko', 'shyam']; +    $rootScope.$digest(); +    expect(element.find('li').length).toEqual(2); +    expect(element.text()).toEqual('misko;shyam;'); +    delete Array.prototype.extraProperty; + +    // GROW +    $rootScope.items = ['adam', 'kai', 'brad']; +    $rootScope.$digest(); +    expect(element.find('li').length).toEqual(3); +    expect(element.text()).toEqual('adam;kai;brad;'); + +    // SHRINK +    $rootScope.items = ['brad']; +    $rootScope.$digest(); +    expect(element.find('li').length).toEqual(1); +    expect(element.text()).toEqual('brad;'); +  })); + + +  it('should ng:repeat over object', inject(function($rootScope, $compile) { +    element = $compile( +      '<ul>' + +        '<li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li>' + +      '</ul>')($rootScope); +    $rootScope.items = {misko:'swe', shyam:'set'}; +    $rootScope.$digest(); +    expect(element.text()).toEqual('misko:swe;shyam:set;'); +  })); + + +  it('should not ng:repeat over parent properties', inject(function($rootScope, $compile) { +    var Class = function() {}; +    Class.prototype.abc = function() {}; +    Class.prototype.value = 'abc'; + +    element = $compile( +      '<ul>' + +        '<li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li>' + +      '</ul>')($rootScope); +    $rootScope.items = new Class(); +    $rootScope.items.name = 'value'; +    $rootScope.$digest(); +    expect(element.text()).toEqual('name:value;'); +  })); + + +  it('should error on wrong parsing of ng:repeat', inject(function($rootScope, $compile, $log) { +    expect(function() { +      element = $compile('<ul><li ng:repeat="i dont parse"></li></ul>')($rootScope); +    }).toThrow("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'."); + +    $log.error.logs.shift(); +  })); + + +  it('should expose iterator offset as $index when iterating over arrays', +      inject(function($rootScope, $compile) { +    element = $compile( +      '<ul>' + +        '<li ng:repeat="item in items" ng:bind="item + $index + \'|\'"></li>' + +      '</ul>')($rootScope); +    $rootScope.items = ['misko', 'shyam', 'frodo']; +    $rootScope.$digest(); +    expect(element.text()).toEqual('misko0|shyam1|frodo2|'); +  })); + + +  it('should expose iterator offset as $index when iterating over objects', +      inject(function($rootScope, $compile) { +    element = $compile( +      '<ul>' + +        '<li ng:repeat="(key, val) in items" ng:bind="key + \':\' + val + $index + \'|\'"></li>' + +      '</ul>')($rootScope); +    $rootScope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'}; +    $rootScope.$digest(); +    expect(element.text()).toEqual('frodo:f0|misko:m1|shyam:s2|'); +  })); + + +  it('should expose iterator position as $position when iterating over arrays', +      inject(function($rootScope, $compile) { +    element = $compile( +      '<ul>' + +        '<li ng:repeat="item in items" ng:bind="item + \':\' + $position + \'|\'"></li>' + +      '</ul>')($rootScope); +    $rootScope.items = ['misko', 'shyam', 'doug']; +    $rootScope.$digest(); +    expect(element.text()).toEqual('misko:first|shyam:middle|doug:last|'); + +    $rootScope.items.push('frodo'); +    $rootScope.$digest(); +    expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|'); + +    $rootScope.items.pop(); +    $rootScope.items.pop(); +    $rootScope.$digest(); +    expect(element.text()).toEqual('misko:first|shyam:last|'); +  })); + + +  it('should expose iterator position as $position when iterating over objects', +      inject(function($rootScope, $compile) { +    element = $compile( +      '<ul>' + +        '<li ng:repeat="(key, val) in items" ng:bind="key + \':\' + val + \':\' + $position + \'|\'">' + +        '</li>' + +      '</ul>')($rootScope); +    $rootScope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'}; +    $rootScope.$digest(); +    expect(element.text()).toEqual('doug:d:first|frodo:f:middle|misko:m:middle|shyam:s:last|'); + +    delete $rootScope.items.doug; +    delete $rootScope.items.frodo; +    $rootScope.$digest(); +    expect(element.text()).toEqual('misko:m:first|shyam:s:last|'); +  })); + + +  it('should ignore $ and $$ properties', inject(function($rootScope, $compile) { +    element = $compile('<ul><li ng:repeat="i in items">{{i}}|</li></ul>')($rootScope); +    $rootScope.items = ['a', 'b', 'c']; +    $rootScope.items.$$hashkey = 'xxx'; +    $rootScope.items.$root = 'yyy'; +    $rootScope.$digest(); + +    expect(element.text()).toEqual('a|b|c|'); +  })); + + +  it('should repeat over nested arrays', inject(function($rootScope, $compile) { +    element = $compile( +      '<ul>' + +        '<li ng:repeat="subgroup in groups">' + +          '<div ng:repeat="group in subgroup">{{group}}|</div>X' + +        '</li>' + +      '</ul>')($rootScope); +    $rootScope.groups = [['a', 'b'], ['c','d']]; +    $rootScope.$digest(); + +    expect(element.text()).toEqual('a|b|Xc|d|X'); +  })); + + +  it('should ignore non-array element properties when iterating over an array', +      inject(function($rootScope, $compile) { +    element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); +    $rootScope.array = ['a', 'b', 'c']; +    $rootScope.array.foo = '23'; +    $rootScope.array.bar = function() {}; +    $rootScope.$digest(); + +    expect(element.text()).toBe('a|b|c|'); +  })); + + +  it('should iterate over non-existent elements of a sparse array', +      inject(function($rootScope, $compile) { +    element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); +    $rootScope.array = ['a', 'b']; +    $rootScope.array[4] = 'c'; +    $rootScope.array[6] = 'd'; +    $rootScope.$digest(); + +    expect(element.text()).toBe('a|b|||c||d|'); +  })); + + +  it('should iterate over all kinds of types', inject(function($rootScope, $compile) { +    element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); +    $rootScope.array = ['a', 1, null, undefined, {}]; +    $rootScope.$digest(); + +    expect(element.text()).toMatch(/a\|1\|\|\|\{\s*\}\|/); +  })); + + +  describe('stability', function() { +    var a, b, c, d, lis; + +    beforeEach(inject(function($rootScope, $compile) { +      element = $compile( +        '<ul>' + +          '<li ng:repeat="item in items" ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li>' + +        '</ul>')($rootScope); +      a = {}; +      b = {}; +      c = {}; +      d = {}; + +      $rootScope.items = [a, b, c]; +      $rootScope.$digest(); +      lis = element.find('li'); +    })); + + +    it('should preserve the order of elements', inject(function($rootScope) { +      $rootScope.items = [a, c, d]; +      $rootScope.$digest(); +      var newElements = element.find('li'); +      expect(newElements[0]).toEqual(lis[0]); +      expect(newElements[1]).toEqual(lis[2]); +      expect(newElements[2]).not.toEqual(lis[1]); +    })); + + +    it('should support duplicates', inject(function($rootScope) { +      $rootScope.items = [a, a, b, c]; +      $rootScope.$digest(); +      var newElements = element.find('li'); +      expect(newElements[0]).toEqual(lis[0]); +      expect(newElements[1]).not.toEqual(lis[0]); +      expect(newElements[2]).toEqual(lis[1]); +      expect(newElements[3]).toEqual(lis[2]); + +      lis = newElements; +      $rootScope.$digest(); +      newElements = element.find('li'); +      expect(newElements[0]).toEqual(lis[0]); +      expect(newElements[1]).toEqual(lis[1]); +      expect(newElements[2]).toEqual(lis[2]); +      expect(newElements[3]).toEqual(lis[3]); + +      $rootScope.$digest(); +      newElements = element.find('li'); +      expect(newElements[0]).toEqual(lis[0]); +      expect(newElements[1]).toEqual(lis[1]); +      expect(newElements[2]).toEqual(lis[2]); +      expect(newElements[3]).toEqual(lis[3]); +    })); + + +    it('should remove last item when one duplicate instance is removed', +        inject(function($rootScope) { +      $rootScope.items = [a, a, a]; +      $rootScope.$digest(); +      lis = element.find('li'); + +      $rootScope.items = [a, a]; +      $rootScope.$digest(); +      var newElements = element.find('li'); +      expect(newElements.length).toEqual(2); +      expect(newElements[0]).toEqual(lis[0]); +      expect(newElements[1]).toEqual(lis[1]); +    })); + + +    it('should reverse items when the collection is reversed', +        inject(function($rootScope) { +      $rootScope.items = [a, b, c]; +      $rootScope.$digest(); +      lis = element.find('li'); + +      $rootScope.items = [c, b, a]; +      $rootScope.$digest(); +      var newElements = element.find('li'); +      expect(newElements.length).toEqual(3); +      expect(newElements[0]).toEqual(lis[2]); +      expect(newElements[1]).toEqual(lis[1]); +      expect(newElements[2]).toEqual(lis[0]); +    })); +  }); +}); diff --git a/test/directive/ngShowHideSpec.js b/test/directive/ngShowHideSpec.js new file mode 100644 index 00000000..6b9aea24 --- /dev/null +++ b/test/directive/ngShowHideSpec.js @@ -0,0 +1,43 @@ +'use strict'; + +describe('ng:show / ng:hide', function() { +  var element; + + +  afterEach(function() { +    dealoc(element); +  }); + +  describe('ng:show', function() { +    it('should show and hide an element', inject(function($rootScope, $compile) { +      element = jqLite('<div ng:show="exp"></div>'); +      element = $compile(element)($rootScope); +      $rootScope.$digest(); +      expect(isCssVisible(element)).toEqual(false); +      $rootScope.exp = true; +      $rootScope.$digest(); +      expect(isCssVisible(element)).toEqual(true); +    })); + + +    it('should make hidden element visible', inject(function($rootScope, $compile) { +      element = jqLite('<div style="display: none" ng:show="exp"></div>'); +      element = $compile(element)($rootScope); +      expect(isCssVisible(element)).toBe(false); +      $rootScope.exp = true; +      $rootScope.$digest(); +      expect(isCssVisible(element)).toBe(true); +    })); +  }); + +  describe('ng:hide', function() { +    it('should hide an element', inject(function($rootScope, $compile) { +      element = jqLite('<div ng:hide="exp"></div>'); +      element = $compile(element)($rootScope); +      expect(isCssVisible(element)).toBe(true); +      $rootScope.exp = true; +      $rootScope.$digest(); +      expect(isCssVisible(element)).toBe(false); +    })); +  }); +}); diff --git a/test/directive/ngStyleSpec.js b/test/directive/ngStyleSpec.js new file mode 100644 index 00000000..a413353b --- /dev/null +++ b/test/directive/ngStyleSpec.js @@ -0,0 +1,88 @@ +'use strict'; + +describe('ng:style', function() { +  var element; + + +  afterEach(function() { +    dealoc(element); +  }); + + +  it('should set', inject(function($rootScope, $compile) { +    element = $compile('<div ng:style="{height: \'40px\'}"></div>')($rootScope); +    $rootScope.$digest(); +    expect(element.css('height')).toEqual('40px'); +  })); + + +  it('should silently ignore undefined style', inject(function($rootScope, $compile) { +    element = $compile('<div ng:style="myStyle"></div>')($rootScope); +    $rootScope.$digest(); +    expect(element.hasClass('ng-exception')).toBeFalsy(); +  })); + + +  describe('preserving styles set before and after compilation', function() { +    var scope, preCompStyle, preCompVal, postCompStyle, postCompVal, element; + +    beforeEach(inject(function($rootScope, $compile) { +      preCompStyle = 'width'; +      preCompVal = '300px'; +      postCompStyle = 'height'; +      postCompVal = '100px'; +      element = jqLite('<div ng:style="styleObj"></div>'); +      element.css(preCompStyle, preCompVal); +      jqLite(document.body).append(element); +      $compile(element)($rootScope); +      scope = $rootScope; +      scope.styleObj = {'margin-top': '44px'}; +      scope.$apply(); +      element.css(postCompStyle, postCompVal); +    })); + +    afterEach(function() { +      element.remove(); +    }); + + +    it('should not mess up stuff after compilation', function() { +      element.css('margin', '44px'); +      expect(element.css(preCompStyle)).toBe(preCompVal); +      expect(element.css('margin-top')).toBe('44px'); +      expect(element.css(postCompStyle)).toBe(postCompVal); +    }); + + +    it('should not mess up stuff after $apply with no model changes', function() { +      element.css('padding-top', '33px'); +      scope.$apply(); +      expect(element.css(preCompStyle)).toBe(preCompVal); +      expect(element.css('margin-top')).toBe('44px'); +      expect(element.css(postCompStyle)).toBe(postCompVal); +      expect(element.css('padding-top')).toBe('33px'); +    }); + + +    it('should not mess up stuff after $apply with non-colliding model changes', function() { +      scope.styleObj = {'padding-top': '99px'}; +      scope.$apply(); +      expect(element.css(preCompStyle)).toBe(preCompVal); +      expect(element.css('margin-top')).not.toBe('44px'); +      expect(element.css('padding-top')).toBe('99px'); +      expect(element.css(postCompStyle)).toBe(postCompVal); +    }); + + +    it('should overwrite original styles after a colliding model change', function() { +      scope.styleObj = {'height': '99px', 'width': '88px'}; +      scope.$apply(); +      expect(element.css(preCompStyle)).toBe('88px'); +      expect(element.css(postCompStyle)).toBe('99px'); +      scope.styleObj = {}; +      scope.$apply(); +      expect(element.css(preCompStyle)).not.toBe('88px'); +      expect(element.css(postCompStyle)).not.toBe('99px'); +    }); +  }); +}); diff --git a/test/directive/ngSwitchSpec.js b/test/directive/ngSwitchSpec.js new file mode 100644 index 00000000..88e81dea --- /dev/null +++ b/test/directive/ngSwitchSpec.js @@ -0,0 +1,63 @@ +'use strict'; + +describe('ng:switch', function() { +  var element; + + +  afterEach(function(){ +    dealoc(element); +  }); + + +  it('should switch on value change', inject(function($rootScope, $compile) { +    element = $compile( +      '<div ng-switch="select">' + +        '<div ng:switch-when="1">first:{{name}}</div>' + +        '<div ng:switch-when="2">second:{{name}}</div>' + +        '<div ng:switch-when="true">true:{{name}}</div>' + +      '</div>')($rootScope); +    expect(element.html()).toEqual( +        '<!-- ngSwitchWhen: 1 --><!-- ngSwitchWhen: 2 --><!-- ngSwitchWhen: true -->'); +    $rootScope.select = 1; +    $rootScope.$apply(); +    expect(element.text()).toEqual('first:'); +    $rootScope.name="shyam"; +    $rootScope.$apply(); +    expect(element.text()).toEqual('first:shyam'); +    $rootScope.select = 2; +    $rootScope.$apply(); +    expect(element.text()).toEqual('second:shyam'); +    $rootScope.name = 'misko'; +    $rootScope.$apply(); +    expect(element.text()).toEqual('second:misko'); +    $rootScope.select = true; +    $rootScope.$apply(); +    expect(element.text()).toEqual('true:misko'); +  })); + + +  it('should switch on switch-when-default', inject(function($rootScope, $compile) { +    element = $compile( +      '<ng:switch on="select">' + +        '<div ng:switch-when="1">one</div>' + +        '<div ng:switch-default>other</div>' + +      '</ng:switch>')($rootScope); +    $rootScope.$apply(); +    expect(element.text()).toEqual('other'); +    $rootScope.select = 1; +    $rootScope.$apply(); +    expect(element.text()).toEqual('one'); +  })); + + +  it('should call change on switch', inject(function($rootScope, $compile) { +    element = $compile( +      '<ng:switch on="url" change="name=\'works\'">' + +        '<div ng:switch-when="a">{{name}}</div>' + +      '</ng:switch>')($rootScope); +    $rootScope.url = 'a'; +    $rootScope.$apply(); +    expect($rootScope.name).toEqual('works'); +    expect(element.text()).toEqual('works'); +  })); +}); diff --git a/test/directive/ngViewSpec.js b/test/directive/ngViewSpec.js new file mode 100644 index 00000000..d74812d3 --- /dev/null +++ b/test/directive/ngViewSpec.js @@ -0,0 +1,413 @@ +'use strict'; + +describe('ng:view', function() { +  var element; + +  beforeEach(module(function() { +    return function($rootScope, $compile) { +      element = $compile('<ng:view onload="load()"></ng:view>')($rootScope); +    }; +  })); + + +  afterEach(function(){ +    dealoc(element); +  }); + + +  it('should do nothing when no routes are defined', +      inject(function($rootScope, $compile, $location) { +    $location.path('/unknown'); +    $rootScope.$digest(); +    expect(element.text()).toEqual(''); +  })); + + +  it('should instantiate controller after compiling the content', function() { +    var log = [], controllerScope, +        Ctrl = function($scope) { +          controllerScope = $scope; +          log.push('ctrl-init'); +        }; + +    module(function($compileProvider, $routeProvider) { +      $compileProvider.directive('compileLog', function() { +        return { +          compile: function() { +            log.push('compile'); +          } +        }; +      }); + +      $routeProvider.when('/some', {template: '/tpl.html', controller: Ctrl}); +    }); + +    inject(function($route, $rootScope, $templateCache, $location) { +      $templateCache.put('/tpl.html', [200, '<div compile-log>partial</div>', {}]); +      $location.path('/some'); +      $rootScope.$digest(); + +      expect(controllerScope.$parent).toBe($rootScope); +      expect(controllerScope).toBe($route.current.scope); +      expect(log).toEqual(['compile', 'ctrl-init']); +    }); +  }); + + +  it('should load content via xhr when route changes', function() { +    module(function($routeProvider) { +      $routeProvider.when('/foo', {template: 'myUrl1'}); +      $routeProvider.when('/bar', {template: 'myUrl2'}); +    }); + +    inject(function($rootScope, $compile, $httpBackend, $location, $route) { +      expect(element.text()).toEqual(''); + +      $location.path('/foo'); +      $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); +      $rootScope.$digest(); +      $httpBackend.flush(); +      expect(element.text()).toEqual('4'); + +      $location.path('/bar'); +      $httpBackend.expect('GET', 'myUrl2').respond('angular is da best'); +      $rootScope.$digest(); +      $httpBackend.flush(); +      expect(element.text()).toEqual('angular is da best'); +    }); +  }); + + +  it('should remove all content when location changes to an unknown route', function() { +    module(function($routeProvider) { +      $routeProvider.when('/foo', {template: 'myUrl1'}); +    }); + +    inject(function($rootScope, $compile, $location, $httpBackend, $route) { +      $location.path('/foo'); +      $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); +      $rootScope.$digest(); +      $httpBackend.flush(); +      expect(element.text()).toEqual('4'); + +      $location.path('/unknown'); +      $rootScope.$digest(); +      expect(element.text()).toEqual(''); +    }); +  }); + + +  it('should chain scopes and propagate evals to the child scope', function() { +    module(function($routeProvider) { +      $routeProvider.when('/foo', {template: 'myUrl1'}); +    }); + +    inject(function($rootScope, $compile, $location, $httpBackend, $route) { +      $rootScope.parentVar = 'parent'; + +      $location.path('/foo'); +      $httpBackend.expect('GET', 'myUrl1').respond('<div>{{parentVar}}</div>'); +      $rootScope.$digest(); +      $httpBackend.flush(); +      expect(element.text()).toEqual('parent'); + +      $rootScope.parentVar = 'new parent'; +      $rootScope.$digest(); +      expect(element.text()).toEqual('new parent'); +    }); +  }); + + +  it('should be possible to nest ng:view in ng:include', inject(function() { +    // TODO(vojta): refactor this test +    dealoc(element); +    var injector = angular.injector(['ng', 'ngMock', function($routeProvider) { +      $routeProvider.when('/foo', {controller: angular.noop, template: 'viewPartial.html'}); +    }]); +    var myApp = injector.get('$rootScope'); +    var $httpBackend = injector.get('$httpBackend'); +    $httpBackend.expect('GET', 'includePartial.html').respond('view: <ng:view></ng:view>'); +    injector.get('$location').path('/foo'); + +    var $route = injector.get('$route'); + +    element = injector.get('$compile')( +        '<div>' + +          'include: <ng:include src="\'includePartial.html\'"> </ng:include>' + +        '</div>')(myApp); +    myApp.$apply(); + +    $httpBackend.expect('GET', 'viewPartial.html').respond('content'); +    $httpBackend.flush(); + +    expect(element.text()).toEqual('include: view: content'); +    expect($route.current.template).toEqual('viewPartial.html'); +    dealoc(myApp); +    dealoc(element); +  })); + + +  it('should initialize view template after the view controller was initialized even when ' + +     'templates were cached', function() { +     //this is a test for a regression that was introduced by making the ng:view cache sync +    function ParentCtrl($scope) { +       $scope.log.push('parent'); +    } + +    module(function($routeProvider) { +      $routeProvider.when('/foo', {controller: ParentCtrl, template: 'viewPartial.html'}); +    }); + + +    inject(function($rootScope, $compile, $location, $httpBackend, $route) { +      $rootScope.log = []; + +      $rootScope.ChildCtrl = function($scope) { +        $scope.log.push('child'); +      }; + +      $location.path('/foo'); +      $httpBackend.expect('GET', 'viewPartial.html'). +          respond('<div ng:init="log.push(\'init\')">' + +                    '<div ng:controller="ChildCtrl"></div>' + +                  '</div>'); +      $rootScope.$apply(); +      $httpBackend.flush(); + +      expect($rootScope.log).toEqual(['parent', 'init', 'child']); + +      $location.path('/'); +      $rootScope.$apply(); +      expect($rootScope.log).toEqual(['parent', 'init', 'child']); + +      $rootScope.log = []; +      $location.path('/foo'); +      $rootScope.$apply(); + +      expect($rootScope.log).toEqual(['parent', 'init', 'child']); +    }); +  }); + + +  it('should discard pending xhr callbacks if a new route is requested before the current ' + +      'finished loading',  function() { +    // this is a test for a bad race condition that affected feedback + +    module(function($routeProvider) { +      $routeProvider.when('/foo', {template: 'myUrl1'}); +      $routeProvider.when('/bar', {template: 'myUrl2'}); +    }); + +    inject(function($route, $rootScope, $location, $httpBackend) { +      expect(element.text()).toEqual(''); + +      $location.path('/foo'); +      $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); +      $rootScope.$digest(); +      $location.path('/bar'); +      $httpBackend.expect('GET', 'myUrl2').respond('<div>{{1+1}}</div>'); +      $rootScope.$digest(); +      $httpBackend.flush(); // now that we have two requests pending, flush! + +      expect(element.text()).toEqual('2'); +    }); +  }); + + +  it('should clear the content when error during xhr request', function() { +    module(function($routeProvider) { +      $routeProvider.when('/foo', {controller: noop, template: 'myUrl1'}); +    }); + +    inject(function($route, $location, $rootScope, $httpBackend) { +      $location.path('/foo'); +      $httpBackend.expect('GET', 'myUrl1').respond(404, ''); +      element.text('content'); + +      $rootScope.$digest(); +      $httpBackend.flush(); + +      expect(element.text()).toBe(''); +    }); +  }); + + +  it('should be async even if served from cache', function() { +    module(function($routeProvider) { +      $routeProvider.when('/foo', {controller: noop, template: 'myUrl1'}); +    }); + +    inject(function($route, $rootScope, $location, $templateCache) { +      $templateCache.put('myUrl1', [200, 'my partial', {}]); +      $location.path('/foo'); + +      var called = 0; +      // we want to assert only during first watch +      $rootScope.$watch(function() { +        if (!called++) expect(element.text()).toBe(''); +      }); + +      $rootScope.$digest(); +      expect(element.text()).toBe('my partial'); +    }); +  }); + +  it('should fire $contentLoaded event when content compiled and linked', function() { +    var log = []; +    var logger = function(name) { +      return function() { +        log.push(name); +      }; +    }; +    var Ctrl = function($scope) { +      $scope.value = 'bound-value'; +      log.push('init-ctrl'); +    }; + +    module(function($routeProvider) { +      $routeProvider.when('/foo', {template: 'tpl.html', controller: Ctrl}); +    }); + +    inject(function($templateCache, $rootScope, $location) { +      $rootScope.$on('$beforeRouteChange', logger('$beforeRouteChange')); +      $rootScope.$on('$afterRouteChange', logger('$afterRouteChange')); +      $rootScope.$on('$contentLoaded', logger('$contentLoaded')); + +      $templateCache.put('tpl.html', [200, '{{value}}', {}]); +      $location.path('/foo'); +      $rootScope.$digest(); + +      expect(element.text()).toBe('bound-value'); +      expect(log).toEqual(['$beforeRouteChange', '$afterRouteChange', 'init-ctrl', +                           '$contentLoaded']); +    }); +  }); + +  it('should destroy previous scope', function() { +    module(function($routeProvider) { +      $routeProvider.when('/foo', {template: 'tpl.html'}); +    }); + +    inject(function($templateCache, $rootScope, $location) { +      $templateCache.put('tpl.html', [200, 'partial', {}]); + +      expect($rootScope.$$childHead).toBeNull(); +      expect($rootScope.$$childTail).toBeNull(); + +      $location.path('/foo'); +      $rootScope.$digest(); + +      expect(element.text()).toBe('partial'); +      expect($rootScope.$$childHead).not.toBeNull(); +      expect($rootScope.$$childTail).not.toBeNull(); + +      $location.path('/non/existing/route'); +      $rootScope.$digest(); + +      expect(element.text()).toBe(''); +      expect($rootScope.$$childHead).toBeNull(); +      expect($rootScope.$$childTail).toBeNull(); +    }); +  }); + + +  it('should destroy previous scope if multiple route changes occur before server responds', +      function() { +    var log = []; +    var createCtrl = function(name) { +      return function($scope) { +        log.push('init-' + name); +        $scope.$on('$destroy', function() { +          log.push('destroy-' + name); +        }); +      }; +    }; + +    module(function($routeProvider) { +      $routeProvider.when('/one', {template: 'one.html', controller: createCtrl('ctrl1')}); +      $routeProvider.when('/two', {template: 'two.html', controller: createCtrl('ctrl2')}); +    }); + +    inject(function($httpBackend, $rootScope, $location) { +      $httpBackend.whenGET('one.html').respond('content 1'); +      $httpBackend.whenGET('two.html').respond('content 2'); + +      $location.path('/one'); +      $rootScope.$digest(); +      $location.path('/two'); +      $rootScope.$digest(); + +      $httpBackend.flush(); +      expect(element.text()).toBe('content 2'); +      expect(log).toEqual(['init-ctrl2']); + +      $location.path('/non-existing'); +      $rootScope.$digest(); + +      expect(element.text()).toBe(''); +      expect(log).toEqual(['init-ctrl2', 'destroy-ctrl2']); + +      expect($rootScope.$$childHead).toBeNull(); +      expect($rootScope.$$childTail).toBeNull(); +    }); +  }); + + +  it('should $destroy scope after update and reload',  function() { +    // this is a regression of bug, where $route doesn't copy scope when only updating + +    var log = []; + +    function logger(msg) { +      return function() { +        log.push(msg); +      }; +    } + +    function createController(name) { +      return function($scope) { +        log.push('init-' + name); +        $scope.$on('$destroy', logger('destroy-' + name)); +        $scope.$on('$routeUpdate', logger('route-update')); +      }; +    } + +    module(function($routeProvider) { +      $routeProvider.when('/bar', {template: 'tpl.html', controller: createController('bar')}); +      $routeProvider.when('/foo', { +          template: 'tpl.html', controller: createController('foo'), reloadOnSearch: false}); +    }); + +    inject(function($templateCache, $location, $rootScope) { +      $templateCache.put('tpl.html', [200, 'partial', {}]); + +      $location.url('/foo'); +      $rootScope.$digest(); +      expect(log).toEqual(['init-foo']); + +      $location.search({q: 'some'}); +      $rootScope.$digest(); +      expect(log).toEqual(['init-foo', 'route-update']); + +      $location.url('/bar'); +      $rootScope.$digest(); +      expect(log).toEqual(['init-foo', 'route-update', 'destroy-foo', 'init-bar']); +    }); +  }); + + +  it('should evaluate onload expression after linking the content', function() { +    module(function($routeProvider) { +      $routeProvider.when('/foo', {template: 'tpl.html'}); +    }); + +    inject(function($templateCache, $location, $rootScope) { +      $templateCache.put('tpl.html', [200, '{{1+1}}', {}]); +      $rootScope.load = jasmine.createSpy('onload'); + +      $location.url('/foo'); +      $rootScope.$digest(); +      expect($rootScope.load).toHaveBeenCalledOnce(); +    }); +  }) +}); diff --git a/test/directive/scriptSpec.js b/test/directive/scriptSpec.js new file mode 100644 index 00000000..471e04ce --- /dev/null +++ b/test/directive/scriptSpec.js @@ -0,0 +1,44 @@ +'use strict'; + +describe('scriptDirective', function() { +  var element; + + +  afterEach(function(){ +    dealoc(element); +  }); + + +  it('should populate $templateCache with contents of a ng-template script element', inject( +      function($compile, $templateCache) { +        if (msie <=8) return; +        // in ie8 it is not possible to create a script tag with the right content. +        // it always comes up as empty. I was trying to set the text of the +        // script tag, but that did not work either, so I gave up. +        $compile('<div>foo' + +                   '<script id="/ignore">ignore me</script>' + +                   '<script type="text/ng-template" id="/myTemplate.html"><x>{{y}}</x></script>' + +                 '</div>' ); +        expect($templateCache.get('/myTemplate.html')).toBe('<x>{{y}}</x>'); +        expect($templateCache.get('/ignore')).toBeUndefined(); +      } +  )); + + +  it('should not compile scripts', inject(function($compile, $templateCache, $rootScope) { +    if (msie <=8) return; // see above + +    var doc = jqLite('<div></div>'); +    // jQuery is too smart and removes +    doc[0].innerHTML = '<script type="text/javascript">some {{binding}}</script>' + +                       '<script type="text/ng-template" id="/some">other {{binding}}</script>'; + +    $compile(doc)($rootScope); +    $rootScope.$digest(); + +    var scripts = doc.find('script'); +    expect(scripts.eq(0).text()).toBe('some {{binding}}'); +    expect(scripts.eq(1).text()).toBe('other {{binding}}'); +    dealoc(doc); +  })); +}); diff --git a/test/widget/selectSpec.js b/test/directive/selectSpec.js index 9db47c05..c262d0cf 100644 --- a/test/widget/selectSpec.js +++ b/test/directive/selectSpec.js @@ -806,4 +806,50 @@ describe('select', function() {        });      });    }); + + +  describe('OPTION value', function() { +    beforeEach(function() { +      this.addMatchers({ +        toHaveValue: function(expected){ +          this.message = function() { +            return 'Expected "' + this.actual.html() + '" to have value="' + expected + '".'; +          }; + +          var value; +          htmlParser(this.actual.html(), { +            start:function(tag, attrs){ +              value = attrs.value; +            }, +            end:noop, +            chars:noop +          }); +          return trim(value) == trim(expected); +        } +      }); +    }); + + +    it('should populate value attribute on OPTION', inject(function($rootScope, $compile) { +      element = $compile('<select ng:model="x"><option>abc</option></select>')($rootScope) +      expect(element).toHaveValue('abc'); +    })); + +    it('should ignore value if already exists', inject(function($rootScope, $compile) { +      element = $compile('<select ng:model="x"><option value="abc">xyz</option></select>')($rootScope) +      expect(element).toHaveValue('abc'); +    })); + +    it('should set value even if newlines present', inject(function($rootScope, $compile) { +      element = $compile('<select ng:model="x"><option attr="\ntext\n" \n>\nabc\n</option></select>')($rootScope) +      expect(element).toHaveValue('\nabc\n'); +    })); + +    it('should set value even if self closing HTML', inject(function($rootScope, $compile) { +      // IE removes the \n from option, which makes this test pointless +      if (msie) return; +      element = $compile('<select ng:model="x"><option>\n</option></select>')($rootScope) +      expect(element).toHaveValue('\n'); +    })); +  });  }); diff --git a/test/directive/styleSpec.js b/test/directive/styleSpec.js new file mode 100644 index 00000000..bdc4ea85 --- /dev/null +++ b/test/directive/styleSpec.js @@ -0,0 +1,31 @@ +'use strict'; + +describe('style', function() { +  var element; + + +  afterEach(function() { +    dealoc(element); +  }); + + +  it('should not compile style element', inject(function($compile, $rootScope) { +    element = jqLite('<style type="text/css">should {{notBound}}</style>'); +    $compile(element)($rootScope); +    $rootScope.$digest(); + +    // read innerHTML and trim to pass on IE8 +    expect(trim(element[0].innerHTML)).toBe('should {{notBound}}'); +  })); + + +  it('should compile content of element with style attr', inject(function($compile, $rootScope) { +    element = jqLite('<div style="some">{{bind}}</div>'); +    $compile(element)($rootScope); +    $rootScope.$apply(function() { +      $rootScope.bind = 'value'; +    }); + +    expect(element.text()).toBe('value'); +  })); +}); diff --git a/test/directivesSpec.js b/test/directivesSpec.js deleted file mode 100644 index 1ce6090b..00000000 --- a/test/directivesSpec.js +++ /dev/null @@ -1,599 +0,0 @@ -'use strict'; - -describe("directive", function() { -  var element; - -  beforeEach(function() { -    element = null; -  }); - -  afterEach(function() { -    dealoc(element); -  }); - - -  var $filterProvider, element; - -  beforeEach(module(['$filterProvider', function(provider){ -    $filterProvider = provider; -  }])); - -  afterEach(function() { -    dealoc(element); -  }); - -  it("should ng:init", inject(function($rootScope, $compile) { -    element = $compile('<div ng:init="a=123"></div>')($rootScope); -    expect($rootScope.a).toEqual(123); -  })); - -  describe('ng:bind', function() { -    it('should set text', inject(function($rootScope, $compile) { -      element = $compile('<div ng:bind="a"></div>')($rootScope); -      expect(element.text()).toEqual(''); -      $rootScope.a = 'misko'; -      $rootScope.$digest(); -      expect(element.hasClass('ng-binding')).toEqual(true); -      expect(element.text()).toEqual('misko'); -    })); - -    it('should set text to blank if undefined', inject(function($rootScope, $compile) { -      element = $compile('<div ng:bind="a"></div>')($rootScope); -      $rootScope.a = 'misko'; -      $rootScope.$digest(); -      expect(element.text()).toEqual('misko'); -      $rootScope.a = undefined; -      $rootScope.$digest(); -      expect(element.text()).toEqual(''); -      $rootScope.a = null; -      $rootScope.$digest(); -      expect(element.text()).toEqual(''); -    })); - -    it('should set html', inject(function($rootScope, $compile) { -      element = $compile('<div ng:bind-html="html"></div>')($rootScope); -      $rootScope.html = '<div unknown>hello</div>'; -      $rootScope.$digest(); -      expect(lowercase(element.html())).toEqual('<div>hello</div>'); -    })); - -    it('should set unsafe html', inject(function($rootScope, $compile) { -      element = $compile('<div ng:bind-html-unsafe="html"></div>')($rootScope); -      $rootScope.html = '<div onclick="">hello</div>'; -      $rootScope.$digest(); -      expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>'); -    })); - -    it('should suppress rendering of falsy values', inject(function($rootScope, $compile) { -      element = $compile('<div>{{ null }}{{ undefined }}{{ "" }}-{{ 0 }}{{ false }}</div>')($rootScope); -      $rootScope.$digest(); -      expect(element.text()).toEqual('-0false'); -    })); - -    it('should render object as JSON ignore $$', inject(function($rootScope, $compile) { -      element = $compile('<div>{{ {key:"value", $$key:"hide"}  }}</div>')($rootScope); -      $rootScope.$digest(); -      expect(fromJson(element.text())).toEqual({key:'value'}); -    })); -  }); - -  describe('ng:bind-template', function() { -    it('should ng:bind-template', inject(function($rootScope, $compile) { -      element = $compile('<div ng:bind-template="Hello {{name}}!"></div>')($rootScope); -      $rootScope.name = 'Misko'; -      $rootScope.$digest(); -      expect(element.hasClass('ng-binding')).toEqual(true); -      expect(element.text()).toEqual('Hello Misko!'); -    })); - -    it('should render object as JSON ignore $$', inject(function($rootScope, $compile) { -      element = $compile('<pre>{{ {key:"value", $$key:"hide"}  }}</pre>')($rootScope); -      $rootScope.$digest(); -      expect(fromJson(element.text())).toEqual({key:'value'}); -    })); - -  }); - -  describe('ng:bind-attr', function() { -    it('should bind attributes', inject(function($rootScope, $compile) { -      element = $compile('<div ng:bind-attr="{src:\'http://localhost/mysrc\', alt:\'myalt\'}"/>')($rootScope); -      $rootScope.$digest(); -      expect(element.attr('src')).toEqual('http://localhost/mysrc'); -      expect(element.attr('alt')).toEqual('myalt'); -    })); - -    it('should not pretty print JSON in attributes', inject(function($rootScope, $compile) { -      element = $compile('<img alt="{{ {a:1} }}"/>')($rootScope); -      $rootScope.$digest(); -      expect(element.attr('alt')).toEqual('{"a":1}'); -    })); - -    it('should remove special attributes on false', inject(function($rootScope, $compile) { -      element = $compile('<input ng:bind-attr="{disabled:\'{{disabled}}\', readonly:\'{{readonly}}\', checked:\'{{checked}}\'}"/>')($rootScope); -      var input = element[0]; -      expect(input.disabled).toEqual(false); -      expect(input.readOnly).toEqual(false); -      expect(input.checked).toEqual(false); - -      $rootScope.disabled = true; -      $rootScope.readonly = true; -      $rootScope.checked = true; -      $rootScope.$digest(); - -      expect(input.disabled).toEqual(true); -      expect(input.readOnly).toEqual(true); -      expect(input.checked).toEqual(true); -    })); - -  }); - -  describe('ng:click', function() { -    it('should get called on a click', inject(function($rootScope, $compile) { -      element = $compile('<div ng:click="clicked = true"></div>')($rootScope); -      $rootScope.$digest(); -      expect($rootScope.clicked).toBeFalsy(); - -      browserTrigger(element, 'click'); -      expect($rootScope.clicked).toEqual(true); -    })); - -    it('should pass event object', inject(function($rootScope, $compile) { -      element = $compile('<div ng:click="event = $event"></div>')($rootScope); -      $rootScope.$digest(); - -      browserTrigger(element, 'click'); -      expect($rootScope.event).toBeDefined(); -    })); -  }); - - -  describe('ng:submit', function() { -    it('should get called on form submit', inject(function($rootScope, $compile) { -      element = $compile('<form action="" ng:submit="submitted = true">' + -        '<input type="submit"/>' + -        '</form>')($rootScope); -      $rootScope.$digest(); -      expect($rootScope.submitted).not.toBeDefined(); - -      browserTrigger(element.children()[0]); -      expect($rootScope.submitted).toEqual(true); -    })); -  }); - -  describe('ng:class', function() { -    it('should add new and remove old classes dynamically', inject(function($rootScope, $compile) { -      element = $compile('<div class="existing" ng:class="dynClass"></div>')($rootScope); -      $rootScope.dynClass = 'A'; -      $rootScope.$digest(); -      expect(element.hasClass('existing')).toBe(true); -      expect(element.hasClass('A')).toBe(true); - -      $rootScope.dynClass = 'B'; -      $rootScope.$digest(); -      expect(element.hasClass('existing')).toBe(true); -      expect(element.hasClass('A')).toBe(false); -      expect(element.hasClass('B')).toBe(true); - -      delete $rootScope.dynClass; -      $rootScope.$digest(); -      expect(element.hasClass('existing')).toBe(true); -      expect(element.hasClass('A')).toBe(false); -      expect(element.hasClass('B')).toBe(false); -    })); - - -    it('should support adding multiple classes via an array', inject(function($rootScope, $compile) { -      element = $compile('<div class="existing" ng:class="[\'A\', \'B\']"></div>')($rootScope); -      $rootScope.$digest(); -      expect(element.hasClass('existing')).toBeTruthy(); -      expect(element.hasClass('A')).toBeTruthy(); -      expect(element.hasClass('B')).toBeTruthy(); -    })); - - -    it('should support adding multiple classes conditionally via a map of class names to boolean' + -        'expressions', inject(function($rootScope, $compile) { -      var element = $compile( -          '<div class="existing" ' + -              'ng:class="{A: conditionA, B: conditionB(), AnotB: conditionA&&!conditionB}">' + -          '</div>')($rootScope); -      $rootScope.conditionA = true; -      $rootScope.$digest(); -      expect(element.hasClass('existing')).toBeTruthy(); -      expect(element.hasClass('A')).toBeTruthy(); -      expect(element.hasClass('B')).toBeFalsy(); -      expect(element.hasClass('AnotB')).toBeTruthy(); - -      $rootScope.conditionB = function() { return true }; -      $rootScope.$digest(); -      expect(element.hasClass('existing')).toBeTruthy(); -      expect(element.hasClass('A')).toBeTruthy(); -      expect(element.hasClass('B')).toBeTruthy(); -      expect(element.hasClass('AnotB')).toBeFalsy(); -    })); - - -    it('should support adding multiple classes via a space delimited string', inject(function($rootScope, $compile) { -      element = $compile('<div class="existing" ng:class="\'A B\'"></div>')($rootScope); -      $rootScope.$digest(); -      expect(element.hasClass('existing')).toBeTruthy(); -      expect(element.hasClass('A')).toBeTruthy(); -      expect(element.hasClass('B')).toBeTruthy(); -    })); - - -    it('should preserve class added post compilation with pre-existing classes', inject(function($rootScope, $compile) { -      element = $compile('<div class="existing" ng:class="dynClass"></div>')($rootScope); -      $rootScope.dynClass = 'A'; -      $rootScope.$digest(); -      expect(element.hasClass('existing')).toBe(true); - -      // add extra class, change model and eval -      element.addClass('newClass'); -      $rootScope.dynClass = 'B'; -      $rootScope.$digest(); - -      expect(element.hasClass('existing')).toBe(true); -      expect(element.hasClass('B')).toBe(true); -      expect(element.hasClass('newClass')).toBe(true); -    })); - - -    it('should preserve class added post compilation without pre-existing classes"', inject(function($rootScope, $compile) { -      element = $compile('<div ng:class="dynClass"></div>')($rootScope); -      $rootScope.dynClass = 'A'; -      $rootScope.$digest(); -      expect(element.hasClass('A')).toBe(true); - -      // add extra class, change model and eval -      element.addClass('newClass'); -      $rootScope.dynClass = 'B'; -      $rootScope.$digest(); - -      expect(element.hasClass('B')).toBe(true); -      expect(element.hasClass('newClass')).toBe(true); -    })); - - -    it('should preserve other classes with similar name"', inject(function($rootScope, $compile) { -      element = $compile('<div class="ui-panel ui-selected" ng:class="dynCls"></div>')($rootScope); -      $rootScope.dynCls = 'panel'; -      $rootScope.$digest(); -      $rootScope.dynCls = 'foo'; -      $rootScope.$digest(); -      expect(element[0].className).toBe('ui-panel ui-selected ng-scope foo'); -    })); - - -    it('should not add duplicate classes', inject(function($rootScope, $compile) { -      element = $compile('<div class="panel bar" ng:class="dynCls"></div>')($rootScope); -      $rootScope.dynCls = 'panel'; -      $rootScope.$digest(); -      expect(element[0].className).toBe('panel bar ng-scope'); -    })); - - -    it('should remove classes even if it was specified via class attribute', inject(function($rootScope, $compile) { -      element = $compile('<div class="panel bar" ng:class="dynCls"></div>')($rootScope); -      $rootScope.dynCls = 'panel'; -      $rootScope.$digest(); -      $rootScope.dynCls = 'window'; -      $rootScope.$digest(); -      expect(element[0].className).toBe('bar ng-scope window'); -    })); - - -    it('should remove classes even if they were added by another code', inject(function($rootScope, $compile) { -      element = $compile('<div ng:class="dynCls"></div>')($rootScope); -      $rootScope.dynCls = 'foo'; -      $rootScope.$digest(); -      element.addClass('foo'); -      $rootScope.dynCls = ''; -      $rootScope.$digest(); -    })); - - -    it('should convert undefined and null values to an empty string', inject(function($rootScope, $compile) { -      element = $compile('<div ng:class="dynCls"></div>')($rootScope); -      $rootScope.dynCls = [undefined, null]; -      $rootScope.$digest(); -    })); - - -    it('should ng:class odd/even', inject(function($rootScope, $compile) { -      element = $compile('<ul><li ng:repeat="i in [0,1]" class="existing" ng:class-odd="\'odd\'" ng:class-even="\'even\'"></li><ul>')($rootScope); -      $rootScope.$digest(); -      var e1 = jqLite(element[0].childNodes[1]); -      var e2 = jqLite(element[0].childNodes[2]); -      expect(e1.hasClass('existing')).toBeTruthy(); -      expect(e1.hasClass('odd')).toBeTruthy(); -      expect(e2.hasClass('existing')).toBeTruthy(); -      expect(e2.hasClass('even')).toBeTruthy(); -    })); - - -    it('should allow both ng:class and ng:class-odd/even on the same element', inject(function($rootScope, $compile) { -      element = $compile('<ul>' + -        '<li ng:repeat="i in [0,1]" ng:class="\'plainClass\'" ' + -        'ng:class-odd="\'odd\'" ng:class-even="\'even\'"></li>' + -        '<ul>')($rootScope); -      $rootScope.$apply(); -      var e1 = jqLite(element[0].childNodes[1]); -      var e2 = jqLite(element[0].childNodes[2]); - -      expect(e1.hasClass('plainClass')).toBeTruthy(); -      expect(e1.hasClass('odd')).toBeTruthy(); -      expect(e1.hasClass('even')).toBeFalsy(); -      expect(e2.hasClass('plainClass')).toBeTruthy(); -      expect(e2.hasClass('even')).toBeTruthy(); -      expect(e2.hasClass('odd')).toBeFalsy(); -    })); - - -    it('should allow both ng:class and ng:class-odd/even with multiple classes', inject(function($rootScope, $compile) { -      element = $compile('<ul>' + -        '<li ng:repeat="i in [0,1]" ng:class="[\'A\', \'B\']" ' + -        'ng:class-odd="[\'C\', \'D\']" ng:class-even="[\'E\', \'F\']"></li>' + -        '<ul>')($rootScope); -      $rootScope.$apply(); -      var e1 = jqLite(element[0].childNodes[1]); -      var e2 = jqLite(element[0].childNodes[2]); - -      expect(e1.hasClass('A')).toBeTruthy(); -      expect(e1.hasClass('B')).toBeTruthy(); -      expect(e1.hasClass('C')).toBeTruthy(); -      expect(e1.hasClass('D')).toBeTruthy(); -      expect(e1.hasClass('E')).toBeFalsy(); -      expect(e1.hasClass('F')).toBeFalsy(); - -      expect(e2.hasClass('A')).toBeTruthy(); -      expect(e2.hasClass('B')).toBeTruthy(); -      expect(e2.hasClass('E')).toBeTruthy(); -      expect(e2.hasClass('F')).toBeTruthy(); -      expect(e2.hasClass('C')).toBeFalsy(); -      expect(e2.hasClass('D')).toBeFalsy(); -    })); -  }); - -  describe('ng:style', function() { - -    it('should set', inject(function($rootScope, $compile) { -      element = $compile('<div ng:style="{height: \'40px\'}"></div>')($rootScope); -      $rootScope.$digest(); -      expect(element.css('height')).toEqual('40px'); -    })); - - -    it('should silently ignore undefined style', inject(function($rootScope, $compile) { -      element = $compile('<div ng:style="myStyle"></div>')($rootScope); -      $rootScope.$digest(); -      expect(element.hasClass('ng-exception')).toBeFalsy(); -    })); - - -    describe('preserving styles set before and after compilation', function() { -      var scope, preCompStyle, preCompVal, postCompStyle, postCompVal, element; - -      beforeEach(inject(function($rootScope, $compile) { -        preCompStyle = 'width'; -        preCompVal = '300px'; -        postCompStyle = 'height'; -        postCompVal = '100px'; -        element = jqLite('<div ng:style="styleObj"></div>'); -        element.css(preCompStyle, preCompVal); -        jqLite(document.body).append(element); -        $compile(element)($rootScope); -        scope = $rootScope; -        scope.styleObj = {'margin-top': '44px'}; -        scope.$apply(); -        element.css(postCompStyle, postCompVal); -      })); - -      afterEach(function() { -        element.remove(); -      }); - - -      it('should not mess up stuff after compilation', function() { -        element.css('margin', '44px'); -        expect(element.css(preCompStyle)).toBe(preCompVal); -        expect(element.css('margin-top')).toBe('44px'); -        expect(element.css(postCompStyle)).toBe(postCompVal); -      }); - - -      it('should not mess up stuff after $apply with no model changes', function() { -        element.css('padding-top', '33px'); -        scope.$apply(); -        expect(element.css(preCompStyle)).toBe(preCompVal); -        expect(element.css('margin-top')).toBe('44px'); -        expect(element.css(postCompStyle)).toBe(postCompVal); -        expect(element.css('padding-top')).toBe('33px'); -      }); - - -      it('should not mess up stuff after $apply with non-colliding model changes', function() { -        scope.styleObj = {'padding-top': '99px'}; -        scope.$apply(); -        expect(element.css(preCompStyle)).toBe(preCompVal); -        expect(element.css('margin-top')).not.toBe('44px'); -        expect(element.css('padding-top')).toBe('99px'); -        expect(element.css(postCompStyle)).toBe(postCompVal); -      }); - - -      it('should overwrite original styles after a colliding model change', function() { -        scope.styleObj = {'height': '99px', 'width': '88px'}; -        scope.$apply(); -        expect(element.css(preCompStyle)).toBe('88px'); -        expect(element.css(postCompStyle)).toBe('99px'); -        scope.styleObj = {}; -        scope.$apply(); -        expect(element.css(preCompStyle)).not.toBe('88px'); -        expect(element.css(postCompStyle)).not.toBe('99px'); -      }); -    }); -  }); - - -  describe('ng:show', function() { -    it('should show and hide an element', inject(function($rootScope, $compile) { -      element = jqLite('<div ng:show="exp"></div>'); -      element = $compile(element)($rootScope); -      $rootScope.$digest(); -      expect(isCssVisible(element)).toEqual(false); -      $rootScope.exp = true; -      $rootScope.$digest(); -      expect(isCssVisible(element)).toEqual(true); -    })); - - -    it('should make hidden element visible', inject(function($rootScope, $compile) { -      element = jqLite('<div style="display: none" ng:show="exp"></div>'); -      element = $compile(element)($rootScope); -      expect(isCssVisible(element)).toBe(false); -      $rootScope.exp = true; -      $rootScope.$digest(); -      expect(isCssVisible(element)).toBe(true); -    })); -  }); - -  describe('ng:hide', function() { -    it('should hide an element', inject(function($rootScope, $compile) { -      element = jqLite('<div ng:hide="exp"></div>'); -      element = $compile(element)($rootScope); -      expect(isCssVisible(element)).toBe(true); -      $rootScope.exp = true; -      $rootScope.$digest(); -      expect(isCssVisible(element)).toBe(false); -    })); -  }); - -  describe('ng:controller', function() { -    var element; - -    beforeEach(inject(function($window) { -      $window.Greeter = function($scope) { -        // private stuff (not exported to scope) -        this.prefix = 'Hello '; - -        // public stuff (exported to scope) -        var ctrl = this; -        $scope.name = 'Misko'; -        $scope.greet = function(name) { -          return ctrl.prefix + name + ctrl.suffix; -        }; - -        $scope.protoGreet = bind(this, this.protoGreet); -      }; -      $window.Greeter.prototype = { -        suffix: '!', -        protoGreet: function(name) { -          return this.prefix + name + this.suffix; -        } -      }; - -      $window.Child = function($scope) { -        $scope.name = 'Adam'; -      }; -    })); - -    afterEach(function() { -      dealoc(element); -    }); - - -    it('should instantiate controller and bind methods', inject(function($compile, $rootScope) { -      element = $compile('<div ng:controller="Greeter">{{greet(name)}}</div>')($rootScope); -      $rootScope.$digest(); -      expect(element.text()).toBe('Hello Misko!'); -    })); - - -    it('should allow nested controllers', inject(function($compile, $rootScope) { -      element = $compile('<div ng:controller="Greeter"><div ng:controller="Child">{{greet(name)}}</div></div>')($rootScope); -      $rootScope.$digest(); -      expect(element.text()).toBe('Hello Adam!'); -      dealoc(element); - -      element = $compile('<div ng:controller="Greeter"><div ng:controller="Child">{{protoGreet(name)}}</div></div>')($rootScope); -      $rootScope.$digest(); -      expect(element.text()).toBe('Hello Adam!'); -    })); - - -    it('should instantiate controller defined on scope', inject(function($compile, $rootScope) { -      $rootScope.Greeter = function($scope) { -        $scope.name = 'Vojta'; -      }; - -      element = $compile('<div ng:controller="Greeter">{{name}}</div>')($rootScope); -      $rootScope.$digest(); -      expect(element.text()).toBe('Vojta'); -    })); -  }); - -  describe('ng:cloak', function() { - -    it('should get removed when an element is compiled', inject(function($rootScope, $compile) { -      element = jqLite('<div ng:cloak></div>'); -      expect(element.attr('ng:cloak')).toBe(''); -      $compile(element); -      expect(element.attr('ng:cloak')).toBeUndefined(); -    })); - - -    it('should remove ng-cloak class from a compiled element with attribute', inject(function($rootScope, $compile) { -      element = jqLite('<div ng:cloak class="foo ng-cloak bar"></div>'); - -      expect(element.hasClass('foo')).toBe(true); -      expect(element.hasClass('ng-cloak')).toBe(true); -      expect(element.hasClass('bar')).toBe(true); - -      $compile(element); - -      expect(element.hasClass('foo')).toBe(true); -      expect(element.hasClass('ng-cloak')).toBe(false); -      expect(element.hasClass('bar')).toBe(true); -    })); - - -    it('should remove ng-cloak class from a compiled element', inject(function($rootScope, $compile) { -      element = jqLite('<div class="foo ng-cloak bar"></div>'); - -      expect(element.hasClass('foo')).toBe(true); -      expect(element.hasClass('ng-cloak')).toBe(true); -      expect(element.hasClass('bar')).toBe(true); - -      $compile(element); - -      expect(element.hasClass('foo')).toBe(true); -      expect(element.hasClass('ng-cloak')).toBe(false); -      expect(element.hasClass('bar')).toBe(true); -    })); -  }); - - -  describe('style', function() { - -    it('should not compile style element', inject(function($compile, $rootScope) { -      element = jqLite('<style type="text/css">should {{notBound}}</style>'); -      $compile(element)($rootScope); -      $rootScope.$digest(); - -      // read innerHTML and trim to pass on IE8 -      expect(trim(element[0].innerHTML)).toBe('should {{notBound}}'); -    })); - - -    it('should compile content of element with style attr', inject(function($compile, $rootScope) { -      element = jqLite('<div style="some">{{bind}}</div>'); -      $compile(element)($rootScope); -      $rootScope.$apply(function() { -        $rootScope.bind = 'value'; -      }); - -      expect(element.text()).toBe('value'); -    })); -  }); -}); diff --git a/test/service/compilerSpec.js b/test/service/compilerSpec.js index cc4bb026..d8ee697b 100644 --- a/test/service/compilerSpec.js +++ b/test/service/compilerSpec.js @@ -1115,7 +1115,23 @@ describe('$compile', function() {          expect(observeSpy.callCount).toBe(2);          expect($exceptionHandler.errors).toEqual(['ERROR', 'ERROR']);        }); -    }) +    }); + + +    it('should translate {{}} in terminal nodes', inject(function($rootScope, $compile) { +      element = $compile('<select ng:model="x"><option value="">Greet {{name}}!</option></select>')($rootScope) +      $rootScope.$digest(); +      expect(sortedHtml(element).replace(' selected="true"', '')). +        toEqual('<select ng:model="x">' + +                  '<option>Greet !</option>' + +                '</select>'); +      $rootScope.name = 'Misko'; +      $rootScope.$digest(); +      expect(sortedHtml(element).replace(' selected="true"', '')). +        toEqual('<select ng:model="x">' + +                  '<option>Greet Misko!</option>' + +                '</select>'); +    }));    }); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js deleted file mode 100644 index 8273f838..00000000 --- a/test/widgetsSpec.js +++ /dev/null @@ -1,1223 +0,0 @@ -'use strict'; - -describe('widget', function() { -  var element; - -  afterEach(function(){ -    dealoc(element); -  }); - -  describe('ng:switch', function() { -    it('should switch on value change', inject(function($rootScope, $compile) { -      element = $compile( -        '<div ng-switch="select">' + -          '<div ng:switch-when="1">first:{{name}}</div>' + -          '<div ng:switch-when="2">second:{{name}}</div>' + -          '<div ng:switch-when="true">true:{{name}}</div>' + -        '</div>')($rootScope); -      expect(element.html()).toEqual( -          '<!-- ngSwitchWhen: 1 --><!-- ngSwitchWhen: 2 --><!-- ngSwitchWhen: true -->'); -      $rootScope.select = 1; -      $rootScope.$apply(); -      expect(element.text()).toEqual('first:'); -      $rootScope.name="shyam"; -      $rootScope.$apply(); -      expect(element.text()).toEqual('first:shyam'); -      $rootScope.select = 2; -      $rootScope.$apply(); -      expect(element.text()).toEqual('second:shyam'); -      $rootScope.name = 'misko'; -      $rootScope.$apply(); -      expect(element.text()).toEqual('second:misko'); -      $rootScope.select = true; -      $rootScope.$apply(); -      expect(element.text()).toEqual('true:misko'); -    })); - - -    it('should switch on switch-when-default', inject(function($rootScope, $compile) { -      element = $compile( -        '<ng:switch on="select">' + -          '<div ng:switch-when="1">one</div>' + -          '<div ng:switch-default>other</div>' + -        '</ng:switch>')($rootScope); -      $rootScope.$apply(); -      expect(element.text()).toEqual('other'); -      $rootScope.select = 1; -      $rootScope.$apply(); -      expect(element.text()).toEqual('one'); -    })); - - -    it('should call change on switch', inject(function($rootScope, $compile) { -      element = $compile( -        '<ng:switch on="url" change="name=\'works\'">' + -          '<div ng:switch-when="a">{{name}}</div>' + -        '</ng:switch>')($rootScope); -      $rootScope.url = 'a'; -      $rootScope.$apply(); -      expect($rootScope.name).toEqual('works'); -      expect(element.text()).toEqual('works'); -    })); -  }); - - -  describe('ng:include', function() { - -    function putIntoCache(url, content) { -      return function($templateCache) { -        $templateCache.put(url, [200, content, {}]); -      }; -    } - - -    it('should include on external file', inject(putIntoCache('myUrl', '{{name}}'), -        function($rootScope, $compile, $browser) { -      element = jqLite('<ng:include src="url" scope="childScope"></ng:include>'); -      element = $compile(element)($rootScope); -      $rootScope.childScope = $rootScope.$new(); -      $rootScope.childScope.name = 'misko'; -      $rootScope.url = 'myUrl'; -      $rootScope.$digest(); -      expect(element.text()).toEqual('misko'); -    })); - - -    it('should remove previously included text if a falsy value is bound to src', inject( -          putIntoCache('myUrl', '{{name}}'), -          function($rootScope, $compile, $browser) { -      element = jqLite('<ng:include src="url" scope="childScope"></ng:include>'); -      element = $compile(element)($rootScope); -      $rootScope.childScope = $rootScope.$new(); -      $rootScope.childScope.name = 'igor'; -      $rootScope.url = 'myUrl'; -      $rootScope.$digest(); - -      expect(element.text()).toEqual('igor'); - -      $rootScope.url = undefined; -      $rootScope.$digest(); - -      expect(element.text()).toEqual(''); -    })); - - -    it('should allow this for scope', inject(putIntoCache('myUrl', '{{"abc"}}'), -          function($rootScope, $compile, $browser) { -      element = jqLite('<ng:include src="url" scope="this"></ng:include>'); -      element = $compile(element)($rootScope); -      $rootScope.url = 'myUrl'; -      $rootScope.$digest(); - -      // TODO(misko): because we are using scope==this, the eval gets registered -      // during the flush phase and hence does not get called. -      // I don't think passing 'this' makes sense. Does having scope on ng:include makes sense? -      // should we make scope="this" illegal? -      $rootScope.$digest(); - -      expect(element.text()).toEqual('abc'); -    })); - - -    it('should fire $contentLoaded event after linking the content', inject( -        function($rootScope, $compile, $templateCache) { -      var contentLoadedSpy = jasmine.createSpy('content loaded').andCallFake(function() { -        expect(element.text()).toBe('partial content'); -      }); - -      $templateCache.put('url', [200, 'partial content', {}]); -      $rootScope.$on('$contentLoaded', contentLoadedSpy); - -      element = $compile('<ng:include src="\'url\'"></ng:include>')($rootScope); -      $rootScope.$digest(); - -      expect(contentLoadedSpy).toHaveBeenCalledOnce(); -    })); - - -    it('should evaluate onload expression when a partial is loaded', inject( -        putIntoCache('myUrl', 'my partial'), -        function($rootScope, $compile, $browser) { -      element = jqLite('<ng:include src="url" onload="loaded = true"></ng:include>'); -      element = $compile(element)($rootScope); - -      expect($rootScope.loaded).not.toBeDefined(); - -      $rootScope.url = 'myUrl'; -      $rootScope.$digest(); - -      expect(element.text()).toEqual('my partial'); -      expect($rootScope.loaded).toBe(true); -    })); - - -    it('should destroy old scope', inject(putIntoCache('myUrl', 'my partial'), -          function($rootScope, $compile, $browser) { -      element = jqLite('<ng:include src="url"></ng:include>'); -      element = $compile(element)($rootScope); - -      expect($rootScope.$$childHead).toBeFalsy(); - -      $rootScope.url = 'myUrl'; -      $rootScope.$digest(); -      expect($rootScope.$$childHead).toBeTruthy(); - -      $rootScope.url = null; -      $rootScope.$digest(); -      expect($rootScope.$$childHead).toBeFalsy(); -    })); - - -    it('should do xhr request and cache it', -        inject(function($rootScope, $httpBackend, $compile, $browser) { -      element = $compile('<ng:include src="url"></ng:include>')($rootScope); -      $httpBackend.expect('GET', 'myUrl').respond('my partial'); - -      $rootScope.url = 'myUrl'; -      $rootScope.$digest(); -      $httpBackend.flush(); -      expect(element.text()).toEqual('my partial'); - -      $rootScope.url = null; -      $rootScope.$digest(); -      expect(element.text()).toEqual(''); - -      $rootScope.url = 'myUrl'; -      $rootScope.$digest(); -      expect(element.text()).toEqual('my partial'); -      dealoc($rootScope); -    })); - - -    it('should clear content when error during xhr request', -        inject(function($httpBackend, $compile, $rootScope) { -      element = $compile('<ng:include src="url">content</ng:include>')($rootScope); -      $httpBackend.expect('GET', 'myUrl').respond(404, ''); - -      $rootScope.url = 'myUrl'; -      $rootScope.$digest(); -      $httpBackend.flush(); - -      expect(element.text()).toBe(''); -    })); - - -    it('should be async even if served from cache', inject( -          putIntoCache('myUrl', 'my partial'), -          function($rootScope, $compile, $browser) { -      element = $compile('<ng:include src="url"></ng:include>')($rootScope); - -      $rootScope.url = 'myUrl'; - -      var called = 0; -      // we want to assert only during first watch -      $rootScope.$watch(function() { -        if (!called++) expect(element.text()).toBe(''); -      }); - -      $rootScope.$digest(); -      expect(element.text()).toBe('my partial'); -    })); - - -    it('should discard pending xhr callbacks if a new template is requested before the current ' + -        'finished loading', inject(function($rootScope, $compile, $httpBackend) { -      element = jqLite("<ng:include src='templateUrl'></ng:include>"); -      var log = []; - -      $rootScope.templateUrl = 'myUrl1'; -      $rootScope.logger = function(msg) { -        log.push(msg); -      } -      $compile(element)($rootScope); -      expect(log.join('; ')).toEqual(''); - -      $httpBackend.expect('GET', 'myUrl1').respond('<div>{{logger("url1")}}</div>'); -      $rootScope.$digest(); -      expect(log.join('; ')).toEqual(''); -      $rootScope.templateUrl = 'myUrl2'; -      $httpBackend.expect('GET', 'myUrl2').respond('<div>{{logger("url2")}}</div>'); -      $rootScope.$digest(); -      $httpBackend.flush(); // now that we have two requests pending, flush! - -      expect(log.join('; ')).toEqual('url2; url2'); // it's here twice because we go through at -                                                    // least two digest cycles -    })); - - -    it('should compile only the content', inject(function($compile, $rootScope, $templateCache) { -      // regression - -      var onload = jasmine.createSpy('$contentLoaded'); -      $rootScope.$on('$contentLoaded', onload); -      $templateCache.put('tpl.html', [200, 'partial {{tpl}}', {}]); - -      element = $compile('<div><div ng:repeat="i in [1]">' + -          '<ng:include src="tpl"></ng:include></div></div>')($rootScope); -      expect(onload).not.toHaveBeenCalled(); - -      $rootScope.$apply(function() { -        $rootScope.tpl = 'tpl.html'; -      }); -      expect(onload).toHaveBeenCalledOnce(); -    })); - - -    describe('autoscoll', function() { -      var autoScrollSpy; - -      function spyOnAnchorScroll() { -        return function($provide) { -          autoScrollSpy = jasmine.createSpy('$anchorScroll'); -          $provide.value('$anchorScroll', autoScrollSpy); -        }; -      } - -      function compileAndLink(tpl) { -        return function($compile, $rootScope) { -          element = $compile(tpl)($rootScope); -        }; -      } - -      function changeTplAndValueTo(template, value) { -        return function($rootScope, $browser) { -          $rootScope.$apply(function() { -            $rootScope.tpl = template; -            $rootScope.value = value; -          }); -        }; -      } - -      beforeEach(module(spyOnAnchorScroll())); -      beforeEach(inject( -          putIntoCache('template.html', 'CONTENT'), -          putIntoCache('another.html', 'CONTENT'))); - - -      it('should call $anchorScroll if autoscroll attribute is present', inject( -          compileAndLink('<ng:include src="tpl" autoscroll></ng:include>'), -          changeTplAndValueTo('template.html'), function() { -        expect(autoScrollSpy).toHaveBeenCalledOnce(); -      })); - - -      it('should call $anchorScroll if autoscroll evaluates to true', inject( -          compileAndLink('<ng:include src="tpl" autoscroll="value"></ng:include>'), -          changeTplAndValueTo('template.html', true), -          changeTplAndValueTo('another.html', 'some-string'), -          changeTplAndValueTo('template.html', 100), function() { -        expect(autoScrollSpy).toHaveBeenCalled(); -        expect(autoScrollSpy.callCount).toBe(3); -      })); - - -      it('should not call $anchorScroll if autoscroll attribute is not present', inject( -          compileAndLink('<ng:include src="tpl"></ng:include>'), -          changeTplAndValueTo('template.html'), function() { -        expect(autoScrollSpy).not.toHaveBeenCalled(); -      })); - - -      it('should not call $anchorScroll if autoscroll evaluates to false', inject( -          compileAndLink('<ng:include src="tpl" autoscroll="value"></ng:include>'), -          changeTplAndValueTo('template.html', false), -          changeTplAndValueTo('template.html', undefined), -          changeTplAndValueTo('template.html', null), function() { -        expect(autoScrollSpy).not.toHaveBeenCalled(); -      })); -    }); -  }); - -  describe('a', function() { -    it('should prevent default action to be executed when href is empty', -        inject(function($rootScope, $compile) { -      var orgLocation = document.location.href, -          preventDefaultCalled = false, -          event; - -      element = $compile('<a href="">empty link</a>')($rootScope); - -      if (msie < 9) { - -        event = document.createEventObject(); -        expect(event.returnValue).not.toBeDefined(); -        element[0].fireEvent('onclick', event); -        expect(event.returnValue).toEqual(false); - -      } else { - -        event = document.createEvent('MouseEvent'); -        event.initMouseEvent( -          'click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - -        event.preventDefaultOrg = event.preventDefault; -        event.preventDefault = function() { -          preventDefaultCalled = true; -          if (this.preventDefaultOrg) this.preventDefaultOrg(); -        }; - -        element[0].dispatchEvent(event); - -        expect(preventDefaultCalled).toEqual(true); -      } - -      expect(document.location.href).toEqual(orgLocation); -    })); -  }); - - -  describe('ng:repeat', function() { -    it('should ng:repeat over array', inject(function($rootScope, $compile) { -      element = $compile( -        '<ul>' + -          '<li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li>' + -        '</ul>')($rootScope); - -      Array.prototype.extraProperty = "should be ignored"; -      // INIT -      $rootScope.items = ['misko', 'shyam']; -      $rootScope.$digest(); -      expect(element.find('li').length).toEqual(2); -      expect(element.text()).toEqual('misko;shyam;'); -      delete Array.prototype.extraProperty; - -      // GROW -      $rootScope.items = ['adam', 'kai', 'brad']; -      $rootScope.$digest(); -      expect(element.find('li').length).toEqual(3); -      expect(element.text()).toEqual('adam;kai;brad;'); - -      // SHRINK -      $rootScope.items = ['brad']; -      $rootScope.$digest(); -      expect(element.find('li').length).toEqual(1); -      expect(element.text()).toEqual('brad;'); -    })); - - -    it('should ng:repeat over object', inject(function($rootScope, $compile) { -      element = $compile( -        '<ul>' + -          '<li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li>' + -        '</ul>')($rootScope); -      $rootScope.items = {misko:'swe', shyam:'set'}; -      $rootScope.$digest(); -      expect(element.text()).toEqual('misko:swe;shyam:set;'); -    })); - - -    it('should not ng:repeat over parent properties', inject(function($rootScope, $compile) { -      var Class = function() {}; -      Class.prototype.abc = function() {}; -      Class.prototype.value = 'abc'; - -      element = $compile( -        '<ul>' + -          '<li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li>' + -        '</ul>')($rootScope); -      $rootScope.items = new Class(); -      $rootScope.items.name = 'value'; -      $rootScope.$digest(); -      expect(element.text()).toEqual('name:value;'); -    })); - - -    it('should error on wrong parsing of ng:repeat', inject(function($rootScope, $compile, $log) { -      expect(function() { -        element = $compile('<ul><li ng:repeat="i dont parse"></li></ul>')($rootScope); -      }).toThrow("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'."); - -      $log.error.logs.shift(); -    })); - - -    it('should expose iterator offset as $index when iterating over arrays', -        inject(function($rootScope, $compile) { -      element = $compile( -        '<ul>' + -          '<li ng:repeat="item in items" ng:bind="item + $index + \'|\'"></li>' + -        '</ul>')($rootScope); -      $rootScope.items = ['misko', 'shyam', 'frodo']; -      $rootScope.$digest(); -      expect(element.text()).toEqual('misko0|shyam1|frodo2|'); -    })); - - -    it('should expose iterator offset as $index when iterating over objects', -        inject(function($rootScope, $compile) { -      element = $compile( -        '<ul>' + -          '<li ng:repeat="(key, val) in items" ng:bind="key + \':\' + val + $index + \'|\'"></li>' + -        '</ul>')($rootScope); -      $rootScope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'}; -      $rootScope.$digest(); -      expect(element.text()).toEqual('frodo:f0|misko:m1|shyam:s2|'); -    })); - - -    it('should expose iterator position as $position when iterating over arrays', -        inject(function($rootScope, $compile) { -      element = $compile( -        '<ul>' + -          '<li ng:repeat="item in items" ng:bind="item + \':\' + $position + \'|\'"></li>' + -        '</ul>')($rootScope); -      $rootScope.items = ['misko', 'shyam', 'doug']; -      $rootScope.$digest(); -      expect(element.text()).toEqual('misko:first|shyam:middle|doug:last|'); - -      $rootScope.items.push('frodo'); -      $rootScope.$digest(); -      expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|'); - -      $rootScope.items.pop(); -      $rootScope.items.pop(); -      $rootScope.$digest(); -      expect(element.text()).toEqual('misko:first|shyam:last|'); -    })); - - -    it('should expose iterator position as $position when iterating over objects', -        inject(function($rootScope, $compile) { -      element = $compile( -        '<ul>' + -          '<li ng:repeat="(key, val) in items" ng:bind="key + \':\' + val + \':\' + $position + \'|\'">' + -          '</li>' + -        '</ul>')($rootScope); -      $rootScope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'}; -      $rootScope.$digest(); -      expect(element.text()).toEqual('doug:d:first|frodo:f:middle|misko:m:middle|shyam:s:last|'); - -      delete $rootScope.items.doug; -      delete $rootScope.items.frodo; -      $rootScope.$digest(); -      expect(element.text()).toEqual('misko:m:first|shyam:s:last|'); -    })); - - -    it('should ignore $ and $$ properties', inject(function($rootScope, $compile) { -      element = $compile('<ul><li ng:repeat="i in items">{{i}}|</li></ul>')($rootScope); -      $rootScope.items = ['a', 'b', 'c']; -      $rootScope.items.$$hashkey = 'xxx'; -      $rootScope.items.$root = 'yyy'; -      $rootScope.$digest(); - -      expect(element.text()).toEqual('a|b|c|'); -    })); - - -    it('should repeat over nested arrays', inject(function($rootScope, $compile) { -      element = $compile( -        '<ul>' + -          '<li ng:repeat="subgroup in groups">' + -            '<div ng:repeat="group in subgroup">{{group}}|</div>X' + -          '</li>' + -        '</ul>')($rootScope); -      $rootScope.groups = [['a', 'b'], ['c','d']]; -      $rootScope.$digest(); - -      expect(element.text()).toEqual('a|b|Xc|d|X'); -    })); - - -    it('should ignore non-array element properties when iterating over an array', -        inject(function($rootScope, $compile) { -      element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); -      $rootScope.array = ['a', 'b', 'c']; -      $rootScope.array.foo = '23'; -      $rootScope.array.bar = function() {}; -      $rootScope.$digest(); - -      expect(element.text()).toBe('a|b|c|'); -    })); - - -    it('should iterate over non-existent elements of a sparse array', -        inject(function($rootScope, $compile) { -      element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); -      $rootScope.array = ['a', 'b']; -      $rootScope.array[4] = 'c'; -      $rootScope.array[6] = 'd'; -      $rootScope.$digest(); - -      expect(element.text()).toBe('a|b|||c||d|'); -    })); - - -    it('should iterate over all kinds of types', inject(function($rootScope, $compile) { -      element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); -      $rootScope.array = ['a', 1, null, undefined, {}]; -      $rootScope.$digest(); - -      expect(element.text()).toMatch(/a\|1\|\|\|\{\s*\}\|/); -    })); - - -    describe('stability', function() { -      var a, b, c, d, lis; - -      beforeEach(inject(function($rootScope, $compile) { -        element = $compile( -          '<ul>' + -            '<li ng:repeat="item in items" ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li>' + -          '</ul>')($rootScope); -        a = {}; -        b = {}; -        c = {}; -        d = {}; - -        $rootScope.items = [a, b, c]; -        $rootScope.$digest(); -        lis = element.find('li'); -      })); - - -      it('should preserve the order of elements', inject(function($rootScope) { -        $rootScope.items = [a, c, d]; -        $rootScope.$digest(); -        var newElements = element.find('li'); -        expect(newElements[0]).toEqual(lis[0]); -        expect(newElements[1]).toEqual(lis[2]); -        expect(newElements[2]).not.toEqual(lis[1]); -      })); - - -      it('should support duplicates', inject(function($rootScope) { -        $rootScope.items = [a, a, b, c]; -        $rootScope.$digest(); -        var newElements = element.find('li'); -        expect(newElements[0]).toEqual(lis[0]); -        expect(newElements[1]).not.toEqual(lis[0]); -        expect(newElements[2]).toEqual(lis[1]); -        expect(newElements[3]).toEqual(lis[2]); - -        lis = newElements; -        $rootScope.$digest(); -        newElements = element.find('li'); -        expect(newElements[0]).toEqual(lis[0]); -        expect(newElements[1]).toEqual(lis[1]); -        expect(newElements[2]).toEqual(lis[2]); -        expect(newElements[3]).toEqual(lis[3]); - -        $rootScope.$digest(); -        newElements = element.find('li'); -        expect(newElements[0]).toEqual(lis[0]); -        expect(newElements[1]).toEqual(lis[1]); -        expect(newElements[2]).toEqual(lis[2]); -        expect(newElements[3]).toEqual(lis[3]); -      })); - - -      it('should remove last item when one duplicate instance is removed', -          inject(function($rootScope) { -        $rootScope.items = [a, a, a]; -        $rootScope.$digest(); -        lis = element.find('li'); - -        $rootScope.items = [a, a]; -        $rootScope.$digest(); -        var newElements = element.find('li'); -        expect(newElements.length).toEqual(2); -        expect(newElements[0]).toEqual(lis[0]); -        expect(newElements[1]).toEqual(lis[1]); -      })); - - -      it('should reverse items when the collection is reversed', -          inject(function($rootScope) { -        $rootScope.items = [a, b, c]; -        $rootScope.$digest(); -        lis = element.find('li'); - -        $rootScope.items = [c, b, a]; -        $rootScope.$digest(); -        var newElements = element.find('li'); -        expect(newElements.length).toEqual(3); -        expect(newElements[0]).toEqual(lis[2]); -        expect(newElements[1]).toEqual(lis[1]); -        expect(newElements[2]).toEqual(lis[0]); -      })); -    }); -  }); - - -  describe('@ng:non-bindable', function() { -    it('should prevent compilation of the owning element and its children', -        inject(function($rootScope, $compile) { -      element = $compile('<div ng:non-bindable><span ng:bind="name"></span></div>')($rootScope); -      $rootScope.name =  'misko'; -      $rootScope.$digest(); -      expect(element.text()).toEqual(''); -    })); -  }); - - -  describe('ng:view', function() { -    beforeEach(module(function() { -      return function($rootScope, $compile) { -        element = $compile('<ng:view onload="load()"></ng:view>')($rootScope); -      }; -    })); - - -    it('should do nothing when no routes are defined', -        inject(function($rootScope, $compile, $location) { -      $location.path('/unknown'); -      $rootScope.$digest(); -      expect(element.text()).toEqual(''); -    })); - - -    it('should instantiate controller after compiling the content', function() { -      var log = [], controllerScope, -          Ctrl = function($scope) { -            controllerScope = $scope; -            log.push('ctrl-init'); -          }; - -      module(function($compileProvider, $routeProvider) { -        $compileProvider.directive('compileLog', function() { -          return { -            compile: function() { -              log.push('compile'); -            } -          }; -        }); - -        $routeProvider.when('/some', {template: '/tpl.html', controller: Ctrl}); -      }); - -      inject(function($route, $rootScope, $templateCache, $location) { -        $templateCache.put('/tpl.html', [200, '<div compile-log>partial</div>', {}]); -        $location.path('/some'); -        $rootScope.$digest(); - -        expect(controllerScope.$parent).toBe($rootScope); -        expect(controllerScope).toBe($route.current.scope); -        expect(log).toEqual(['compile', 'ctrl-init']); -      }); -    }); - - -    it('should load content via xhr when route changes', function() { -      module(function($routeProvider) { -        $routeProvider.when('/foo', {template: 'myUrl1'}); -        $routeProvider.when('/bar', {template: 'myUrl2'}); -      }); - -      inject(function($rootScope, $compile, $httpBackend, $location, $route) { -        expect(element.text()).toEqual(''); - -        $location.path('/foo'); -        $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); -        $rootScope.$digest(); -        $httpBackend.flush(); -        expect(element.text()).toEqual('4'); - -        $location.path('/bar'); -        $httpBackend.expect('GET', 'myUrl2').respond('angular is da best'); -        $rootScope.$digest(); -        $httpBackend.flush(); -        expect(element.text()).toEqual('angular is da best'); -      }); -    }); - - -    it('should remove all content when location changes to an unknown route', function() { -      module(function($routeProvider) { -        $routeProvider.when('/foo', {template: 'myUrl1'}); -      }); - -      inject(function($rootScope, $compile, $location, $httpBackend, $route) { -        $location.path('/foo'); -        $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); -        $rootScope.$digest(); -        $httpBackend.flush(); -        expect(element.text()).toEqual('4'); - -        $location.path('/unknown'); -        $rootScope.$digest(); -        expect(element.text()).toEqual(''); -      }); -    }); - - -    it('should chain scopes and propagate evals to the child scope', function() { -      module(function($routeProvider) { -        $routeProvider.when('/foo', {template: 'myUrl1'}); -      }); - -      inject(function($rootScope, $compile, $location, $httpBackend, $route) { -        $rootScope.parentVar = 'parent'; - -        $location.path('/foo'); -        $httpBackend.expect('GET', 'myUrl1').respond('<div>{{parentVar}}</div>'); -        $rootScope.$digest(); -        $httpBackend.flush(); -        expect(element.text()).toEqual('parent'); - -        $rootScope.parentVar = 'new parent'; -        $rootScope.$digest(); -        expect(element.text()).toEqual('new parent'); -      }); -    }); - - -    it('should be possible to nest ng:view in ng:include', inject(function() { -      // TODO(vojta): refactor this test -      dealoc(element); -      var injector = angular.injector(['ng', 'ngMock', function($routeProvider) { -        $routeProvider.when('/foo', {controller: angular.noop, template: 'viewPartial.html'}); -      }]); -      var myApp = injector.get('$rootScope'); -      var $httpBackend = injector.get('$httpBackend'); -      $httpBackend.expect('GET', 'includePartial.html').respond('view: <ng:view></ng:view>'); -      injector.get('$location').path('/foo'); - -      var $route = injector.get('$route'); - -      element = injector.get('$compile')( -          '<div>' + -            'include: <ng:include src="\'includePartial.html\'"> </ng:include>' + -          '</div>')(myApp); -      myApp.$apply(); - -      $httpBackend.expect('GET', 'viewPartial.html').respond('content'); -      $httpBackend.flush(); - -      expect(element.text()).toEqual('include: view: content'); -      expect($route.current.template).toEqual('viewPartial.html'); -      dealoc(myApp); -      dealoc(element); -    })); - - -    it('should initialize view template after the view controller was initialized even when ' + -       'templates were cached', function() { -       //this is a test for a regression that was introduced by making the ng:view cache sync -      function ParentCtrl($scope) { -         $scope.log.push('parent'); -      } - -      module(function($routeProvider) { -        $routeProvider.when('/foo', {controller: ParentCtrl, template: 'viewPartial.html'}); -      }); - - -      inject(function($rootScope, $compile, $location, $httpBackend, $route) { -        $rootScope.log = []; - -        $rootScope.ChildCtrl = function($scope) { -          $scope.log.push('child'); -        }; - -        $location.path('/foo'); -        $httpBackend.expect('GET', 'viewPartial.html'). -            respond('<div ng:init="log.push(\'init\')">' + -                      '<div ng:controller="ChildCtrl"></div>' + -                    '</div>'); -        $rootScope.$apply(); -        $httpBackend.flush(); - -        expect($rootScope.log).toEqual(['parent', 'init', 'child']); - -        $location.path('/'); -        $rootScope.$apply(); -        expect($rootScope.log).toEqual(['parent', 'init', 'child']); - -        $rootScope.log = []; -        $location.path('/foo'); -        $rootScope.$apply(); - -        expect($rootScope.log).toEqual(['parent', 'init', 'child']); -      }); -    }); - - -    it('should discard pending xhr callbacks if a new route is requested before the current ' + -        'finished loading',  function() { -      // this is a test for a bad race condition that affected feedback - -      module(function($routeProvider) { -        $routeProvider.when('/foo', {template: 'myUrl1'}); -        $routeProvider.when('/bar', {template: 'myUrl2'}); -      }); - -      inject(function($route, $rootScope, $location, $httpBackend) { -        expect(element.text()).toEqual(''); - -        $location.path('/foo'); -        $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); -        $rootScope.$digest(); -        $location.path('/bar'); -        $httpBackend.expect('GET', 'myUrl2').respond('<div>{{1+1}}</div>'); -        $rootScope.$digest(); -        $httpBackend.flush(); // now that we have two requests pending, flush! - -        expect(element.text()).toEqual('2'); -      }); -    }); - - -    it('should clear the content when error during xhr request', function() { -      module(function($routeProvider) { -        $routeProvider.when('/foo', {controller: noop, template: 'myUrl1'}); -      }); - -      inject(function($route, $location, $rootScope, $httpBackend) { -        $location.path('/foo'); -        $httpBackend.expect('GET', 'myUrl1').respond(404, ''); -        element.text('content'); - -        $rootScope.$digest(); -        $httpBackend.flush(); - -        expect(element.text()).toBe(''); -      }); -    }); - - -    it('should be async even if served from cache', function() { -      module(function($routeProvider) { -        $routeProvider.when('/foo', {controller: noop, template: 'myUrl1'}); -      }); - -      inject(function($route, $rootScope, $location, $templateCache) { -        $templateCache.put('myUrl1', [200, 'my partial', {}]); -        $location.path('/foo'); - -        var called = 0; -        // we want to assert only during first watch -        $rootScope.$watch(function() { -          if (!called++) expect(element.text()).toBe(''); -        }); - -        $rootScope.$digest(); -        expect(element.text()).toBe('my partial'); -      }); -    }); - -    it('should fire $contentLoaded event when content compiled and linked', function() { -      var log = []; -      var logger = function(name) { -        return function() { -          log.push(name); -        }; -      }; -      var Ctrl = function($scope) { -        $scope.value = 'bound-value'; -        log.push('init-ctrl'); -      }; - -      module(function($routeProvider) { -        $routeProvider.when('/foo', {template: 'tpl.html', controller: Ctrl}); -      }); - -      inject(function($templateCache, $rootScope, $location) { -        $rootScope.$on('$beforeRouteChange', logger('$beforeRouteChange')); -        $rootScope.$on('$afterRouteChange', logger('$afterRouteChange')); -        $rootScope.$on('$contentLoaded', logger('$contentLoaded')); - -        $templateCache.put('tpl.html', [200, '{{value}}', {}]); -        $location.path('/foo'); -        $rootScope.$digest(); - -        expect(element.text()).toBe('bound-value'); -        expect(log).toEqual(['$beforeRouteChange', '$afterRouteChange', 'init-ctrl', -                             '$contentLoaded']); -      }); -    }); - -    it('should destroy previous scope', function() { -      module(function($routeProvider) { -        $routeProvider.when('/foo', {template: 'tpl.html'}); -      }); - -      inject(function($templateCache, $rootScope, $location) { -        $templateCache.put('tpl.html', [200, 'partial', {}]); - -        expect($rootScope.$$childHead).toBeNull(); -        expect($rootScope.$$childTail).toBeNull(); - -        $location.path('/foo'); -        $rootScope.$digest(); - -        expect(element.text()).toBe('partial'); -        expect($rootScope.$$childHead).not.toBeNull(); -        expect($rootScope.$$childTail).not.toBeNull(); - -        $location.path('/non/existing/route'); -        $rootScope.$digest(); - -        expect(element.text()).toBe(''); -        expect($rootScope.$$childHead).toBeNull(); -        expect($rootScope.$$childTail).toBeNull(); -      }); -    }); - - -    it('should destroy previous scope if multiple route changes occur before server responds', -        function() { -      var log = []; -      var createCtrl = function(name) { -        return function($scope) { -          log.push('init-' + name); -          $scope.$on('$destroy', function() { -            log.push('destroy-' + name); -          }); -        }; -      }; - -      module(function($routeProvider) { -        $routeProvider.when('/one', {template: 'one.html', controller: createCtrl('ctrl1')}); -        $routeProvider.when('/two', {template: 'two.html', controller: createCtrl('ctrl2')}); -      }); - -      inject(function($httpBackend, $rootScope, $location) { -        $httpBackend.whenGET('one.html').respond('content 1'); -        $httpBackend.whenGET('two.html').respond('content 2'); - -        $location.path('/one'); -        $rootScope.$digest(); -        $location.path('/two'); -        $rootScope.$digest(); - -        $httpBackend.flush(); -        expect(element.text()).toBe('content 2'); -        expect(log).toEqual(['init-ctrl2']); - -        $location.path('/non-existing'); -        $rootScope.$digest(); - -        expect(element.text()).toBe(''); -        expect(log).toEqual(['init-ctrl2', 'destroy-ctrl2']); - -        expect($rootScope.$$childHead).toBeNull(); -        expect($rootScope.$$childTail).toBeNull(); -      }); -    }); - - -    it('should $destroy scope after update and reload',  function() { -      // this is a regression of bug, where $route doesn't copy scope when only updating - -      var log = []; - -      function logger(msg) { -        return function() { -          log.push(msg); -        }; -      } - -      function createController(name) { -        return function($scope) { -          log.push('init-' + name); -          $scope.$on('$destroy', logger('destroy-' + name)); -          $scope.$on('$routeUpdate', logger('route-update')); -        }; -      } - -      module(function($routeProvider) { -        $routeProvider.when('/bar', {template: 'tpl.html', controller: createController('bar')}); -        $routeProvider.when('/foo', { -            template: 'tpl.html', controller: createController('foo'), reloadOnSearch: false}); -      }); - -      inject(function($templateCache, $location, $rootScope) { -        $templateCache.put('tpl.html', [200, 'partial', {}]); - -        $location.url('/foo'); -        $rootScope.$digest(); -        expect(log).toEqual(['init-foo']); - -        $location.search({q: 'some'}); -        $rootScope.$digest(); -        expect(log).toEqual(['init-foo', 'route-update']); - -        $location.url('/bar'); -        $rootScope.$digest(); -        expect(log).toEqual(['init-foo', 'route-update', 'destroy-foo', 'init-bar']); -      }); -    }); - - -    it('should evaluate onload expression after linking the content', function() { -      module(function($routeProvider) { -        $routeProvider.when('/foo', {template: 'tpl.html'}); -      }); - -      inject(function($templateCache, $location, $rootScope) { -        $templateCache.put('tpl.html', [200, '{{1+1}}', {}]); -        $rootScope.load = jasmine.createSpy('onload'); - -        $location.url('/foo'); -        $rootScope.$digest(); -        expect($rootScope.load).toHaveBeenCalledOnce(); -      }); -    }) -  }); - - -  describe('ng:pluralize', function() { - -    describe('deal with pluralized strings without offset', function() { -       beforeEach(inject(function($rootScope, $compile) { -          element = $compile( -            '<ng:pluralize count="email"' + -                           "when=\"{'0': 'You have no new email'," + -                                   "'one': 'You have one new email'," + -                                   "'other': 'You have {} new emails'}\">" + -            '</ng:pluralize>')($rootScope); -        })); - - -        it('should show single/plural strings', inject(function($rootScope) { -          $rootScope.email = 0; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have no new email'); - -          $rootScope.email = '0'; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have no new email'); - -          $rootScope.email = 1; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have one new email'); - -          $rootScope.email = 0.01; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have 0.01 new emails'); - -          $rootScope.email = '0.1'; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have 0.1 new emails'); - -          $rootScope.email = 2; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have 2 new emails'); - -          $rootScope.email = -0.1; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have -0.1 new emails'); - -          $rootScope.email = '-0.01'; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have -0.01 new emails'); - -          $rootScope.email = -2; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have -2 new emails'); -        })); - - -        it('should show single/plural strings with mal-formed inputs', inject(function($rootScope) { -          $rootScope.email = ''; -          $rootScope.$digest(); -          expect(element.text()).toBe(''); - -          $rootScope.email = null; -          $rootScope.$digest(); -          expect(element.text()).toBe(''); - -          $rootScope.email = undefined; -          $rootScope.$digest(); -          expect(element.text()).toBe(''); - -          $rootScope.email = 'a3'; -          $rootScope.$digest(); -          expect(element.text()).toBe(''); - -          $rootScope.email = '011'; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have 11 new emails'); - -          $rootScope.email = '-011'; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have -11 new emails'); - -          $rootScope.email = '1fff'; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have one new email'); - -          $rootScope.email = '0aa22'; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have no new email'); - -          $rootScope.email = '000001'; -          $rootScope.$digest(); -          expect(element.text()).toBe('You have one new email'); -        })); -    }); - - -    describe('deal with pluralized strings with offset', function() { -      it('should show single/plural strings with offset', inject(function($rootScope, $compile) { -        element = $compile( -          "<ng:pluralize count=\"viewCount\"  offset=2 " + -              "when=\"{'0': 'Nobody is viewing.'," + -                      "'1': '{{p1}} is viewing.'," + -                      "'2': '{{p1}} and {{p2}} are viewing.'," + -                      "'one': '{{p1}}, {{p2}} and one other person are viewing.'," + -                      "'other': '{{p1}}, {{p2}} and {} other people are viewing.'}\">" + -          "</ng:pluralize>")($rootScope); -        $rootScope.p1 = 'Igor'; -        $rootScope.p2 = 'Misko'; - -        $rootScope.viewCount = 0; -        $rootScope.$digest(); -        expect(element.text()).toBe('Nobody is viewing.'); - -        $rootScope.viewCount = 1; -        $rootScope.$digest(); -        expect(element.text()).toBe('Igor is viewing.'); - -        $rootScope.viewCount = 2; -        $rootScope.$digest(); -        expect(element.text()).toBe('Igor and Misko are viewing.'); - -        $rootScope.viewCount = 3; -        $rootScope.$digest(); -        expect(element.text()).toBe('Igor, Misko and one other person are viewing.'); - -        $rootScope.viewCount = 4; -        $rootScope.$digest(); -        expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.'); -      })); -    }); -  }); - - -  describe('scriptDirective', function() { -    it('should populate $templateCache with contents of a ng-template script element', inject( -        function($compile, $templateCache) { -          if (msie <=8) return; -          // in ie8 it is not possible to create a script tag with the right content. -          // it always comes up as empty. I was trying to set the text of the -          // script tag, but that did not work either, so I gave up. -          $compile('<div>foo' + -                     '<script id="/ignore">ignore me</script>' + -                     '<script type="text/ng-template" id="/myTemplate.html"><x>{{y}}</x></script>' + -                   '</div>' ); -          expect($templateCache.get('/myTemplate.html')).toBe('<x>{{y}}</x>'); -          expect($templateCache.get('/ignore')).toBeUndefined(); -        } -    )); - - -    it('should not compile scripts', inject(function($compile, $templateCache, $rootScope) { -      if (msie <=8) return; // see above - -      var doc = jqLite('<div></div>'); -      // jQuery is too smart and removes -      doc[0].innerHTML = '<script type="text/javascript">some {{binding}}</script>' + -                         '<script type="text/ng-template" id="/some">other {{binding}}</script>'; - -      $compile(doc)($rootScope); -      $rootScope.$digest(); - -      var scripts = doc.find('script'); -      expect(scripts.eq(0).text()).toBe('some {{binding}}'); -      expect(scripts.eq(1).text()).toBe('other {{binding}}'); -      dealoc(doc); -    })); -  }); -}); | 
