diff options
| author | Misko Hevery | 2011-11-22 21:28:39 -0800 | 
|---|---|---|
| committer | Misko Hevery | 2012-01-25 11:50:37 -0800 | 
| commit | 9ee2cdff44e7d496774b340de816344126c457b3 (patch) | |
| tree | 476ffcb4425e7160865029d6b57d41b766750285 | |
| parent | 8af4fde18246ac1587b471a549e70d5d858bf0ee (diff) | |
| download | angular.js-9ee2cdff44e7d496774b340de816344126c457b3.tar.bz2 | |
refactor(directives): connect new compiler
- turn everything into a directive
36 files changed, 1558 insertions, 1734 deletions
| diff --git a/css/angular.css b/css/angular.css index 0c90b569..44513c65 100644 --- a/css/angular.css +++ b/css/angular.css @@ -1,6 +1,7 @@  @charset "UTF-8"; -[ng\:cloak], .ng-cloak { +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], +.ng-cloak, .x-ng-cloak {    display: none;  } diff --git a/docs/content/api/angular.inputType.ngdoc b/docs/content/api/angular.inputType.ngdoc index bfd5fe6f..9cbf9eb2 100644 --- a/docs/content/api/angular.inputType.ngdoc +++ b/docs/content/api/angular.inputType.ngdoc @@ -84,7 +84,7 @@ All `inputType` widgets support:    it('should invalidate on wrong input', function() {      expect(element('form[name=myForm]').prop('className')).toMatch('ng-valid');      input('data').enter('{}'); -    expect(binding('data')).toEqual('data={\n  }'); +    expect(binding('data')).toEqual('{}');      input('data').enter('{');      expect(element('form[name=myForm]').prop('className')).toMatch('ng-invalid');    }); diff --git a/docs/content/guide/dev_guide.expressions.ngdoc b/docs/content/guide/dev_guide.expressions.ngdoc index b7ecc521..61592897 100644 --- a/docs/content/guide/dev_guide.expressions.ngdoc +++ b/docs/content/guide/dev_guide.expressions.ngdoc @@ -185,16 +185,20 @@ Extensions: You can further extend the expression vocabulary by adding new metho     {name:'Julie', phone:'555-8765'}]"></div>   Search: <input ng:model="searchText"/>   <table class="example3"> -   <tr><th>Name</th><th>Phone</th><tr> -   <tr ng:repeat="friend in friends | filter:searchText"> -     <td>{{friend.name}}</td> -     <td>{{friend.phone}}</td> -   </tr> +   <thead> +     <tr><th>Name</th><th>Phone</th><tr> +   </thead> +   <tbody> +     <tr ng:repeat="friend in friends | filter:searchText"> +       <td>{{friend.name}}</td> +       <td>{{friend.phone}}</td> +     </tr> +    </tbody>   </table>  </doc:source>  <doc:scenario>   it('should filter the list', function() { -    var tr = using('table.example3').repeater('tr.ng-attr-widget'); +    var tr = using('table.example3 tbody').repeater('tr');      expect(tr.count()).toBe(5);      input('searchText').enter('a');      expect(tr.count()).toBe(2); diff --git a/docs/content/guide/dev_guide.forms.ngdoc b/docs/content/guide/dev_guide.forms.ngdoc index 35f42036..c33596e6 100644 --- a/docs/content/guide/dev_guide.forms.ngdoc +++ b/docs/content/guide/dev_guide.forms.ngdoc @@ -277,20 +277,20 @@ The following example demonstrates:  This example shows how to implement a custom HTML editor widget in Angular. -    <doc:example> +    <doc:example module="formModule">        <doc:source>          <script>            function EditorCntl($scope) {              $scope.htmlContent = '<b>Hello</b> <i>World</i>!';            } -          HTMLEditorWidget.$inject = ['$element', '$scope', 'htmlFilter']; -          function HTMLEditorWidget(element, scope, htmlFilter) { +          HTMLEditorWidget.$inject = ['$scope', '$element', '$sanitize']; +          function HTMLEditorWidget(scope, element, $sanitize) {              scope.$parseModel = function() {                // need to protect for script injection                try { -                this.$viewValue = htmlFilter( -                  this.$modelValue || '').get(); +                this.$viewValue = $sanitize( +                  this.$modelValue || '');                  if (this.$error.HTML) {                    // we were invalid, but now we are OK.                    this.$emit('$valid', 'HTML'); @@ -312,24 +312,25 @@ This example shows how to implement a custom HTML editor widget in Angular.              });            } -          angular.directive('ng:html-editor-model', function() { -            return ['$formFactory', '$element', function ($formFactory, element) { -              var exp = element.attr('ng:html-editor-model'), -                  form = $formFactory.forElement(element), -                  widget; -              element.attr('contentEditable', true); -              widget = form.$createWidget({ -                scope: this, -                model: exp, -                controller: HTMLEditorWidget, -                controllerArgs: {$element: element}}); -              // if the element is destroyed, then we need to -              // notify the form. -              element.bind('$destroy', function() { -                widget.$destroy(); -              }); -            }]; -          }); +          angular.module.formModule = function($compileProvider){ +            $compileProvider.directive('ngHtmlEditorModel', function ($formFactory) { +              return function(scope, element, attr) { +                var form = $formFactory.forElement(element), +                    widget; +                element.attr('contentEditable', true); +                widget = form.$createWidget({ +                  scope: scope, +                  model: attr.ngHtmlEditorModel, +                  controller: HTMLEditorWidget, +                  controllerArgs: {$element: element}}); +                // if the element is destroyed, then we need to +                // notify the form. +                element.bind('$destroy', function() { +                  widget.$destroy(); +                }); +              }; +            }); +          };          </script>          <form name='editorForm' ng:controller="EditorCntl">            <div ng:html-editor-model="htmlContent"></div> @@ -337,7 +338,7 @@ This example shows how to implement a custom HTML editor widget in Angular.            HTML: <br/>            <textarea ng:model="htmlContent" cols="80"></textarea>            <hr/> -          <pre>editorForm = {{editorForm}}</pre> +          <pre>editorForm = {{editorForm|json}}</pre>          </form>        </doc:source>        <doc:scenario> diff --git a/docs/src/templates/doc_widgets.js b/docs/src/templates/doc_widgets.js index db49088d..bee59234 100644 --- a/docs/src/templates/doc_widgets.js +++ b/docs/src/templates/doc_widgets.js @@ -1,4 +1,4 @@ -(function() { +angular.module('ngdocs.directives', [], function($compileProvider) {    var angularJsUrl;    var scripts = document.getElementsByTagName("script"); @@ -22,119 +22,122 @@      ' </body>\n' +      '</html>'; -  angular.widget('doc:example', ['$injector', '$browser', '$location', '$element', -                        function($injector,    $browser,   $location,   element){ -    this.descend(false); // do not compile the example code -    var module = element.attr('module') || ''; - -    //jQuery find() methods in this widget contain primitive selectors on purpose so that we can use -    //jqlite instead. jqlite's find() method currently supports onlt getElementsByTagName! -    var example = element.find('pre').eq(0),  //doc-source -        scriptSrc = '', -        htmlSrc = example.text().replace(/<script[^\>]*>([\s\S]+)<\/script>/im, function(_, script) { -            scriptSrc = script; -            return ''; -          }), -        showSource = example.attr('source') !== 'false', -        jsfiddle = example.attr('jsfiddle') || true, -        scenario = element.find('pre').eq(1); //doc-scenario - -    var tabs = angular.element('<ul class="doc-example">'); - -    // show source tab, if not disabled -    if (showSource) { -      tabs.append( -        '<li class="doc-example-heading"><h3>Source</h3></li>' + -        '<li class="doc-example-source" ng:non-bindable>' + -        jsFiddleButton(jsfiddle) + // may or may not have value -        '<pre class="brush: js; html-script: true; toolbar: false;"></pre></li>'); -    } -    // show live preview tab -    var livePreviewTab; -    tabs.append('<li class="doc-example-heading"><h3>Live Preview</h3></li>'); -    tabs.append(livePreviewTab = angular.element('<li class="doc-example-live">' + htmlSrc +'</li>')); -    // show scenario tab, if present -    if (scenario.text()) { -      tabs.append( -        '<li class="doc-example-heading"><h3>Scenario Test</h3></li>' + -        '<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>'); -    } - -    tabs.find('li').eq(1).find('pre').text( -      HTML_TEMPLATE. -        replace('_SCRIPT_SOURCE_', scriptSrc ? ' <script>\n' + indent(scriptSrc, '  ') + '\n </script>\n'  : ''). -        replace('_HTML_SOURCE_', indent(htmlSrc, '  ')). -        replace('_MODULE_', module ? '="' + module + '"' : '')); +  $compileProvider.directive('docExample', ['$injector', '$log', '$browser', '$location', +                                    function($injector,   $log,   $browser,   $location) { +    return { +      terminal: true, +      compile: function(element, attrs) { +        var module = element.attr('module') || ''; + +        //jQuery find() methods in this widget contain primitive selectors on purpose so that we can use +        //jqlite instead. jqlite's find() method currently supports onlt getElementsByTagName! +        var example = element.find('pre').eq(0),  //doc-source +            scriptSrc = '', +            htmlSrc = example.text().replace(/<script[^\>]*>([\s\S]+)<\/script>/im, function(_, script) { +                scriptSrc = script; +                return ''; +              }), +            showSource = example.attr('source') !== 'false', +            jsfiddle = example.attr('jsfiddle') || true, +            scenario = element.find('pre').eq(1); //doc-scenario + +        var tabs = angular.element('<ul class="doc-example">'); + +        // show source tab, if not disabled +        if (showSource) { +          tabs.append( +            '<li class="doc-example-heading"><h3>Source</h3></li>' + +            '<li class="doc-example-source" ng:non-bindable>' + +            jsFiddleButton(jsfiddle) + // may or may not have value +            '<pre class="brush: js; html-script: true; toolbar: false;"></pre></li>'); +        } +        // show live preview tab +        var livePreviewTab; +        tabs.append('<li class="doc-example-heading"><h3>Live Preview</h3></li>'); +        tabs.append(livePreviewTab = angular.element('<li class="doc-example-live">' + htmlSrc +'</li>')); +        // show scenario tab, if present +        if (scenario.text()) { +          tabs.append( +            '<li class="doc-example-heading"><h3>Scenario Test</h3></li>' + +            '<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>'); +        } -    element.html(''); -    element.append(tabs); +        tabs.find('li').eq(1).find('pre').text( +          HTML_TEMPLATE. +            replace('_SCRIPT_SOURCE_', scriptSrc ? ' <script>\n' + indent(scriptSrc, '  ') + '\n </script>\n'  : ''). +            replace('_HTML_SOURCE_', indent(htmlSrc, '  ')). +            replace('_MODULE_', module ? '="' + module + '"' : '')); -    try { -      if (window.execScript) { // IE -        window.execScript(scriptSrc || '"stupid IE!"'); // IE complains when evaling empty string -      } else { -        window.eval(scriptSrc); -      } -    } catch (e) { -      alert(e); -    } +        element.html(''); +        element.append(tabs); -    return function() { -      var scope = this; -      var modules = [ -        'ng', -        function($provide) { -          $provide.value('$browser', $browser); -          $provide.value('$location', $location); +        try { +          if (window.execScript) { // IE +            window.execScript(scriptSrc || '"stupid IE!"'); // IE complains when evaling empty string +          } else { +            window.eval(scriptSrc); +          } +        } catch (e) { +          alert(e);          } -      ]; -      module && modules.push(module); - -      angular.bootstrap(livePreviewTab, modules).invoke(function($rootScope) { -        element.bind('$destroy', scope.$root.$watch(function() { -          $rootScope.$digest(); -        })); -      }); -    }; -    function jsFiddleButton(jsfiddle) { -      var fixJsFiddleIssue132 = true; -      if (jsfiddle !== 'false') { -        if(jsfiddle === true) { -          //dynamically generate a fiddle -          var fiddleUrl = 'http://jsfiddle.net/api/post/library/pure/'; - -          function jsFiddleEscape(text, prefix) { -            return indent(text.replace(/<\/textarea>/gi,'</textarea>'), prefix); +        return function(docsAppScope) { +          var modules = [ +            function($provide) { +              $provide.value('$browser', $browser); +              $provide.value('$location', $location); +            } +          ]; +          module && modules.push(module); + +          angular.bootstrap(livePreviewTab, modules). +              invoke(['$rootScope', function(example$rootScope) { +                element.bind('$destroy', docsAppScope.$root.$watch(function() { +                  // this propagates the $watch from the docs app to the example app +                  example$rootScope.$digest(); +                })); +              }]); +        }; + +        function jsFiddleButton(jsfiddle) { +          var fixJsFiddleIssue132 = true; +          if (jsfiddle !== 'false') { +            if(jsfiddle === true) { +              //dynamically generate a fiddle +              var fiddleUrl = 'http://jsfiddle.net/api/post/library/pure/'; + +              function jsFiddleEscape(text, prefix) { +                return indent(text.replace(/<\/textarea>/gi,'</textarea>'), prefix); +              } + +              return '<form class="jsfiddle" method="post" action="' + fiddleUrl + '" target="_blank">' + +                        (fixJsFiddleIssue132 ? '' : '<textarea name="resources">' + angularJsUrl + '</textarea>') + +                        '<textarea name="css">\n' + +                          (fixJsFiddleIssue132 ? '</style>\n<script src="' + angularJsUrl + '"></script>\n<style>\n' : '') + +                          '.ng-invalid { border: 1px solid red; } \n' + +                          'body { font-family: Arial,Helvetica,sans-serif; }\n' + +                          'body, td, th { font-size: 14px; margin: 0; }\n' + +                          'table { border-collapse: separate; border-spacing: 2px; display: table; margin-bottom: 0; margin-top: 0; -moz-box-sizing: border-box; text-indent: 0; }\n' + +                          'a:link, a:visited, a:hover { color: #5D6DB6; text-decoration: none; }\n' + +                          '.error { color: red; }\n' + +                        '</textarea>' + +                        '<input type="text" name="title" value="AngularJS Live Example">' + +                        '<textarea name="html">' + +                          '<div ng:app' + (module ? '="' + module + '"' : '') + '>\n' + jsFiddleEscape(htmlSrc, ' ') + '\n</div>' + +                        '</textarea>' + +                        '<textarea name="js">' + jsFiddleEscape(scriptSrc) + '</textarea>' + +                        '<button>edit at jsFiddle</button>' + +                      '</form>'; +            } else { +              //use existing fiddle +              fiddleUrl = "http://jsfiddle.net" + jsfiddle; +              return '<form class="jsfiddle" method="get" action="' + fiddleUrl + '" target="_blank">' + +                       '<button>edit at jsFiddle</button>' + +                     '</form>'; +            }            } - -          return '<form class="jsfiddle" method="post" action="' + fiddleUrl + '" target="_blank">' + -                    (fixJsFiddleIssue132 ? '' : '<textarea name="resources">' + angularJsUrl + '</textarea>') + -                    '<textarea name="css">\n' + -                      (fixJsFiddleIssue132 ? '</style>\n<script src="' + angularJsUrl + '"></script>\n<style>\n' : '') + -                      '.ng-invalid { border: 1px solid red; } \n' + -                      'body { font-family: Arial,Helvetica,sans-serif; }\n' + -                      'body, td, th { font-size: 14px; margin: 0; }\n' + -                      'table { border-collapse: separate; border-spacing: 2px; display: table; margin-bottom: 0; margin-top: 0; -moz-box-sizing: border-box; text-indent: 0; }\n' + -                      'a:link, a:visited, a:hover { color: #5D6DB6; text-decoration: none; }\n' + -                      '.error { color: red; }\n' + -                    '</textarea>' + -                    '<input type="text" name="title" value="AngularJS Live Example">' + -                    '<textarea name="html">' + -                      '<div ng:app' + (module ? '="' + module + '"' : '') + '>\n' + jsFiddleEscape(htmlSrc, ' ') + '\n</div>' + -                    '</textarea>' + -                    '<textarea name="js">' + jsFiddleEscape(scriptSrc) + '</textarea>' + -                    '<button>edit at jsFiddle</button>' + -                  '</form>'; -        } else { -          //use existing fiddle -          fiddleUrl = "http://jsfiddle.net" + jsfiddle; -          return '<form class="jsfiddle" method="get" action="' + fiddleUrl + '" target="_blank">' + -                   '<button>edit at jsFiddle</button>' + -                 '</form>'; -        } +        };        } -      return '';      }    }]); @@ -163,93 +166,95 @@      return lines.join('\n');    } -  var HTML_TPL = -      '<p><a ng:init="showInstructions = {show}" ng:show="!showInstructions" ng:click="showInstructions = true" href>Workspace Reset Instructions  ➤</a></p>' + -      '<div ng:controller="TutorialInstructionsCtrl" ng:show="showInstructions">' + -        '<div class="tabs-nav">' + -          '<ul>' + -          '</ul>' + -        '</div>' + -        '<div class="tabs-content"><div class="tabs-content-inner">' + - -        '</div></div>' + +  $compileProvider.directive('docTutorialInstructions', function() { +    var HTML_NAV = '<li ng:class="currentCls(\'{id}\')"><a ng:click="select(\'{id}\')" href>{title}</a></li>'; +    var HTML_CONTENT = '<div ng:show="selected==\'{id}\'">{content}</div>'; + +    var HTML_TPL = +        '<p><a ng:init="showInstructions = {show}" ng:show="!showInstructions" ng:click="showInstructions = true" href>Workspace Reset Instructions  ➤</a></p>' + +        '<div ng:controller="TutorialInstructionsCtrl" ng:show="showInstructions">' + +          '<div class="tabs-nav">' + +            '<ul>' + +            '</ul>' + +          '</div>' + +          '<div class="tabs-content"><div class="tabs-content-inner">' + + +          '</div></div>' + +        '</div>'; + +    var DEFAULT_NAV = +      '<li ng:class="currentCls(\'git-mac\')"><a ng:click="select(\'git-mac\')" href>Git on Mac/Linux</a></li>' + +      '<li ng:class="currentCls(\'git-win\')"><a ng:click="select(\'git-win\')" href>Git on Windows</a></li>' + +      '<li ng:class="currentCls(\'ss-mac\')"><a ng:click="select(\'ss-mac\')" href>Snapshots on Mac/Linux</a></li>' + +      '<li ng:class="currentCls(\'ss-win\')"><a ng:click="select(\'ss-win\')" href>Snapshots on Windows</a></li>'; + +    var DEFAULT_CONTENT = +      '<div ng:show="selected==\'git-mac\'">' + +        '<ol>' + +        '<li><p>Reset the workspace to step {step}.</p>' + +        '<pre><code> git checkout -f step-{step}</code></pre></li>' + +        '<li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{step}/app">angular\'s server</a>.</p></li>' + +        '</ol>' + +      '</div>' + + +      '<div ng:show="selected==\'git-win\'">' + +        '<ol>' + +        '<li><p>Reset the workspace to step {step}.</p>' + +        '<pre><code> git checkout -f step-{step}</code></pre></li>' + +        '<li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{step}/app">angular\'s server</a>.</p></li>' + +        '</ol>' + +      '</div>' + + +      '<div ng:show="selected==\'ss-mac\'">' + +        '<ol>' + +        '<li><p>Reset the workspace to step {step}.</p>' + +        '<pre><code> ./goto_step.sh {step}</code></pre></li>' + +        '<li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{step}/app">angular\'s server</a>.</p></li>' + +        '</ol>' + +      '</div>' + + +      '<div ng:show="selected==\'ss-win\'">' + +        '<ol>' + +        '<li><p>Reset the workspace to step {step}.</p>' + +        '<pre><code> ./goto_step.bat {step}</code></pre></li>' + +        '<li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{step}/app">angular\'s server</a>.</p></li>' + +        '</ol>' +        '</div>'; -  var HTML_NAV = '<li ng:class="currentCls(\'{id}\')"><a ng:click="select(\'{id}\')" href>{title}</a></li>'; -  var HTML_CONTENT = '<div ng:show="selected==\'{id}\'">{content}</div>'; - -  var DEFAULT_NAV = -    '<li ng:class="currentCls(\'git-mac\')"><a ng:click="select(\'git-mac\')" href>Git on Mac/Linux</a></li>' + -    '<li ng:class="currentCls(\'git-win\')"><a ng:click="select(\'git-win\')" href>Git on Windows</a></li>' + -    '<li ng:class="currentCls(\'ss-mac\')"><a ng:click="select(\'ss-mac\')" href>Snapshots on Mac/Linux</a></li>' + -    '<li ng:class="currentCls(\'ss-win\')"><a ng:click="select(\'ss-win\')" href>Snapshots on Windows</a></li>'; - -  var DEFAULT_CONTENT = -    '<div ng:show="selected==\'git-mac\'">' + -      '<ol>' + -      '<li><p>Reset the workspace to step {step}.</p>' + -      '<pre><code> git checkout -f step-{step}</code></pre></li>' + -      '<li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{step}/app">angular\'s server</a>.</p></li>' + -      '</ol>' + -    '</div>' + - -    '<div ng:show="selected==\'git-win\'">' + -      '<ol>' + -      '<li><p>Reset the workspace to step {step}.</p>' + -      '<pre><code> git checkout -f step-{step}</code></pre></li>' + -      '<li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{step}/app">angular\'s server</a>.</p></li>' + -      '</ol>' + -    '</div>' + - -    '<div ng:show="selected==\'ss-mac\'">' + -      '<ol>' + -      '<li><p>Reset the workspace to step {step}.</p>' + -      '<pre><code> ./goto_step.sh {step}</code></pre></li>' + -      '<li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{step}/app">angular\'s server</a>.</p></li>' + -      '</ol>' + -    '</div>' + - -    '<div ng:show="selected==\'ss-win\'">' + -      '<ol>' + -      '<li><p>Reset the workspace to step {step}.</p>' + -      '<pre><code> ./goto_step.bat {step}</code></pre></li>' + -      '<li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{step}/app">angular\'s server</a>.</p></li>' + -      '</ol>' + -    '</div>'; - -  angular.widget('doc:tutorial-instructions', function(element) { -    this.descend(true); - -    var tabs = angular.element(HTML_TPL.replace('{show}', element.attr('show') || 'false')), -        nav = tabs.find('ul'), -        // use simple selectors because jqLite find() supports getElementsByTagName only -        content = tabs.find('div').find('div'), -        children = element.children(); - -    if (children.length) { -      // load custom content -      angular.forEach(element.children(), function(elm) { -        elm = angular.element(elm); -        var id = elm.attr('id'); - -        nav.append(HTML_NAV.replace('{title}', elm.attr('title')).replace(/\{id\}/g, id)); -        content.append(HTML_CONTENT.replace('{id}', id).replace('{content}', elm.html())); -      }); -    } else { -      // default -      nav.append(DEFAULT_NAV); -      content.append(DEFAULT_CONTENT.replace(/\{step\}/g, element.attr('step'))); -    } +    return { +      compile: function(element, attrs) { +        var tabs = angular.element(HTML_TPL.replace('{show}', attrs.show || 'false')), +            nav = tabs.find('ul'), +            // use simple selectors because jqLite find() supports getElementsByTagName only +            content = tabs.find('div').find('div'), +            children = element.children(); + +        if (children.length) { +          // load custom content +          angular.forEach(element.children(), function(elm) { +            elm = angular.element(elm); +            var id = elm.attr('id'); + +            nav.append(HTML_NAV.replace('{title}', elm.attr('title')).replace(/\{id\}/g, id)); +            content.append(HTML_CONTENT.replace('{id}', id).replace('{content}', elm.html())); +          }); +        } else { +          // default +          nav.append(DEFAULT_NAV); +          content.append(DEFAULT_CONTENT.replace(/\{step\}/g, element.attr('step'))); +        } -    element.html(''); -    element.append(tabs); +        element.html(''); +        element.append(tabs); +      } +    }    }); -  angular.directive('doc:tutorial-nav', function(step) { -    return function(element) { +  $compileProvider.directive('docTutorialNav', function() { +    return function(scope, element, attrs) {        var prevStep, codeDiff, nextStep, -          content; +          content, step = attrs.docTutorialNav;        step = parseInt(step, 10); @@ -282,4 +287,4 @@        return (step < 10) ? ('0' + step) : step;      }    }); -})(); +}); diff --git a/docs/src/templates/docs.js b/docs/src/templates/docs.js index 7fb9cb22..b9fc0407 100644 --- a/docs/src/templates/docs.js +++ b/docs/src/templates/docs.js @@ -125,11 +125,6 @@ function DocsController(scope, $location, $window, $cookies, $filter) {    }  } -// prevent compilation of code -angular.widget('code', function(element) { -  element.attr('ng:non-bindable', 'true'); -}); -  SyntaxHighlighter['defaults'].toolbar = false;  SyntaxHighlighter['defaults'].gutter = true; @@ -151,7 +146,7 @@ function TutorialInstructionsCtrl($cookieStore) {    };  } -angular.module('ngdocs', [], function($locationProvider, $filterProvider) { +angular.module('ngdocs', ['ngdocs.directives'], function($locationProvider, $filterProvider, $compileProvider) {    $locationProvider.html5Mode(true).hashPrefix('!');    $filterProvider.register('title', function(){ @@ -161,4 +156,8 @@ angular.module('ngdocs', [], function($locationProvider, $filterProvider) {        });      };    }); + +  $compileProvider.directive('code', function() { +    return { terminal: true }; +  });  }); diff --git a/src/Angular.js b/src/Angular.js index 4a0589c3..f7c3e318 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -90,15 +90,7 @@ var $$scope           = '$scope',      /** @name angular */      angular           = window.angular || (window.angular = {}), -    angularModule     = null, -    /** @name angular.markup */ -    angularTextMarkup = extensionMap(angular, 'markup'), -    /** @name angular.attrMarkup */ -    angularAttrMarkup = extensionMap(angular, 'attrMarkup'), -    /** @name angular.directive */ -    angularDirective  = extensionMap(angular, 'directive', lowercase), -    /** @name angular.widget */ -    angularWidget     = extensionMap(angular, 'widget', shivForIE), +    angularModule,      /** @name angular.module.ng */      angularInputType  = extensionMap(angular, 'inputType', lowercase),      nodeName_, @@ -988,8 +980,7 @@ function assertArg(arg, name, reason) {  }  function assertArgFn(arg, name) { -  assertArg(arg, name);    assertArg(isFunction(arg), name, 'not a function, got ' + -      (typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); +      (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));    return arg;  } diff --git a/src/AngularPublic.js b/src/AngularPublic.js index bfc50ef8..516bbad4 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -60,16 +60,43 @@ function publishExternalAPI(angular){    angularModule('ng', ['ngLocale'], ['$provide',      function ngModule($provide) { -    // TODO(misko): temporary services to get the compiler working; -      $provide.value('$textMarkup', angularTextMarkup); -      $provide.value('$attrMarkup', angularAttrMarkup); -      $provide.value('$directive', angularDirective); -      $provide.value('$widget', angularWidget); -        $provide.service('$anchorScroll', $AnchorScrollProvider);        $provide.service('$browser', $BrowserProvider);        $provide.service('$cacheFactory', $CacheFactoryProvider); -      $provide.service('$compile', $CompileProvider); +      $provide.service('$compile', $CompileProvider). +        directive({ +            a: htmlAnchorDirective, +            input: inputDirective, +            textarea: inputDirective, +            form: ngFormDirective, +            select: selectDirective, +            option: optionDirective, +            ngBind: ngBindDirective, +            ngBindHtml: ngBindHtmlDirective, +            ngBindHtmlUnsafe: ngBindHtmlUnsafeDirective, +            ngBindTemplate: ngBindTemplateDirective, +            ngBindAttr: ngBindAttrDirective, +            ngClass: ngClassDirective, +            ngClassEven: ngClassEvenDirective, +            ngClassOdd: ngClassOddDirective, +            ngCloak: ngCloakDirective, +            ngController: ngControllerDirective, +            ngForm: ngFormDirective, +            ngHide: ngHideDirective, +            ngInclude: ngIncludeDirective, +            ngInit: ngInitDirective, +            ngNonBindable: ngNonBindableDirective, +            ngPluralize: ngPluralizeDirective, +            ngRepeat: ngRepeatDirective, +            ngShow: ngShowDirective, +            ngSubmit: ngSubmitDirective, +            ngStyle: ngStyleDirective, +            ngSwitch: ngSwitchDirective, +            ngOptions: ngOptionsDirective, +            ngView: ngViewDirective +          }). +        directive(ngEventDirectives). +        directive(ngAttributeAliasDirectives);        $provide.service('$controller', $ControllerProvider);        $provide.service('$cookies', $CookiesProvider);        $provide.service('$cookieStore', $CookieStoreProvider); @@ -89,9 +116,9 @@ function publishExternalAPI(angular){        $provide.service('$routeParams', $RouteParamsProvider);        $provide.service('$rootScope', $RootScopeProvider);        $provide.service('$q', $QProvider); +      $provide.service('$sanitize', $SanitizeProvider);        $provide.service('$sniffer', $SnifferProvider);        $provide.service('$templateCache', $TemplateCacheProvider);        $provide.service('$window', $WindowProvider);      }]); -} - +}; diff --git a/src/Injector.js b/src/Injector.js index 38cf652d..94154bec 100644 --- a/src/Injector.js +++ b/src/Injector.js @@ -219,6 +219,7 @@ function inferInjectionArgs(fn) {   *   - `Constructor`: a new instance of the provider will be created using   *               {@link angular.module.AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.   * + * @returns {Object} registered provider instance   */  /** @@ -232,6 +233,7 @@ function inferInjectionArgs(fn) {   * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provide'` key.   * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for   * `$provide.service(name, {$get:$getFn})`. + * @returns {Object} registered provider instance   */ @@ -246,6 +248,7 @@ function inferInjectionArgs(fn) {   * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provide'` key.   * @param {function()} value The $getFn for the instance creation. Internally this is a short hand for   * `$provide.service(name, {$get:function(){ return value; }})`. + * @returns {Object} registered provider instance   */ @@ -285,7 +288,7 @@ function createInjector(modulesToLoad) {        if (isObject(key)) {          forEach(key, reverseParams(delegate));        } else { -        delegate(key, value); +        return delegate(key, value);        }      }    } @@ -297,12 +300,12 @@ function createInjector(modulesToLoad) {      if (!provider.$get) {        throw Error('Provider ' + name + ' must define $get factory method.');      } -    providerCache[name + providerSuffix] = provider; +    return providerCache[name + providerSuffix] = provider;    } -  function factory(name, factoryFn) { service(name, { $get:factoryFn }); } +  function factory(name, factoryFn) { return service(name, { $get:factoryFn }); } -  function value(name, value) { factory(name, valueFn(value)); } +  function value(name, value) { return factory(name, valueFn(value)); }    function decorator(serviceName, decorFn) {      var origProvider = providerInjector.get(serviceName + providerSuffix), diff --git a/src/directives.js b/src/directives.js index eb35addb..dc0a986f 100644 --- a/src/directives.js +++ b/src/directives.js @@ -58,10 +58,14 @@       </doc:scenario>     </doc:example>   */ -angularDirective("ng:init", function(expression){ -  return function(element){ -    this.$eval(expression); -  }; +var ngInitDirective = valueFn({ +  compile: function() { +    return { +      pre: function(scope, element, attrs) { +        scope.$eval(attrs.ngInit); +      } +    } +  }  });  /** @@ -158,16 +162,24 @@ angularDirective("ng:init", function(expression){       </doc:scenario>     </doc:example>   */ -angularDirective("ng:controller", function(expression) { -  this.scope(true); -  return ['$controller', '$window', function($controller, $window) { -    var scope = this, -        Controller = getter(scope, expression, true) || getter($window, expression, true); - -    assertArgFn(Controller, expression); -    $controller(Controller, scope); -  }]; -}); +var ngControllerDirective = ['$controller', '$window', function($controller, $window) { +  return { +    scope: true, +    compile: function() { +      return { +        pre: function(scope, element, attr) { +          var expression = attr.ngController, +              Controller = getter(scope, expression, true) || getter($window, expression, true); + +          assertArgFn(Controller, expression); +          $controller(Controller, scope); +        } +      }; +    } +  } +}]; + +  /**   * @ngdoc directive @@ -208,55 +220,30 @@ angularDirective("ng:controller", function(expression) {       </doc:scenario>     </doc:example>   */ -angularDirective("ng:bind", function(expression, element){ -  element.addClass('ng-binding'); -  return ['$exceptionHandler', '$parse', '$element', function($exceptionHandler, $parse, element) { -    var exprFn = $parse(expression), -        lastValue = Number.NaN, -        scope = this; +var ngBindDirective = valueFn(function(scope, element, attr) { +  element.addClass('ng-binding').data('$binding', attr.ngBind); +  scope.$watch(attr.ngBind, function(value) { +    element.text(value == undefined ? '' : value); +  }); +}); -    scope.$watch(function() { -      // TODO(misko): remove error handling https://github.com/angular/angular.js/issues/347 -      var value, html, isHtml, isDomElement, -          hadOwnElement = scope.hasOwnProperty('$element'), -          oldElement = scope.$element; -      // TODO(misko): get rid of $element https://github.com/angular/angular.js/issues/348 -      scope.$element = element; -      try { -        value = exprFn(scope); -        // If we are HTML than save the raw HTML data so that we don't recompute sanitization since -        // it is expensive. -        // TODO(misko): turn this into a more generic way to compute this -        if ((isHtml = (value instanceof HTML))) -          value = (html = value).html; -        if (lastValue === value) return; -        isDomElement = isElement(value); -        if (!isHtml && !isDomElement && isObject(value)) { -          value = toJson(value, true); -        } -        if (value != lastValue) { -          lastValue = value; -          if (isHtml) { -            element.html(html.get()); -          } else if (isDomElement) { -            element.html(''); -            element.append(value); -          } else { -            element.text(value == undefined ? '' : value); -          } -        } -      } catch (e) { -        $exceptionHandler(e); -      } finally { -        if (hadOwnElement) { -          scope.$element = oldElement; -        } else { -          delete scope.$element; -        } +var ngBindHtmlUnsafeDirective = valueFn(function(scope, element, attr) { +  element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); +  scope.$watch(attr.ngBindHtmlUnsafe, function(value) { +    element.html(value == undefined ? '' : value); +  }); +}); + +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);        }      }); -  }]; -}); +  } +}];  /** @@ -292,32 +279,29 @@ angularDirective("ng:bind", function(expression, element){       </doc:source>       <doc:scenario>         it('should check ng:bind', function() { -         expect(using('.doc-example-live').binding('{{salutation}} {{name}}')). -           toBe('Hello World!'); +         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}} {{name}}')). -           toBe('Greetings user!'); +         expect(using('.doc-example-live').binding('salutation')). +           toBe('Greetings'); +         expect(using('.doc-example-live').binding('name')). +           toBe('user');         });       </doc:scenario>     </doc:example>   */ -angularDirective("ng:bind-template", function(expression, element){ -  element.addClass('ng-binding'); -  var templateFn = compileBindTemplate(expression); -  return function(element) { -    var lastValue, -        scope = this; - -    scope.$watch(function() { -      var value = templateFn(scope, element, true); -      if (value != lastValue) { -        element.text(value); -        lastValue = value; -      } +var ngBindTemplateDirective = ['$interpolate', function($interpolate) { +  return function(scope, element, attr) { +    var interpolateFn = $interpolate(attr.ngBindTemplate); +    element.addClass('ng-binding').data('$binding', interpolateFn); +    scope.$watch(interpolateFn, function(value) { +      element.text(value);      }); -  }; -}); +  } +}];  /**   * @ngdoc directive @@ -392,23 +376,25 @@ angularDirective("ng:bind-template", function(expression, element){       </doc:scenario>     </doc:example>   */ -angularDirective("ng:bind-attr", function(expression){ -  return function(element){ -    var lastValue = {}, -        scope = this; +var ngBindAttrDirective = ['$interpolate', function($interpolate) { +  return function(scope, element, attr) { +    var lastValue = {}; +    var interpolateFns = {};      scope.$watch(function() { -      var values = scope.$eval(expression); +      var values = scope.$eval(attr.ngBindAttr);        for(var key in values) { -        var value = compileBindTemplate(values[key])(scope, element); +        var exp = values[key], +            fn = (interpolateFns[exp] || +              (interpolateFns[values[key]] = $interpolate(exp))), +            value = fn(scope);          if (lastValue[key] !== value) { -          lastValue[key] = value; -          element.attr(key, BOOLEAN_ATTR[lowercase(key)] ? toBoolean(value) : value); +          attr.$set(key, lastValue[key] = value);          }        }      }); -  }; -}); +  } +}];  /** @@ -448,19 +434,33 @@ angularDirective("ng:bind-attr", function(expression){   *   * TODO: maybe we should consider allowing users to control event propagation in the future.   */ -angularDirective("ng:click", function(expression, element){ -  return function(element){ -    var self = this; -    element.bind('click', function(event){ -      self.$apply(expression); +var ngEventDirectives = {}; +forEach('click dblclick mousedown mouseup mouseover mousemove'.split(' '), function(name) { +  var directiveName = camelCase('ng-' + name); +  ngEventDirectives[directiveName] = valueFn(function(scope, element, attr) { +    element.bind(lowercase(name), function(event) { +      scope.$apply(attr[directiveName]);        event.stopPropagation();      }); -  }; +  });  });  /**   * @ngdoc directive + * @name angular.directive.ng:dblclick + * + * @description + * The ng:dblclick allows you to specify custom behavior when + * element is double-clicked. + * + * @element ANY + * @param {expression} expression {@link guide/dev_guide.expressions Expression} to evaluate upon + * double-click. + */ + +/** + * @ngdoc directive   * @name angular.directive.ng:submit   *   * @description @@ -496,48 +496,42 @@ angularDirective("ng:click", function(expression, element){       </doc:source>       <doc:scenario>         it('should check ng:submit', function() { -         expect(binding('list')).toBe('list=[]'); +         expect(binding('list')).toBe('[]');           element('.doc-example-live #submit').click(); -         expect(binding('list')).toBe('list=["hello"]'); +         expect(binding('list')).toBe('["hello"]');           expect(input('text').val()).toBe('');         });         it('should ignore empty strings', function() { -         expect(binding('list')).toBe('list=[]'); +         expect(binding('list')).toBe('[]');           element('.doc-example-live #submit').click();           element('.doc-example-live #submit').click(); -         expect(binding('list')).toBe('list=["hello"]'); +         expect(binding('list')).toBe('["hello"]');         });       </doc:scenario>     </doc:example>   */ -angularDirective("ng:submit", function(expression, element) { -  return function(element) { -    var self = this; -    element.bind('submit', function() { -      self.$apply(expression); -    }); -  }; +var ngSubmitDirective = valueFn(function(scope, element, attrs) { +  element.bind('submit', function() { +    scope.$apply(attrs.ngSubmit); +  });  }); -function ngClass(selector) { -  return function(expression, element) { -    return function(element) { -      var scope = this; -      scope.$watch(expression, function(newVal, oldVal) { -        if (selector(scope.$index)) { -          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)) +function classDirective(name, selector) { +  name = 'ngClass' + name; +  return valueFn(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); -        } -      }); -    }; -  }; +         if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal);      } +    }); +  });  }  /** @@ -584,7 +578,7 @@ function ngClass(selector) {       </doc:scenario>     </doc:example>   */ -angularDirective("ng:class", ngClass(function() {return true;})); +var ngClassDirective = classDirective('', true);  /**   * @ngdoc directive @@ -624,7 +618,7 @@ angularDirective("ng:class", ngClass(function() {return true;}));       </doc:scenario>     </doc:example>   */ -angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;})); +var ngClassOddDirective = classDirective('Odd', 0);  /**   * @ngdoc directive @@ -663,7 +657,7 @@ angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));       </doc:scenario>     </doc:example>   */ -angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;})); +var ngClassEvenDirective = classDirective('Even', 1);  /**   * @ngdoc directive @@ -697,13 +691,11 @@ angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));       </doc:scenario>     </doc:example>   */ -angularDirective("ng:show", function(expression, element){ -  return function(element) { -    var scope = this; -    scope.$watch(expression, function(value) { -      element.css('display', toBoolean(value) ? '' : 'none'); -    }); -  }; +//TODO(misko): refactor to remove element from the DOM +var ngShowDirective = valueFn(function(scope, element, attr){ +  scope.$watch(attr.ngShow, function(value){ +    element.css('display', toBoolean(value) ? '' : 'none'); +  });  });  /** @@ -738,13 +730,11 @@ angularDirective("ng:show", function(expression, element){       </doc:scenario>     </doc:example>   */ -angularDirective("ng:hide", function(expression, element){ -  return function(element) { -    var scope = this; -    scope.$watch(expression, function(value) { -      element.css('display', toBoolean(value) ? 'none' : ''); -    }); -  }; +//TODO(misko): refactor to remove element from the DOM +var ngHideDirective = valueFn(function(scope, element, attr){ +  scope.$watch(attr.ngHide, function(value){ +    element.css('display', toBoolean(value) ? 'none' : ''); +  });  });  /** @@ -779,16 +769,13 @@ angularDirective("ng:hide", function(expression, element){       </doc:scenario>     </doc:example>   */ -angularDirective("ng:style", function(expression, element) { -  return function(element) { -    var scope = this; -    scope.$watch(expression, function(newStyles, oldStyles) { -      if (oldStyles && (newStyles !== oldStyles)) { -        forEach(oldStyles, function(val, style) { element.css(style, '');}); -      } -      if (newStyles) element.css(newStyles); -    }); -  }; +var ngStyleDirective = valueFn(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); +  });  }); @@ -845,7 +832,22 @@ angularDirective("ng:style", function(expression, element) {     </doc:example>   *   */ -angularDirective("ng:cloak", function(expression, element) { -  element.removeAttr('ng:cloak'); -  element.removeClass('ng-cloak'); +var ngCloakDirective = valueFn({ +  compile: function(element, attr) { +    attr.$set(attr.$attr.ngCloak, undefined); +    element.removeClass('ng-cloak'); +  }  }); + +function ngAttributeAliasDirective(propName, attrName) { +  ngAttributeAliasDirectives[camelCase('ng-' + attrName)] = ['$interpolate', function($interpolate) { +    return function(scope, element, attr) { +      scope.$watch($interpolate(attr[camelCase('ng-' + attrName)]), function(value) { +        attr.$set(attrName, value); +      }); +    } +  }]; +} +var ngAttributeAliasDirectives = {}; +forEach(BOOLEAN_ATTR, ngAttributeAliasDirective); +ngAttributeAliasDirective(null, 'src'); diff --git a/src/jqLite.js b/src/jqLite.js index e48d250b..2505a307 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -405,15 +405,14 @@ forEach({    text: extend((msie < 9)        ? function(element, value) { -        // NodeType == 3 is text node -        if (element.nodeType == 3) { -          if (isUndefined(value)) -            return element.nodeValue; -          element.nodeValue = value; -        } else { +        if (element.nodeType == 1 /** Element */) {            if (isUndefined(value))              return element.innerText;            element.innerText = value; +        } else { +          if (isUndefined(value)) +            return element.nodeValue; +          element.nodeValue = value;          }        }        : function(element, value) { diff --git a/src/markups.js b/src/markups.js index f6f2143a..9e3e16f0 100644 --- a/src/markups.js +++ b/src/markups.js @@ -1,109 +1,5 @@  'use strict'; -/** - * @ngdoc overview - * @name angular.markup - * @description - * - * Angular markup transforms the content of DOM elements or portions of the content into other - * text or DOM elements for further compilation. - * - * Markup extensions do not themselves produce linking functions. Think of markup as a way to - * produce shorthand for a {@link angular.widget widget} or a {@link angular.directive directive}. - * - * The most prominent example of a markup in Angular is the built-in, double curly markup - * `{{expression}}`, which is shorthand for `<span ng:bind="expression"></span>`. - * - * Create custom markup like this: - * - * <pre> - *   angular.markup('newMarkup', function(text, textNode, parentElement){ - *     //tranformation code - *   }); - * </pre> - * - * For more information, see {@link guide/dev_guide.compiler.markup Understanding Angular Markup} - * in the Angular Developer Guide. - */ - -/** - * @ngdoc overview - * @name angular.attrMarkup - * @description - * - * Attribute markup allows you to modify the state of an attribute's text. - * - * Attribute markup extends the Angular complier in a way similar to {@link angular.markup}, - * which allows you to modify the content of a node. - * - * The most prominent example of an attribute markup in Angular is the built-in double curly markup - * which is a shorthand for {@link angular.directive.ng:bind-attr ng:bind-attr}. - * - * ## Example - * - * <pre> - *   angular.attrMarkup('newAttrMarkup', function(attrValue, attrName, element){ - *     //tranformation code - *   }); - * </pre> - * - * For more information about Angular attribute markup, see {@link guide/dev_guide.compiler.markup - * Understanding Angular Markup} in the Angular Developer Guide. - */ - - -angularTextMarkup('{{}}', function(text, textNode, parentElement) { -  var bindings = parseBindings(text), -      self = this; -  if (hasBindings(bindings)) { -    if (isLeafNode(parentElement[0])) { -      parentElement.attr('ng:bind-template', text); -    } else { -      var cursor = textNode, newElement; -      forEach(parseBindings(text), function(text){ -        var exp = binding(text); -        if (exp) { -          newElement = jqLite('<span>'); -          newElement.attr('ng:bind', exp); -        } else { -          newElement = jqLite(document.createTextNode(text)); -        } -        if (msie && text.charAt(0) == ' ') { -          newElement = jqLite('<span> </span>'); -          var nbsp = newElement.html(); -          newElement.text(text.substr(1)); -          newElement.html(nbsp + newElement.html()); -        } -        cursor.after(newElement); -        cursor = newElement; -      }); -      textNode.remove(); -    } -  } -}); - -/** - * This tries to normalize the behavior of value attribute across browsers. If value attribute is - * not specified, then specify it to be that of the text. - */ -angularTextMarkup('option', function(text, textNode, parentElement){ -  if (lowercase(nodeName_(parentElement)) == 'option') { -    if (msie <= 7) { -      // In IE7 The issue is that there is no way to see if the value was specified hence -      // we have to resort to parsing HTML; -      htmlParser(parentElement[0].outerHTML, { -        start: function(tag, attrs) { -          if (isUndefined(attrs.value)) { -            parentElement.attr('value', text); -          } -        } -      }); -    } else if (parentElement[0].getAttribute('value') == null) { -      // jQuery does normalization on 'value' so we have to bypass it. -      parentElement.attr('value', text); -    } -  } -});  /**   * @ngdoc directive @@ -171,7 +67,7 @@ angularTextMarkup('option', function(text, textNode, parentElement){          it('should execute ng:click but not reload when no href but name specified', function() {            element('#link-5').click();            expect(input('value').val()).toEqual('5'); -          expect(element('#link-5').attr('href')).toBe(undefined); +          expect(element('#link-5').attr('href')).toBe("");          });          it('should only change url when only ng:href', function() { @@ -371,25 +267,3 @@ angularTextMarkup('option', function(text, textNode, parentElement){  * @param {template} template any string which can contain '{{}}' markup.  */ - -var NG_BIND_ATTR = 'ng:bind-attr'; -var SIDE_EFFECT_ATTRS = {}; - -forEach('src,href,multiple,selected,checked,disabled,readonly,required'.split(','), function(name) { -  SIDE_EFFECT_ATTRS['ng:' + name] = name; -}); - -angularAttrMarkup('{{}}', function(value, name, element){ -  // don't process existing attribute markup -  if (angularDirective(name) || angularDirective("@" + name)) return; -  if (msie && name == 'src') -    value = decodeURI(value); -  var bindings = parseBindings(value), -      bindAttr; -  if (hasBindings(bindings) || SIDE_EFFECT_ATTRS[name]) { -    element.removeAttr(name); -    bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}"); -    bindAttr[SIDE_EFFECT_ATTRS[name] || name] = value; -    element.attr(NG_BIND_ATTR, toJson(bindAttr)); -  } -}); diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js index a7979342..7e33181c 100644 --- a/src/scenario/Scenario.js +++ b/src/scenario/Scenario.js @@ -345,25 +345,71 @@ function browserTrigger(element, type, keys) {   * Finds all bindings with the substring match of name and returns an   * array of their values.   * - * @param {string} name The name to match + * @param {string} bindExp The name to match   * @return {Array.<string>} String of binding values   */ -_jQuery.fn.bindings = function(name) { -  function contains(text, value) { -    return value instanceof RegExp -      ? value.test(text) -      : text && text.indexOf(value) >= 0; +_jQuery.fn.bindings = function(windowJquery, bindExp) { +  var result = [], match, +      bindSelector = '.ng-binding:visible'; +  if (angular.isString(bindExp)) { +    bindExp = bindExp.replace(/\s/g, ''); +    match = function (actualExp) { +      if (actualExp) { +        actualExp = actualExp.replace(/\s/g, ''); +        if (actualExp == bindExp) return true; +        if (actualExp.indexOf(bindExp) == 0) { +          return actualExp.charAt(bindExp.length) == '|'; +        } +      } +    } +  } else if (bindExp) { +    match = function(actualExp) { +      return actualExp && bindExp.exec(actualExp); +    } +  } else { +    match = function(actualExp) { +      return !!actualExp; +    }; +  } +  var selection = this.find(bindSelector); +  if (this.is(bindSelector)) { +    selection = selection.add(this);    } -  var result = []; -  this.find('.ng-binding:visible').each(function() { -    var element = new _jQuery(this); -    if (!angular.isDefined(name) || -      contains(element.attr('ng:bind'), name) || -      contains(element.attr('ng:bind-template'), name)) { -      if (element.is('input, textarea')) { -        result.push(element.val()); + +  function push(value) { +    if (value == undefined) { +      value = ''; +    } else if (typeof value != 'string') { +      value = angular.toJson(value); +    } +    result.push('' + value); +  } + +  selection.each(function() { +    var element = windowJquery(this), +        binding; +    if (binding = element.data('$binding')) { +      if (typeof binding == 'string') { +        if (match(binding)) { +          push(element.scope().$eval(binding)); +        }        } else { -        result.push(element.html()); +        if (!angular.isArray(binding)) { +          binding = [binding]; +        } +        for(var fns, j=0, jj=binding.length;  j<jj; j++) { +          fns = binding[j]; +          if (fns.parts) { +            fns = fns.parts; +          } else { +            fns = [fns]; +          } +          for (var scope, fn, i = 0, ii = fns.length; i < ii; i++) { +            if(match((fn = fns[i]).exp)) { +              push(fn(scope = scope || element.scope())); +            } +          } +        }        }      }    }); diff --git a/src/scenario/dsl.js b/src/scenario/dsl.js index bbe29948..fb0037e0 100644 --- a/src/scenario/dsl.js +++ b/src/scenario/dsl.js @@ -180,7 +180,7 @@ angular.scenario.dsl('using', function() {  angular.scenario.dsl('binding', function() {    return function(name) {      return this.addFutureAction("select binding '" + name + "'", function($window, $document, done) { -      var values = $document.elements().bindings(name); +      var values = $document.elements().bindings($window.angular.element, name);        if (!values.length) {          return done("Binding selector '" + name + "' did not match.");        } @@ -260,7 +260,7 @@ angular.scenario.dsl('repeater', function() {    chain.column = function(binding) {      return this.addFutureAction("repeater '" + this.label + "' column '" + binding + "'", function($window, $document, done) { -      done(null, $document.elements().bindings(binding)); +      done(null, $document.elements().bindings($window.angular.element, binding));      });    }; @@ -269,7 +269,7 @@ angular.scenario.dsl('repeater', function() {        var matches = $document.elements().slice(index, index + 1);        if (!matches.length)          return done('row ' + index + ' out of bounds'); -      done(null, matches.bindings()); +      done(null, matches.bindings($window.angular.element));      });    }; diff --git a/src/service/compiler.js b/src/service/compiler.js index 6185c909..c663baac 100644 --- a/src/service/compiler.js +++ b/src/service/compiler.js @@ -33,7 +33,7 @@                   // watch the 'compile' expression for changes                  return scope.$eval(attrs.compile);                }, -              function(scope, value) { +              function(value) {                  // when the 'compile' expression changes                  // assign it into the current DOM                  element.html(value); @@ -631,7 +631,7 @@ function $CompileProvider($provide) {                  bindings = parent.data('$binding') || [];              bindings.push(interpolateFn);              parent.data('$binding', bindings).addClass('ng-binding'); -            scope.$watch(interpolateFn, function(scope, value) { +            scope.$watch(interpolateFn, function(value) {                node[0].nodeValue = value;              });            }) @@ -656,7 +656,7 @@ function $CompileProvider($provide) {          compile: function(element, attr) {            if (interpolateFn) {              return function(scope, element, attr) { -              scope.$watch(interpolateFn, function(scope, value){ +              scope.$watch(interpolateFn, function(value) {                  attr.$set(name, value);                });              }; diff --git a/src/service/filter.js b/src/service/filter.js index 2947d84b..4ed3f620 100644 --- a/src/service/filter.js +++ b/src/service/filter.js @@ -94,7 +94,6 @@ function $FilterProvider($provide) {    register('currency', currencyFilter);    register('date', dateFilter);    register('filter', filterFilter); -  register('html', htmlFilter);    register('json', jsonFilter);    register('limitTo', limitToFilter);    register('linky', linkyFilter); diff --git a/src/service/filter/filter.js b/src/service/filter/filter.js index 61cfc80f..49960546 100644 --- a/src/service/filter/filter.js +++ b/src/service/filter/filter.js @@ -64,17 +64,17 @@       <doc:scenario>         it('should search across all fields when filtering with a string', function() {           input('searchText').enter('m'); -         expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). +         expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).             toEqual(['Mary', 'Mike', 'Adam']);           input('searchText').enter('76'); -         expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). +         expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).             toEqual(['John', 'Julie']);         });         it('should search in specific fields when filtering with a predicate object', function() {           input('search.$').enter('i'); -         expect(repeater('#searchObjResults tr', 'friend in friends').column('name')). +         expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).             toEqual(['Mary', 'Mike', 'Julie']);         });       </doc:scenario> diff --git a/src/service/filter/filters.js b/src/service/filter/filters.js index 69bfbacf..58a3a869 100644 --- a/src/service/filter/filters.js +++ b/src/service/filter/filters.js @@ -385,7 +385,7 @@ function dateFilter($locale) {       </doc:source>       <doc:scenario>         it('should jsonify filtered objects', function() { -         expect(binding('| json')).toBe('{\n  "name":"value"}'); +         expect(binding("{'name':'value'}")).toBe('{\n  "name":"value"}');         });       </doc:scenario>     </doc:example> @@ -422,108 +422,6 @@ var uppercaseFilter = valueFn(uppercase);  /**   * @ngdoc filter - * @name angular.module.ng.$filter.html - * @function - * - * @description - *   Prevents the input from getting escaped by angular. By default the input is sanitized and - *   inserted into the DOM as is. - * - *   The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are - *   then serialized back to properly escaped html string. This means that no unsafe input can make - *   it into the returned string, however, since our parser is more strict than a typical browser - *   parser, it's possible that some obscure input, which would be recognized as valid HTML by a - *   browser, won't make it through the sanitizer. - * - *   If you hate your users, you may call the filter with optional 'unsafe' argument, which bypasses - *   the html sanitizer, but makes your application vulnerable to XSS and other attacks. Using this - *   option is strongly discouraged and should be used only if you absolutely trust the input being - *   filtered and you can't get the content through the sanitizer. - * - * @param {string} html Html input. - * @param {string=} option If 'unsafe' then do not sanitize the HTML input. - * @returns {string} Sanitized or raw html. - * - * @example -   <doc:example> -     <doc:source> -       <script> -         function Ctrl($scope) { -           $scope.snippet = -             '<p style="color:blue">an html\n' + -             '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + -             'snippet</p>'; -         } -       </script> -       <div ng:controller="Ctrl"> -          Snippet: <textarea ng:model="snippet" cols="60" rows="3"></textarea> -           <table> -             <tr> -               <td>Filter</td> -               <td>Source</td> -               <td>Rendered</td> -             </tr> -             <tr id="html-filter"> -               <td>html filter</td> -               <td> -                 <pre><div ng:bind="snippet | html"><br/></div></pre> -               </td> -               <td> -                 <div ng:bind="snippet | html"></div> -               </td> -             </tr> -             <tr id="escaped-html"> -               <td>no filter</td> -               <td><pre><div ng:bind="snippet"><br/></div></pre></td> -               <td><div ng:bind="snippet"></div></td> -             </tr> -             <tr id="html-unsafe-filter"> -               <td>unsafe html filter</td> -               <td><pre><div ng:bind="snippet | html:'unsafe'"><br/></div></pre></td> -               <td><div ng:bind="snippet | html:'unsafe'"></div></td> -             </tr> -           </table> -         </div> -     </doc:source> -     <doc:scenario> -       it('should sanitize the html snippet ', function() { -         expect(using('#html-filter').binding('snippet | html')). -           toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); -       }); - -       it('should escape snippet without any filter', function() { -         expect(using('#escaped-html').binding('snippet')). -           toBe("<p style=\"color:blue\">an html\n" + -                "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + -                "snippet</p>"); -       }); - -       it('should inline raw snippet if filtered as unsafe', function() { -         expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")). -           toBe("<p style=\"color:blue\">an html\n" + -                "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + -                "snippet</p>"); -       }); - -       it('should update', function() { -         input('snippet').enter('new <b>text</b>'); -         expect(using('#html-filter').binding('snippet | html')).toBe('new <b>text</b>'); -         expect(using('#escaped-html').binding('snippet')).toBe("new <b>text</b>"); -         expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new <b>text</b>'); -       }); -     </doc:scenario> -   </doc:example> - */ -//TODO(misko): turn sensitization into injectable service -function htmlFilter() { -  return function(html, option){ -    return new HTML(html, option); -  }; -} - - -/** - * @ngdoc filter   * @name angular.module.ng.$filter.linky   * @function   * @@ -558,10 +456,10 @@ function htmlFilter() {           <tr id="linky-filter">             <td>linky filter</td>             <td> -             <pre><div ng:bind="snippet | linky"><br/></div></pre> +             <pre><div ng:bind-html="snippet | linky"><br/></div></pre>             </td>             <td> -             <div ng:bind="snippet | linky"></div> +             <div ng:bind-html="snippet | linky"></div>             </td>           </tr>           <tr id="escaped-html"> @@ -574,10 +472,10 @@ function htmlFilter() {       <doc:scenario>         it('should linkify the snippet with urls', function() {           expect(using('#linky-filter').binding('snippet | linky')). -           toBe('Pretty text with some links:\n' + -                '<a href="http://angularjs.org/">http://angularjs.org/</a>,\n' + -                '<a href="mailto:us@somewhere.org">us@somewhere.org</a>,\n' + -                '<a href="mailto:another@somewhere.org">another@somewhere.org</a>,\n' + +           toBe('Pretty text with some links:
' + +                '<a href="http://angularjs.org/">http://angularjs.org/</a>,
' + +                '<a href="mailto:us@somewhere.org">us@somewhere.org</a>,
' + +                '<a href="mailto:another@somewhere.org">another@somewhere.org</a>,
' +                  'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');         }); @@ -624,6 +522,6 @@ function linkyFilter() {        raw = raw.substring(i + match[0].length);      }      writer.chars(raw); -    return new HTML(html.join('')); +    return html.join('');    };  }; diff --git a/src/service/filter/orderBy.js b/src/service/filter/orderBy.js index c67d2769..e7528a4b 100644 --- a/src/service/filter/orderBy.js +++ b/src/service/filter/orderBy.js @@ -63,7 +63,7 @@       </doc:source>       <doc:scenario>         it('should be reverse ordered by aged', function() { -         expect(binding('predicate')).toBe('Sorting predicate = -age; reverse = '); +         expect(binding('predicate')).toBe('-age');           expect(repeater('table.friend', 'friend in friends').column('friend.age')).             toEqual(['35', '29', '21', '19', '10']);           expect(repeater('table.friend', 'friend in friends').column('friend.name')). diff --git a/src/widget/form.js b/src/widget/form.js index 962cb6b8..405aae74 100644 --- a/src/widget/form.js +++ b/src/widget/form.js @@ -82,28 +82,31 @@        </doc:scenario>      </doc:example>   */ -angularWidget('form', function(form){ -  this.descend(true); -  this.directives(true); -  return ['$formFactory', '$element', function($formFactory, formElement) { -    var name = formElement.attr('name'), -        parentForm = $formFactory.forElement(formElement), -        form = $formFactory(parentForm); -    formElement.data('$form', form); -    formElement.bind('submit', function(event) { -      if (!formElement.attr('action')) event.preventDefault(); -    }); -    if (name) { -      this[name] = form; +var ngFormDirective = ['$formFactory', function($formFactory) { +  return { +    restrict: 'E', +    compile: function() { +      return { +        pre: function(scope, formElement, attr) { +          var name = attr.name, +            parentForm = $formFactory.forElement(formElement), +            form = $formFactory(parentForm); +          formElement.data('$form', form); +          formElement.bind('submit', function(event){ +            if (!attr.action) event.preventDefault(); +          }); +          if (name) { +            scope[name] = form; +          } +          watch('valid'); +          watch('invalid'); +          function watch(name) { +            form.$watch('$' + name, function(value) { +              formElement[value ? 'addClass' : 'removeClass']('ng-' + name); +            }); +          } +        } +      };      } -    watch('valid'); -    watch('invalid'); -    function watch(name) { -      form.$watch('$' + name, function(value) { -        formElement[value ? 'addClass' : 'removeClass']('ng-' + name); -      }); -    } -  }]; -}); - -angularWidget('ng:form', angularWidget('form')); +  }; +}]; diff --git a/src/widget/input.js b/src/widget/input.js index e666a0c1..9f9d9852 100644 --- a/src/widget/input.js +++ b/src/widget/input.js @@ -542,23 +542,23 @@ angularInputType('checkbox', function(inputElement, widget) {        </doc:scenario>      </doc:example>   */ -angularInputType('radio', function(inputElement, widget) { +angularInputType('radio', function(inputElement, widget, attr) {    //correct the name -  inputElement.attr('name', widget.$id + '@' + inputElement.attr('name')); +  attr.$set('name', widget.$id + '@' + attr.name);    inputElement.bind('click', function() {      widget.$apply(function() {        if (inputElement[0].checked) { -        widget.$emit('$viewChange', widget.$value); +        widget.$emit('$viewChange', attr.value);        }      });    });    widget.$render = function() { -    inputElement[0].checked = isDefined(widget.$value) && (widget.$value == widget.$viewValue); +    inputElement[0].checked = isDefined(attr.value) && (attr.value == widget.$viewValue);    };    if (inputElement[0].checked) { -    widget.$viewValue = widget.$value; +    widget.$viewValue = attr.value;    }  }); @@ -664,28 +664,28 @@ var HTML5_INPUTS_TYPES =  makeMap(        </doc:source>        <doc:scenario>          it('should initialize to model', function() { -          expect(binding('user')).toEqual('{\n  \"last\":\"visitor",\n  \"name\":\"guest\"}'); +          expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}');            expect(binding('myForm.userName.$valid')).toEqual('true');            expect(binding('myForm.$valid')).toEqual('true');          });          it('should be invalid if empty when required', function() {            input('user.name').enter(''); -          expect(binding('user')).toEqual('{\n  \"last\":\"visitor",\n  \"name\":\"\"}'); +          expect(binding('user')).toEqual('{"last":"visitor","name":""}');            expect(binding('myForm.userName.$valid')).toEqual('false');            expect(binding('myForm.$valid')).toEqual('false');          });          it('should be valid if empty when min length is set', function() {            input('user.last').enter(''); -          expect(binding('user')).toEqual('{\n  \"last\":\"",\n  \"name\":\"guest\"}'); +          expect(binding('user')).toEqual('{"last":"","name":"guest"}');            expect(binding('myForm.lastName.$valid')).toEqual('true');            expect(binding('myForm.$valid')).toEqual('true');          });          it('should be invalid if less than required min length', function() {            input('user.last').enter('xx'); -          expect(binding('user')).toEqual('{\n  \"last\":\"xx",\n  \"name\":\"guest\"}'); +          expect(binding('user')).toEqual('{"last":"xx","name":"guest"}');            expect(binding('myForm.lastName.$valid')).toEqual('false');            expect(binding('myForm.lastName.$error')).toMatch(/MINLENGTH/);            expect(binding('myForm.$valid')).toEqual('false'); @@ -694,7 +694,7 @@ var HTML5_INPUTS_TYPES =  makeMap(          it('should be valid if longer than max length', function() {            input('user.last').enter('some ridiculously long name');            expect(binding('user')) -            .toEqual('{\n  \"last\":\"some ridiculously long name",\n  \"name\":\"guest\"}'); +            .toEqual('{"last":"some ridiculously long name","name":"guest"}');            expect(binding('myForm.lastName.$valid')).toEqual('false');            expect(binding('myForm.lastName.$error')).toMatch(/MAXLENGTH/);            expect(binding('myForm.$valid')).toEqual('false'); @@ -702,26 +702,24 @@ var HTML5_INPUTS_TYPES =  makeMap(        </doc:scenario>      </doc:example>   */ -angularWidget('input', function(inputElement){ -  this.directives(true); -  this.descend(true); -  var modelExp = inputElement.attr('ng:model'); -  return modelExp && -    ['$defer', '$formFactory', '$element', -        function($defer, $formFactory, inputElement) { +var inputDirective = ['$defer', '$formFactory', function($defer, $formFactory) { +  return { +    restrict: 'E', +    link: function(modelScope, inputElement, attr) { +      if (!attr.ngModel) return; +        var form = $formFactory.forElement(inputElement),            // We have to use .getAttribute, since jQuery tries to be smart and use the            // type property. Trouble is some browser change unknown to text. -          type = inputElement[0].getAttribute('type') || 'text', +          type = attr.type || 'text',            TypeController, -          modelScope = this,            patternMatch, widget, -          pattern = trim(inputElement.attr('ng:pattern')), -          minlength = parseInt(inputElement.attr('ng:minlength'), 10), -          maxlength = parseInt(inputElement.attr('ng:maxlength'), 10), +          pattern = attr.ngPattern, +          modelExp = attr.ngModel, +          minlength = parseInt(attr.ngMinlength, 10), +          maxlength = parseInt(attr.ngMaxlength, 10),            loadFromScope = type.match(/^\s*\@\s*(.*)/); -         if (!pattern) {           patternMatch = valueFn(true);         } else { @@ -743,7 +741,7 @@ angularWidget('input', function(inputElement){        type = lowercase(type);        TypeController = (loadFromScope -              ? (assertArgFn(this.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn +              ? (assertArgFn(modelScope.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn                : angularInputType(type)) || noop;        if (!HTML5_INPUTS_TYPES[type]) { @@ -757,26 +755,21 @@ angularWidget('input', function(inputElement){        }        //TODO(misko): setting $inject is a hack -      !TypeController.$inject && (TypeController.$inject = ['$element', '$scope']); +      !TypeController.$inject && (TypeController.$inject = ['$element', '$scope', '$attr']);        widget = form.$createWidget({            scope: modelScope,            model: modelExp, -          onChange: inputElement.attr('ng:change'), -          alias: inputElement.attr('name'), +          onChange: attr.ngChange, +          alias: attr.name,            controller: TypeController, -          controllerArgs: {$element: inputElement} +          controllerArgs: {$element: inputElement, $attr: attr}        }); -      watchElementProperty(this, widget, 'value', inputElement); -      watchElementProperty(this, widget, 'required', inputElement); -      watchElementProperty(this, widget, 'readonly', inputElement); -      watchElementProperty(this, widget, 'disabled', inputElement); -        widget.$pristine = !(widget.$dirty = false);        widget.$on('$validate', function() {          var $viewValue = trim(widget.$viewValue), -            inValid = widget.$required && !$viewValue, +            inValid = attr.required && !$viewValue,              tooLong = maxlength && $viewValue && $viewValue.length > maxlength,              tooShort = minlength && $viewValue && $viewValue.length < minlength,              missMatch = $viewValue && !patternMatch($viewValue); @@ -812,7 +805,7 @@ angularWidget('input', function(inputElement){            inputElement.val(widget.$viewValue || '');          }; -        inputElement.bind('keydown change input', function(event) { +        inputElement.bind('keydown change input', function(event){            var key = event.keyCode;            if (/*command*/   key != 91 &&                /*modifiers*/ !(15 < key && key < 19) && @@ -827,8 +820,9 @@ angularWidget('input', function(inputElement){            }          });        } -    }]; -}); +    } +  }; +}];  /** @@ -856,24 +850,3 @@ angularWidget('input', function(inputElement){   * @param {string=} ng:change Angular expression to be executed when input changes due to user   *    interaction with the input element.   */ -angularWidget('textarea', angularWidget('input')); - - -function watchElementProperty(modelScope, widget, name, element) { -  var bindAttr = fromJson(element.attr('ng:bind-attr') || '{}'), -      match = /\s*\{\{(.*)\}\}\s*/.exec(bindAttr[name]), -      isBoolean = BOOLEAN_ATTR[name]; -  widget['$' + name] = isBoolean -    ? ( // some browsers return true some '' when required is set without value. -        isString(element.prop(name)) || !!element.prop(name) || -        // this is needed for ie9, since it will treat boolean attributes as false -        !!element[0].attributes[name]) -    : element.attr(name); -  if (bindAttr[name] && match) { -    modelScope.$watch(match[1], function(value) { -      widget['$' + name] = isBoolean ? !!value : value; -      widget.$emit('$validate'); -      widget.$render && widget.$render(); -    }); -  } -} diff --git a/src/widget/select.js b/src/widget/select.js index a3633ddd..ae9e1b7c 100644 --- a/src/widget/select.js +++ b/src/widget/select.js @@ -112,321 +112,335 @@        </doc:source>        <doc:scenario>           it('should check ng:options', function() { -           expect(binding('color')).toMatch('red'); +           expect(binding('{selected_color:color}')).toMatch('red');             select('color').option('0'); -           expect(binding('color')).toMatch('black'); +           expect(binding('{selected_color:color}')).toMatch('black');             using('.nullable').select('color').option(''); -           expect(binding('color')).toMatch('null'); +           expect(binding('{selected_color:color}')).toMatch('null');           });        </doc:scenario>      </doc:example>   */ - -                       //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777 -var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/; - - -angularWidget('select', function(element){ -  this.directives(true); -  this.descend(true); -  return element.attr('ng:model') && -               ['$formFactory', '$compile', '$parse', '$element', -        function($formFactory,   $compile,   $parse,   selectElement){ -    var modelScope = this, -        match, -        form = $formFactory.forElement(selectElement), -        multiple = selectElement.attr('multiple'), -        optionsExp = selectElement.attr('ng:options'), -        modelExp = selectElement.attr('ng:model'), -        widget = form.$createWidget({ -          scope: modelScope, -          model: modelExp, -          onChange: selectElement.attr('ng:change'), -          alias: selectElement.attr('name'), -          controller: ['$scope', optionsExp ? Options : (multiple ? Multiple : Single)]}); - -    selectElement.bind('$destroy', function() { widget.$destroy(); }); - -    widget.$pristine = !(widget.$dirty = false); - -    watchElementProperty(modelScope, widget, 'required', selectElement); -    watchElementProperty(modelScope, widget, 'readonly', selectElement); -    watchElementProperty(modelScope, widget, 'disabled', selectElement); - -    widget.$on('$validate', function() { -      var valid = !widget.$required || !!widget.$modelValue; -      if (valid && multiple && widget.$required) valid = !!widget.$modelValue.length; -      if (valid !== !widget.$error.REQUIRED) { -        widget.$emit(valid ? '$valid' : '$invalid', 'REQUIRED'); -      } -    }); - -    widget.$on('$viewChange', function() { -      widget.$pristine = !(widget.$dirty = true); -    }); - -    forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) { -      widget.$watch('$' + name, function(value) { -        selectElement[value ? 'addClass' : 'removeClass']('ng-' + name); +var ngOptionsDirective = valueFn({ terminal: true }); +var selectDirective = ['$formFactory', '$compile', '$parse', +               function($formFactory,   $compile,   $parse){ +                         //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777 +  var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/; + +  return { +    restrict: 'E', +    link: function(modelScope, selectElement, attr) { +      if (!attr.ngModel) return; +      var form = $formFactory.forElement(selectElement), +          multiple = attr.multiple, +          optionsExp = attr.ngOptions, +          modelExp = attr.ngModel, +          widget = form.$createWidget({ +            scope: modelScope, +            model: modelExp, +            onChange: attr.ngChange, +            alias: attr.name, +            controller: ['$scope', optionsExp ? Options : (multiple ? Multiple : Single)]}); + +      selectElement.bind('$destroy', function() { widget.$destroy(); }); + +      widget.$pristine = !(widget.$dirty = false); + +      widget.$on('$validate', function() { +        var valid = !attr.required || !!widget.$modelValue; +        if (valid && multiple && attr.required) valid = !!widget.$modelValue.length; +        if (valid !== !widget.$error.REQUIRED) { +          widget.$emit(valid ? '$valid' : '$invalid', 'REQUIRED'); +        }        }); -    }); -    //////////////////////////// +      widget.$on('$viewChange', function() { +        widget.$pristine = !(widget.$dirty = true); +      }); -    function Multiple(widget) { -      widget.$render = function() { -        var items = new HashMap(widget.$viewValue); -        forEach(selectElement.children(), function(option){ -          option.selected = isDefined(items.get(option.value)); +      forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) { +        widget.$watch('$' + name, function(value) { +          selectElement[value ? 'addClass' : 'removeClass']('ng-' + name);          }); -      }; +      }); + +      //////////////////////////// -      selectElement.bind('change', function() { -        widget.$apply(function() { -          var array = []; +      function Multiple(widget) { +        widget.$render = function() { +          var items = new HashMap(this.$viewValue);            forEach(selectElement.children(), function(option){ -            if (option.selected) { -              array.push(option.value); -            } +            option.selected = isDefined(items.get(option.value)); +          }); +        }; + +        selectElement.bind('change', function() { +          widget.$apply(function() { +            var array = []; +            forEach(selectElement.children(), function(option){ +              if (option.selected) { +                array.push(option.value); +              } +            }); +            widget.$emit('$viewChange', array);            }); -          widget.$emit('$viewChange', array);          }); -      }); -    } +      } -    function Single(widget) { -      widget.$render = function() { -        selectElement.val(widget.$viewValue); -      }; +      function Single(widget) { +        widget.$render = function() { +          selectElement.val(widget.$viewValue); +        }; -      selectElement.bind('change', function() { -        widget.$apply(function() { -          widget.$emit('$viewChange', selectElement.val()); +        selectElement.bind('change', function() { +          widget.$apply(function() { +            widget.$emit('$viewChange', selectElement.val()); +          });          }); -      }); - -      widget.$viewValue = selectElement.val(); -    } - -    function Options(widget) { -      var match; -      if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) { -        throw Error( -          "Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + -          " but got '" + optionsExp + "'."); +        widget.$viewValue = selectElement.val();        } -      var displayFn = $parse(match[2] || match[1]), -          valueName = match[4] || match[6], -          keyName = match[5], -          groupByFn = $parse(match[3] || ''), -          valueFn = $parse(match[2] ? match[1] : valueName), -          valuesFn = $parse(match[7]), -          // we can't just jqLite('<option>') since jqLite is not smart enough -          // to create it in <select> and IE barfs otherwise. -          optionTemplate = jqLite(document.createElement('option')), -          optGroupTemplate = jqLite(document.createElement('optgroup')), -          nullOption = false, // if false then user will not be able to select it -          // This is an array of array of existing option groups in DOM. We try to reuse these if possible -          // optionGroupsCache[0] is the options with no option group -          // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element -          optionGroupsCache = [[{element: selectElement, label:''}]]; - -      // find existing special options -      forEach(selectElement.children(), function(option) { -        if (option.value == '') { -          // developer declared null option, so user should be able to select it -          nullOption = jqLite(option).remove(); -          // compile the element since there might be bindings in it -          $compile(nullOption)(modelScope); -        } -      }); -      selectElement.html(''); // clear contents +      function Options(widget) { +        var match; -      selectElement.bind('change', function() { -        widget.$apply(function() { -          var optionGroup, -              collection = valuesFn(modelScope) || [], -              key = selectElement.val(), -              tempScope = inherit(modelScope), -              value, optionElement, index, groupIndex, length, groupLength; +        if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) { +          throw Error( +            "Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + +            " but got '" + optionsExp + "'."); +        } -          if (multiple) { -            value = []; -            for (groupIndex = 0, groupLength = optionGroupsCache.length; -            groupIndex < groupLength; -            groupIndex++) { -              // list of options for that group. (first item has the parent) -              optionGroup = optionGroupsCache[groupIndex]; - -              for(index = 1, length = optionGroup.length; index < length; index++) { -                if ((optionElement = optionGroup[index].element)[0].selected) { -                  if (keyName) tempScope[keyName] = key; -                  tempScope[valueName] = collection[optionElement.val()]; -                  value.push(valueFn(tempScope)); +        var displayFn = $parse(match[2] || match[1]), +            valueName = match[4] || match[6], +            keyName = match[5], +            groupByFn = $parse(match[3] || ''), +            valueFn = $parse(match[2] ? match[1] : valueName), +            valuesFn = $parse(match[7]), +            // we can't just jqLite('<option>') since jqLite is not smart enough +            // to create it in <select> and IE barfs otherwise. +            optionTemplate = jqLite(document.createElement('option')), +            optGroupTemplate = jqLite(document.createElement('optgroup')), +            nullOption = false, // if false then user will not be able to select it +            // This is an array of array of existing option groups in DOM. We try to reuse these if possible +            // optionGroupsCache[0] is the options with no option group +            // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element +            optionGroupsCache = [[{element: selectElement, label:''}]]; + +        // find existing special options +        forEach(selectElement.children(), function(option) { +          if (option.value == '') { +            // developer declared null option, so user should be able to select it +            nullOption = jqLite(option).remove(); +            // compile the element since there might be bindings in it +            $compile(nullOption)(modelScope); +          } +        }); +        selectElement.html(''); // clear contents + +        selectElement.bind('change', function() { +          widget.$apply(function() { +            var optionGroup, +                collection = valuesFn(modelScope) || [], +                key = selectElement.val(), +                tempScope = inherit(modelScope), +                value, optionElement, index, groupIndex, length, groupLength; + +            if (multiple) { +              value = []; +              for (groupIndex = 0, groupLength = optionGroupsCache.length; +              groupIndex < groupLength; +              groupIndex++) { +                // list of options for that group. (first item has the parent) +                optionGroup = optionGroupsCache[groupIndex]; + +                for(index = 1, length = optionGroup.length; index < length; index++) { +                  if ((optionElement = optionGroup[index].element)[0].selected) { +                    if (keyName) tempScope[keyName] = key; +                    tempScope[valueName] = collection[optionElement.val()]; +                    value.push(valueFn(tempScope)); +                  }                  }                } -            } -          } else { -            if (key == '?') { -              value = undefined; -            } else if (key == ''){ -              value = null;              } else { -              tempScope[valueName] = collection[key]; -              if (keyName) tempScope[keyName] = key; -              value = valueFn(tempScope); +              if (key == '?') { +                value = undefined; +              } else if (key == ''){ +                value = null; +              } else { +                tempScope[valueName] = collection[key]; +                if (keyName) tempScope[keyName] = key; +                value = valueFn(tempScope); +              }              } -          } -          if (isDefined(value) && modelScope.$viewVal !== value) { -            widget.$emit('$viewChange', value); -          } +            if (isDefined(value) && modelScope.$viewVal !== value) { +              widget.$emit('$viewChange', value); +            } +          });          }); -      }); -      widget.$watch(render); -      widget.$render = render; - -      function render() { -        var optionGroups = {'':[]}, // Temporary location for the option groups before we render them -            optionGroupNames = [''], -            optionGroupName, -            optionGroup, -            option, -            existingParent, existingOptions, existingOption, -            modelValue = widget.$modelValue, -            values = valuesFn(modelScope) || [], -            keys = keyName ? sortedKeys(values) : values, -            groupLength, length, -            groupIndex, index, -            optionScope = inherit(modelScope), -            selected, -            selectedSet = false, // nothing is selected yet -            lastElement, -            element; - -        if (multiple) { -          selectedSet = new HashMap(modelValue); -        } else if (modelValue === null || nullOption) { -          // if we are not multiselect, and we are null then we have to add the nullOption -          optionGroups[''].push({selected:modelValue === null, id:'', label:''}); -          selectedSet = true; -        } +        widget.$watch(render); +        widget.$render = render; + +        function render() { +          var optionGroups = {'':[]}, // Temporary location for the option groups before we render them +              optionGroupNames = [''], +              optionGroupName, +              optionGroup, +              option, +              existingParent, existingOptions, existingOption, +              modelValue = widget.$modelValue, +              values = valuesFn(modelScope) || [], +              keys = keyName ? sortedKeys(values) : values, +              groupLength, length, +              groupIndex, index, +              optionScope = inherit(modelScope), +              selected, +              selectedSet = false, // nothing is selected yet +              lastElement, +              element; -        // We now build up the list of options we need (we merge later) -        for (index = 0; length = keys.length, index < length; index++) { -             optionScope[valueName] = values[keyName ? optionScope[keyName]=keys[index]:index]; -             optionGroupName = groupByFn(optionScope) || ''; -          if (!(optionGroup = optionGroups[optionGroupName])) { -            optionGroup = optionGroups[optionGroupName] = []; -            optionGroupNames.push(optionGroupName); -          }            if (multiple) { -            selected = selectedSet.remove(valueFn(optionScope)) != undefined; -          } else { -            selected = modelValue === valueFn(optionScope); -            selectedSet = selectedSet || selected; // see if at least one item is selected +            selectedSet = new HashMap(modelValue); +          } else if (modelValue === null || nullOption) { +            // if we are not multiselect, and we are null then we have to add the nullOption +            optionGroups[''].push({selected:modelValue === null, id:'', label:''}); +            selectedSet = true;            } -          optionGroup.push({ -            id: keyName ? keys[index] : index,   // either the index into array or key from object -            label: displayFn(optionScope) || '', // what will be seen by the user -            selected: selected                   // determine if we should be selected -          }); -        } -        if (!multiple && !selectedSet) { -          // nothing was selected, we have to insert the undefined item -          optionGroups[''].unshift({id:'?', label:'', selected:true}); -        } -        // Now we need to update the list of DOM nodes to match the optionGroups we computed above -        for (groupIndex = 0, groupLength = optionGroupNames.length; -             groupIndex < groupLength; -             groupIndex++) { -          // current option group name or '' if no group -          optionGroupName = optionGroupNames[groupIndex]; - -          // list of options for that group. (first item has the parent) -          optionGroup = optionGroups[optionGroupName]; - -          if (optionGroupsCache.length <= groupIndex) { -            // we need to grow the optionGroups -            existingParent = { -              element: optGroupTemplate.clone().attr('label', optionGroupName), -              label: optionGroup.label -            }; -            existingOptions = [existingParent]; -            optionGroupsCache.push(existingOptions); -            selectElement.append(existingParent.element); -          } else { -            existingOptions = optionGroupsCache[groupIndex]; -            existingParent = existingOptions[0];  // either SELECT (no group) or OPTGROUP element - -            // update the OPTGROUP label if not the same. -            if (existingParent.label != optionGroupName) { -              existingParent.element.attr('label', existingParent.label = optionGroupName); +          // We now build up the list of options we need (we merge later) +          for (index = 0; length = keys.length, index < length; index++) { +               optionScope[valueName] = values[keyName ? optionScope[keyName]=keys[index]:index]; +               optionGroupName = groupByFn(optionScope) || ''; +            if (!(optionGroup = optionGroups[optionGroupName])) { +              optionGroup = optionGroups[optionGroupName] = []; +              optionGroupNames.push(optionGroupName); +            } +            if (multiple) { +              selected = selectedSet.remove(valueFn(optionScope)) != undefined; +            } else { +              selected = modelValue === valueFn(optionScope); +              selectedSet = selectedSet || selected; // see if at least one item is selected              } +            optionGroup.push({ +              id: keyName ? keys[index] : index,   // either the index into array or key from object +              label: displayFn(optionScope) || '', // what will be seen by the user +              selected: selected                   // determine if we should be selected +            }); +          } +          if (!multiple && !selectedSet) { +            // nothing was selected, we have to insert the undefined item +            optionGroups[''].unshift({id:'?', label:'', selected:true});            } -          lastElement = null;  // start at the begining -          for(index = 0, length = optionGroup.length; index < length; index++) { -            option = optionGroup[index]; -            if ((existingOption = existingOptions[index+1])) { -              // reuse elements -              lastElement = existingOption.element; -              if (existingOption.label !== option.label) { -                lastElement.text(existingOption.label = option.label); -              } -              if (existingOption.id !== option.id) { -                lastElement.val(existingOption.id = option.id); -              } -              if (existingOption.element.selected !== option.selected) { -                lastElement.prop('selected', (existingOption.selected = option.selected)); -              } +          // Now we need to update the list of DOM nodes to match the optionGroups we computed above +          for (groupIndex = 0, groupLength = optionGroupNames.length; +               groupIndex < groupLength; +               groupIndex++) { +            // current option group name or '' if no group +            optionGroupName = optionGroupNames[groupIndex]; + +            // list of options for that group. (first item has the parent) +            optionGroup = optionGroups[optionGroupName]; + +            if (optionGroupsCache.length <= groupIndex) { +              // we need to grow the optionGroups +              existingParent = { +                element: optGroupTemplate.clone().attr('label', optionGroupName), +                label: optionGroup.label +              }; +              existingOptions = [existingParent]; +              optionGroupsCache.push(existingOptions); +              selectElement.append(existingParent.element);              } else { -              // grow elements +              existingOptions = optionGroupsCache[groupIndex]; +              existingParent = existingOptions[0];  // either SELECT (no group) or OPTGROUP element -              // if it's a null option -              if (option.id === '' && nullOption) { -                // put back the pre-compiled element -                element = nullOption; -              } else { -                // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but -                // in this version of jQuery on some browser the .text() returns a string -                // rather then the element. -                (element = optionTemplate.clone()) -                    .val(option.id) -                    .attr('selected', option.selected) -                    .text(option.label); +              // update the OPTGROUP label if not the same. +              if (existingParent.label != optionGroupName) { +                existingParent.element.attr('label', existingParent.label = optionGroupName);                } +            } -              existingOptions.push(existingOption = { -                  element: element, -                  label: option.label, -                  id: option.id, -                  selected: option.selected -              }); -              if (lastElement) { -                lastElement.after(element); +            lastElement = null;  // start at the begining +            for(index = 0, length = optionGroup.length; index < length; index++) { +              option = optionGroup[index]; +              if ((existingOption = existingOptions[index+1])) { +                // reuse elements +                lastElement = existingOption.element; +                if (existingOption.label !== option.label) { +                  lastElement.text(existingOption.label = option.label); +                } +                if (existingOption.id !== option.id) { +                  lastElement.val(existingOption.id = option.id); +                } +                if (existingOption.element.selected !== option.selected) { +                  lastElement.prop('selected', (existingOption.selected = option.selected)); +                }                } else { -                existingParent.element.append(element); +                // grow elements + +                // if it's a null option +                if (option.id === '' && nullOption) { +                  // put back the pre-compiled element +                  element = nullOption; +                } else { +                  // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but +                  // in this version of jQuery on some browser the .text() returns a string +                  // rather then the element. +                  (element = optionTemplate.clone()) +                      .val(option.id) +                      .attr('selected', option.selected) +                      .text(option.label); +                } + +                existingOptions.push(existingOption = { +                    element: element, +                    label: option.label, +                    id: option.id, +                    selected: option.selected +                }); +                if (lastElement) { +                  lastElement.after(element); +                } else { +                  existingParent.element.append(element); +                } +                lastElement = element;                } -              lastElement = element; +            } +            // remove any excessive OPTIONs in a group +            index++; // increment since the existingOptions[0] is parent element not OPTION +            while(existingOptions.length > index) { +              existingOptions.pop().element.remove();              }            } -          // remove any excessive OPTIONs in a group -          index++; // increment since the existingOptions[0] is parent element not OPTION -          while(existingOptions.length > index) { -            existingOptions.pop().element.remove(); +          // remove any excessive OPTGROUPs from select +          while(optionGroupsCache.length > groupIndex) { +            optionGroupsCache.pop()[0].element.remove();            } +        }; +      } +    } +  } +}]; + +var optionDirective = ['$interpolate', function($interpolate) { +  return { +    priority: 100, +    compile: function(element, attr) { +      if (isUndefined(attr.value)) { +        var interpolateFn = $interpolate(element.text(), true); +        if (interpolateFn) { +          return function (scope, element, attr) { +            scope.$watch(interpolateFn, function(value) { +              attr.$set('value', value); +            }); +          } +        } else { +          attr.$set('value', element.text());          } -        // remove any excessive OPTGROUPs from select -        while(optionGroupsCache.length > groupIndex) { -          optionGroupsCache.pop()[0].element.remove(); -        } -      }; +      }      } -  }]; -}); +  } +}]; diff --git a/src/widgets.js b/src/widgets.js index 53be8b14..cf32bdc1 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -67,7 +67,7 @@           </select>           url of the template: <tt><a href="{{template.url}}">{{template.url}}</a></tt>           <hr/> -         <ng:include src="template.url"></ng:include> +         <div class="ng-include" src="template.url"></div>         </div>        </doc:source>        <doc:scenario> @@ -87,64 +87,62 @@        </doc:scenario>      </doc:example>   */ -angularWidget('ng:include', function(element){ -  var compiler = this, -      srcExp = element.attr("src"), -      scopeExp = element.attr("scope") || '', -      onloadExp = element[0].getAttribute('onload') || '', //workaround for jquery bug #7537 -      autoScrollExp = element.attr('autoscroll'); - -  if (element[0]['ng:compiled']) { -    this.descend(true); -    this.directives(true); -  } else { -    element[0]['ng:compiled'] = true; -    return ['$http', '$templateCache', '$anchorScroll', '$element', -    function($http,   $templateCache,   $anchorScroll,   element) { -      var scope = this, -          changeCounter = 0, -          childScope; - -      function incrementChange() { changeCounter++;} -      this.$watch(srcExp, incrementChange); -      this.$watch(function() { -        var includeScope = scope.$eval(scopeExp); -        if (includeScope) return includeScope.$id; -      }, incrementChange); -      this.$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(); -              compiler.compile(element)(childScope); -              if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { -                $anchorScroll(); +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', +                  function($http,   $templateCache,   $anchorScroll,   $compile) { +  return { +    compile: function(element, attr) { +      var srcExp = attr.src, +          scopeExp = attr.scope || '', +          onloadExp = attr.onload || '', //workaround for jquery bug #7537 +          autoScrollExp = attr.autoscroll; +      if (!element[0]['ng:compiled']) { +        element[0]['ng:compiled'] = true; +        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('');                } -              scope.$eval(onloadExp);              } -          }).error(clearContent); -        } else { -          clearContent(); -        } -      }); -    }]; + +             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)(childScope); +                   if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { +                     $anchorScroll(); +                   } +                   scope.$eval(onloadExp); +                 } +               }).error(clearContent); +             } else { +               clearContent(); +             } +          }); +        }; +      } +    }    } -}); +}];  /**   * @ngdoc widget @@ -203,58 +201,62 @@ angularWidget('ng:include', function(element){        </doc:scenario>      </doc:example>   */ -angularWidget('ng:switch', function(element) { -  var compiler = this, -      watchExpr = element.attr("on"), -      changeExpr = element.attr('change'), -      casesTemplate = {}, -      defaultCaseTemplate, -      children = element.children(), -      length = children.length, -      child, -      when; - -  if (!watchExpr) throw new Error("Missing 'on' attribute."); -  while(length--) { -    child = jqLite(children[length]); -    // this needs to be here for IE -    child.remove(); -    when = child.attr('ng:switch-when'); -    if (isString(when)) { -      casesTemplate[when] = compiler.compile(child); -    } else if (isString(child.attr('ng:switch-default'))) { -      defaultCaseTemplate = compiler.compile(child); -    } -  } -  children = null; // release memory; -  element.html(''); +var ngSwitchDirective = ['$compile', function($compile){ +  return { +    compile: function(element, attr) { +      var watchExpr = attr.on, +        changeExpr = attr.change, +        casesTemplate = {}, +        defaultCaseTemplate, +        children = element.children(), +        length = children.length, +        child, +        when; + +      if (!watchExpr) throw new Error("Missing 'on' attribute."); +      while(length--) { +        child = jqLite(children[length]); +        // this needs to be here for IE +        child.remove(); +        // TODO(misko): this attr reading is not normilized +        when = child.attr('ng:switch-when'); +        if (isString(when)) { +          casesTemplate[when] = $compile(child); +          // TODO(misko): this attr reading is not normilized +        } else if (isString(child.attr('ng:switch-default'))) { +          defaultCaseTemplate = $compile(child); +        } +      } +      children = null; // release memory; +      element.html(''); -  return function(element){ -    var changeCounter = 0; -    var childScope; -    var selectedTemplate; -    var scope = this; +      return function(scope, element, attr){ +        var changeCounter = 0; +        var childScope; +        var selectedTemplate; -    this.$watch(watchExpr, function(value) { -      element.html(''); -      if ((selectedTemplate = casesTemplate[value] || defaultCaseTemplate)) { -        changeCounter++; -        if (childScope) childScope.$destroy(); -        childScope = scope.$new(); -        childScope.$eval(changeExpr); -      } -    }); +        scope.$watch(watchExpr, function(value) { +          element.html(''); +          if ((selectedTemplate = casesTemplate[value] || defaultCaseTemplate)) { +            changeCounter++; +            if (childScope) childScope.$destroy(); +            childScope = scope.$new(); +            childScope.$eval(changeExpr); +          } +        }); -    this.$watch(function() {return changeCounter;}, function() { -      element.html(''); -      if (selectedTemplate) { -        selectedTemplate(childScope, function(caseElement) { -          element.append(caseElement); +        scope.$watch(function() {return changeCounter;}, function() { +          element.html(''); +          if (selectedTemplate) { +            selectedTemplate(childScope, function(caseElement) { +              element.append(caseElement); +            }); +          }          }); -      } -    }); +      }; +    }    }; -}); +}];  /* @@ -265,25 +267,24 @@ angularWidget('ng:switch', function(element) {   * changing the location or causing page reloads, e.g.:   * <a href="" ng:click="model.$save()">Save</a>   */ -angularWidget('a', function() { -  this.descend(true); -  this.directives(true); - -  return function(element) { -    var hasNgHref = ((element.attr('ng:bind-attr') || '').indexOf('"href":') !== -1); - +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 (!hasNgHref && !element.attr('name') && !element.attr('href')) { -      element.attr('href', ''); +    if (!attr.href) { +      attr.$set('href', '');      } -    if (element.attr('href') === '' && !hasNgHref) { +    return function(scope, element) {        element.bind('click', function(event){ -        event.preventDefault(); +        // if we have no href url, then don't navigate anywhere. +        if (!element.attr('href')) { +          event.preventDefault(); +        }        });      } -  }; +  }  }); @@ -344,125 +345,131 @@ angularWidget('a', function() {        </doc:scenario>      </doc:example>   */ -angularWidget('@ng:repeat', function(expression, element){ -  element.removeAttr('ng:repeat'); -  element.replaceWith(jqLite('<!-- ng:repeat: ' + expression + ' -->')); -  var linker = this.compile(element); -  return function(iterStartElement){ -    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]; - -    var parentScope = this; -    // 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(); -    this.$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); -          } +var ngRepeatDirective = ['$compile', function($compile) { +  return { +    priority: 1000, +    terminal: true, +    compile: function(element, attr) { +      var expression = attr.ngRepeat; +      attr.$set(attr.$attr.ngRepeat); +      element.replaceWith(jqLite('<!-- ng:repeat: ' + expression + ' -->')); +      var linker = $compile(element); +      return function(scope, iterStartElement, attr){ +        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 + "'.");          } -        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; +        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 { -            // 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; +            array = collection || [];            } -        } else { -          // new item which we don't know about -          childScope = parentScope.$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); -          }); -        } -      } +          // 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(); +            } -      //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(); +            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); +              }); +            }            } -        } -      } -      lastOrder = nextOrder; -    }); +          //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; +        }); +      }; +    }    }; -}); +}];  /** @@ -496,7 +503,7 @@ angularWidget('@ng:repeat', function(expression, element){        </doc:scenario>      </doc:example>   */ -angularWidget("@ng:non-bindable", noop); +var ngNonBindableDirective = valueFn({ terminal: true });  /** @@ -564,49 +571,48 @@ angularWidget("@ng:non-bindable", noop);        </doc:scenario>      </doc:example>   */ -angularWidget('ng:view', function(element) { -  var compiler = this; - -  if (!element[0]['ng:compiled']) { -    element[0]['ng:compiled'] = true; -    return ['$http', '$templateCache', '$route', '$anchorScroll', '$element', -    function($http,   $templateCache,   $route,   $anchorScroll,   element) { -      var template; -      var changeCounter = 0; - -      this.$on('$afterRouteChange', function() { -        changeCounter++; -      }); +var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile', +               function($http,   $templateCache,   $route,   $anchorScroll,   $compile) { +  return { +    compile: function(element, attr) { +      if (!element[0]['ng:compiled']) { +        element[0]['ng:compiled'] = true; + +        return function(scope, element, attrs) { +          var changeCounter = 0; + +          scope.$on('$afterRouteChange', function() { +            changeCounter++; +          }); -      this.$watch(function() {return changeCounter;}, function(newChangeCounter) { -        var template = $route.current && $route.current.template; +          scope.$watch(function() {return changeCounter;}, function(newChangeCounter) { +            var template = $route.current && $route.current.template; -        function clearContent() { -          // ignore callback if another route change occured since -          if (newChangeCounter == changeCounter) { -            element.html(''); -          } -        } +            function clearContent() { +              // ignore callback if another route change occured since +              if (newChangeCounter == changeCounter) { +                element.html(''); +              } +            } -        if (template) { -          $http.get(template, {cache: $templateCache}).success(function(response) { -            // ignore callback if another route change occured since -            if (newChangeCounter == changeCounter) { -              element.html(response); -              compiler.compile(element)($route.current.scope); -              $anchorScroll(); +            if (template) { +              $http.get(template, {cache: $templateCache}).success(function(response) { +                // ignore callback if another route change occured since +                if (newChangeCounter == changeCounter) { +                  element.html(response); +                  $compile(element)($route.current.scope); +                  $anchorScroll(); +                } +              }).error(clearContent); +            } else { +              clearContent();              } -          }).error(clearContent); -        } else { -          clearContent(); -        } -      }); -    }]; -  } else { -    compiler.descend(true); -    compiler.directives(true); -  } -}); +          }); +        }; +      } +    } +  }; +}];  /** @@ -715,81 +721,80 @@ angularWidget('ng:view', function(element) {            <!--- Example with simple pluralization rules for en locale --->            Without Offset: -          <ng:pluralize count="personCount" +          <ng-pluralize count="personCount"                          when="{'0': 'Nobody is viewing.',                                 'one': '1 person is viewing.',                                 'other': '{} people are viewing.'}"> -          </ng:pluralize><br> +          </ng-pluralize><br>            <!--- Example with offset --->            With Offset(2): -          <ng:pluralize count="personCount" 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> +          </ng-pluralize>          </div>        </doc:source>        <doc:scenario>          it('should show correct pluralized string', function() { -          expect(element('.doc-example-live .ng-pluralize:first').text()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                               toBe('1 person is viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          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()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                                 toBe('Nobody is viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          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()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                              toBe('2 people are viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          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()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                              toBe('3 people are viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          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()). +          expect(element('.doc-example-live ng-pluralize:first').text()).                                              toBe('4 people are viewing.'); -          expect(element('.doc-example-live .ng-pluralize:last').text()). +          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()). +          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()). +          expect(element('.doc-example-live ng-pluralize:last').text()).                toBe('Di, Vojta and 2 other people are viewing.');          });        </doc:scenario>      </doc:example>   */ -angularWidget('ng:pluralize', function(element) { -  var numberExp = element.attr('count'), -      whenExp = element.attr('when'), -      offset = element.attr('offset') || 0; - -  return ['$locale', '$element', function($locale, element) { -    var scope = this, +var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { +  var BRACE = /{}/g; +  return function(scope, element, attr) { +    var numberExp = attr.count, +        whenExp = attr.when, +        offset = attr.offset || 0,          whens = scope.$eval(whenExp),          whensExpFns = {};      forEach(whens, function(expression, key) { -      whensExpFns[key] = compileBindTemplate(expression.replace(/{}/g, -                                             '{{' + numberExp + '-' + offset + '}}')); +      whensExpFns[key] = +        $interpolate(expression.replace(BRACE, '{{' + numberExp + '-' + offset + '}}'));      });      scope.$watch(function() { @@ -806,5 +811,5 @@ angularWidget('ng:pluralize', function(element) {      }, function(newVal) {        element.text(newVal);      }); -  }]; -}); +  }; +}]; diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 2d469698..10f5fd2a 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -1,6 +1,12 @@  'use strict';  describe('angular', function() { +  var element; + +  afterEach(function(){ +    dealoc(element); +  }); +    describe('case', function() {      it('should change case', function() {        expect(lowercase('ABC90')).toEqual('abc90'); @@ -382,28 +388,6 @@ describe('angular', function() {    }); -  describe('directive', function() { -    it('should register directives with case-insensitive id', inject(function($compile) { -      angularDirective('ALLCAPS', function(val, el) {el.text('+' + val + '+')}); -      angularDirective('lowercase', function(val, el) {el.text('-' + val + '-')}); - -      var el = jqLite('<div>' + -                        '<span allcaps="xx1"></span>' + -                        '<span ALLcaps="xx2"></span>' + -                        '<span ALLCAPS="xx3"></span>' + -                        '<span lowerCASE="XX4">xx4</span>' + -                      '</div>'); -      $compile(el); -      expect(lowercase(sortedHtml(el))).toBe('<div>' + -                                                '<span allcaps="xx1">+xx1+</span>' + -                                                '<span allcaps="xx2">+xx2+</span>' + -                                                '<span allcaps="xx3">+xx3+</span>' + -                                                '<span lowercase="xx4">-xx4-</span>' + -                                              '</div>'); -    })); -  }); - -    describe('isDate', function() {      it('should return true for Date object', function() {        expect(isDate(new Date())).toBe(true); @@ -420,7 +404,7 @@ describe('angular', function() {    describe('compile', function() {      it('should link to existing node and create scope', inject(function($rootScope, $compile) {        var template = angular.element('<div>{{greeting = "hello world"}}</div>'); -      $compile(template)($rootScope); +      element = $compile(template)($rootScope);        $rootScope.$digest();        expect(template.text()).toEqual('hello world');        expect($rootScope.greeting).toEqual('hello world'); @@ -428,7 +412,7 @@ describe('angular', function() {      it('should link to existing node and given scope', inject(function($rootScope, $compile) {        var template = angular.element('<div>{{greeting = "hello world"}}</div>'); -      $compile(template)($rootScope); +      element = $compile(template)($rootScope);        $rootScope.$digest();        expect(template.text()).toEqual('hello world');      })); @@ -436,15 +420,15 @@ describe('angular', function() {      it('should link to new node and given scope', inject(function($rootScope, $compile) {        var template = jqLite('<div>{{greeting = "hello world"}}</div>'); -      var templateFn = $compile(template); +      var compile = $compile(template);        var templateClone = template.clone(); -      var element = templateFn($rootScope, function(clone){ +      element = compile($rootScope, function(clone){          templateClone = clone;        });        $rootScope.$digest(); -      expect(template.text()).toEqual(''); +      expect(template.text()).toEqual('{{greeting = "hello world"}}');        expect(element.text()).toEqual('hello world');        expect(element).toEqual(templateClone);        expect($rootScope.greeting).toEqual('hello world'); @@ -452,9 +436,9 @@ describe('angular', function() {      it('should link to cloned node and create scope', inject(function($rootScope, $compile) {        var template = jqLite('<div>{{greeting = "hello world"}}</div>'); -      var element = $compile(template)($rootScope, noop); +      element = $compile(template)($rootScope, noop);        $rootScope.$digest(); -      expect(template.text()).toEqual(''); +      expect(template.text()).toEqual('{{greeting = "hello world"}}');        expect(element.text()).toEqual('hello world');        expect($rootScope.greeting).toEqual('hello world');      })); @@ -524,4 +508,11 @@ describe('angular', function() {        expect(startingTag('<ng:abc x="2"><div>text</div></ng:abc>')).toEqual('<ng:abc x="2">');      });    }); + +  describe('snake_case', function(){ +    it('should convert to snake_case', function() { +      expect(snake_case('ABC')).toEqual('a_b_c'); +      expect(snake_case('alanBobCharles')).toEqual('alan_bob_charles'); +    }); +  });  }); diff --git a/test/BinderSpec.js b/test/BinderSpec.js index a94e5eb2..84deca35 100644 --- a/test/BinderSpec.js +++ b/test/BinderSpec.js @@ -2,6 +2,8 @@  describe('Binder', function() { +  var element; +    function childNode(element, index) {      return jqLite(element[0].childNodes[index]);    } @@ -19,9 +21,8 @@ describe('Binder', function() {    });    afterEach(function() { -    if (this.element && this.element.dealoc) { -      this.element.dealoc(); -    } +    dealoc(element); +    dealoc(this.element);    });    it('BindUpdate', inject(function($rootScope, $compile) { @@ -42,60 +43,18 @@ describe('Binder', function() {    }));    it('ApplyTextBindings', inject(function($rootScope, $compile) { -    var element = $compile('<div ng:bind="model.a">x</div>')($rootScope); +    element = $compile('<div ng:bind="model.a">x</div>')($rootScope);      $rootScope.model = {a:123};      $rootScope.$apply();      expect(element.text()).toBe('123');    })); -  it('ReplaceBindingInTextWithSpan preserve surounding text', function() { -    expect(this.compileToHtml('<b>a{{b}}c</b>')).toBe('<b>a<span ng:bind="b"></span>c</b>'); -  }); - -  it('ReplaceBindingInTextWithSpan', function() { -    expect(this.compileToHtml('<b>{{b}}</b>')).toBe('<b><span ng:bind="b"></span></b>'); -  }); - -  it('BindingSpaceConfusesIE', inject(function($rootScope, $compile) { -    if (!msie) return; -    var span = document.createElement('span'); -    span.innerHTML = ' '; -    var nbsp = span.firstChild.nodeValue; -    expect(this.compileToHtml('<b>{{a}} {{b}}</b>')). -        toBe('<b><span ng:bind="a"></span><span>' + nbsp + '</span><span ng:bind="b"></span></b>'); -    dealoc(($rootScope)); -    expect(this.compileToHtml('<b>{{A}} x {{B}} ({{C}})</b>')). -        toBe('<b><span ng:bind="A"></span><span>' + nbsp + 'x </span><span ng:bind="B"></span>' + -             '<span>' + nbsp + '(</span><span ng:bind="C"></span>)</b>'); -  })); - -  it('BindingOfAttributes', inject(function($rootScope, $compile) { -    var element = $compile('<a href="http://s/a{{b}}c" foo="x"></a>')($rootScope); -    var attrbinding = element.attr('ng:bind-attr'); -    var bindings = fromJson(attrbinding); -    expect(decodeURI(bindings.href)).toBe('http://s/a{{b}}c'); -    expect(bindings.foo).toBeFalsy(); -  })); - -  it('MarkMultipleAttributes', inject(function($rootScope, $compile) { -    var element = $compile('<a href="http://s/a{{b}}c" foo="{{d}}"></a>')($rootScope); -    var attrbinding = element.attr('ng:bind-attr'); -    var bindings = fromJson(attrbinding); -    expect(bindings.foo).toBe('{{d}}'); -    expect(decodeURI(bindings.href)).toBe('http://s/a{{b}}c'); -  })); -    it('AttributesNoneBound', inject(function($rootScope, $compile) {      var a = $compile('<a href="abc" foo="def"></a>')($rootScope);      expect(a[0].nodeName).toBe('A');      expect(a.attr('ng:bind-attr')).toBeFalsy();    })); -  it('ExistingAttrbindingIsAppended', inject(function($rootScope, $compile) { -    var a = $compile('<a href="http://s/{{abc}}" ng:bind-attr="{\'b\':\'{{def}}\'}"></a>')($rootScope); -    expect(a.attr('ng:bind-attr')).toBe('{"b":"{{def}}","href":"http://s/{{abc}}"}'); -  })); -    it('AttributesAreEvaluated', inject(function($rootScope, $compile) {      var a = $compile('<a ng:bind-attr=\'{"a":"a", "b":"a+b={{a+b}}"}\'></a>')($rootScope);      $rootScope.$eval('a=1;b=2'); @@ -106,7 +65,7 @@ describe('Binder', function() {    it('InputTypeButtonActionExecutesInScope', inject(function($rootScope, $compile) {      var savedCalled = false; -    var element = $compile( +    element = $compile(        '<input type="button" ng:click="person.save()" value="Apply">')($rootScope);      $rootScope.person = {};      $rootScope.person.save = function() { @@ -117,8 +76,8 @@ describe('Binder', function() {    }));    it('InputTypeButtonActionExecutesInScope2', inject(function($rootScope, $compile) { -    var log = ''; -    var element = $compile('<input type="image" ng:click="action()">')($rootScope); +    var log = ""; +    element = $compile('<input type="image" ng:click="action()">')($rootScope);      $rootScope.action = function() {        log += 'click;';      }; @@ -129,7 +88,7 @@ describe('Binder', function() {    it('ButtonElementActionExecutesInScope', inject(function($rootScope, $compile) {      var savedCalled = false; -    var element = $compile('<button ng:click="person.save()">Apply</button>')($rootScope); +    element = $compile('<button ng:click="person.save()">Apply</button>')($rootScope);      $rootScope.person = {};      $rootScope.person.save = function() {        savedCalled = true; @@ -179,7 +138,7 @@ describe('Binder', function() {    }));    it('RepeaterContentDoesNotBind', inject(function($rootScope, $compile) { -    var element = $compile( +    element = $compile(        '<ul>' +          '<LI ng:repeat="item in model.items"><span ng:bind="item.a"></span></li>' +        '</ul>')($rootScope); @@ -198,8 +157,8 @@ describe('Binder', function() {    });    it('RepeaterAdd', inject(function($rootScope, $compile, $browser) { -    var element = $compile('<div><input type="text" ng:model="item.x" ng:repeat="item in items"></div>')($rootScope); -    $rootScope.items = [{x: 'a'}, {x: 'b'}]; +    element = $compile('<div><input type="text" ng:model="item.x" ng:repeat="item in items"></div>')($rootScope); +    $rootScope.items = [{x:'a'}, {x:'b'}];      $rootScope.$apply();      var first = childNode(element, 1);      var second = childNode(element, 2); @@ -213,7 +172,7 @@ describe('Binder', function() {    }));    it('ItShouldRemoveExtraChildrenWhenIteratingOverHash', inject(function($rootScope, $compile) { -    var element = $compile('<div><div ng:repeat="i in items">{{i}}</div></div>')($rootScope); +    element = $compile('<div><div ng:repeat="i in items">{{i}}</div></div>')($rootScope);      var items = {};      $rootScope.items = items; @@ -234,7 +193,7 @@ describe('Binder', function() {        $exceptionHandlerProvider.mode('log');      });      inject(function($rootScope, $exceptionHandler, $compile) { -      $compile('<div>{{error.throw()}}</div>', null, true)($rootScope); +      element = $compile('<div>{{error.throw()}}</div>', null, true)($rootScope);        var errorLogs = $exceptionHandler.errors;        $rootScope.error = { @@ -277,7 +236,7 @@ describe('Binder', function() {    });    it('NestedRepeater', inject(function($rootScope, $compile) { -    var element = $compile( +    element = $compile(        '<div>' +          '<div ng:repeat="m in model" name="{{m.name}}">' +             '<ul name="{{i}}" ng:repeat="i in m.item"></ul>' + @@ -290,21 +249,21 @@ describe('Binder', function() {      expect(sortedHtml(element)).toBe(          '<div>'+            '<#comment></#comment>'+ -          '<div name="a" ng:bind-attr="{"name":"{{m.name}}"}">'+ +          '<div name="a">'+              '<#comment></#comment>'+ -            '<ul name="a1" ng:bind-attr="{"name":"{{i}}"}"></ul>'+ -            '<ul name="a2" ng:bind-attr="{"name":"{{i}}"}"></ul>'+ +            '<ul name="a1"></ul>'+ +            '<ul name="a2"></ul>'+            '</div>'+ -          '<div name="b" ng:bind-attr="{"name":"{{m.name}}"}">'+ +          '<div name="b">'+              '<#comment></#comment>'+ -            '<ul name="b1" ng:bind-attr="{"name":"{{i}}"}"></ul>'+ -            '<ul name="b2" ng:bind-attr="{"name":"{{i}}"}"></ul>'+ +            '<ul name="b1"></ul>'+ +            '<ul name="b2"></ul>'+            '</div>' +          '</div>');    }));    it('HideBindingExpression', inject(function($rootScope, $compile) { -    var element = $compile('<div ng:hide="hidden == 3"/>')($rootScope); +    element = $compile('<div ng:hide="hidden == 3"/>')($rootScope);      $rootScope.hidden = 3;      $rootScope.$apply(); @@ -318,7 +277,7 @@ describe('Binder', function() {    }));    it('HideBinding', inject(function($rootScope, $compile) { -    var element = $compile('<div ng:hide="hidden"/>')($rootScope); +    element = $compile('<div ng:hide="hidden"/>')($rootScope);      $rootScope.hidden = 'true';      $rootScope.$apply(); @@ -337,7 +296,7 @@ describe('Binder', function() {    }));    it('ShowBinding', inject(function($rootScope, $compile) { -    var element = $compile('<div ng:show="show"/>')($rootScope); +    element = $compile('<div ng:show="show"/>')($rootScope);      $rootScope.show = 'true';      $rootScope.$apply(); @@ -357,7 +316,7 @@ describe('Binder', function() {    it('BindClass', inject(function($rootScope, $compile) { -    var element = $compile('<div ng:class="clazz"/>')($rootScope); +    element = $compile('<div ng:class="clazz"/>')($rootScope);      $rootScope.clazz = 'testClass';      $rootScope.$apply(); @@ -371,7 +330,7 @@ describe('Binder', function() {    }));    it('BindClassEvenOdd', inject(function($rootScope, $compile) { -    var element = $compile( +    element = $compile(        '<div>' +          '<div ng:repeat="i in [0,1]" ng:class-even="\'e\'" ng:class-odd="\'o\'"></div>' +        '</div>')($rootScope); @@ -387,7 +346,7 @@ describe('Binder', function() {    }));    it('BindStyle', inject(function($rootScope, $compile) { -    var element = $compile('<div ng:style="style"/>')($rootScope); +    element = $compile('<div ng:style="style"/>')($rootScope);      $rootScope.$eval('style={height: "10px"}');      $rootScope.$apply(); @@ -413,28 +372,29 @@ describe('Binder', function() {    });    it('ShoulIgnoreVbNonBindable', inject(function($rootScope, $compile) { -    var element = $compile( -      '<div>{{a}}' + -        '<div ng:non-bindable>{{a}}</div>' + -        '<div ng:non-bindable="">{{b}}</div>' + -        '<div ng:non-bindable="true">{{c}}</div>' + -      '</div>')($rootScope); +    element = $compile( +      "<div>{{a}}" + +        "<div ng:non-bindable>{{a}}</div>" + +        "<div ng:non-bindable=''>{{b}}</div>" + +        "<div ng:non-bindable='true'>{{c}}</div>" + +      "</div>")($rootScope);      $rootScope.a = 123;      $rootScope.$apply();      expect(element.text()).toBe('123{{a}}{{b}}{{c}}');    }));    it('ShouldTemplateBindPreElements', inject(function ($rootScope, $compile) { -    var element = $compile('<pre>Hello {{name}}!</pre>')($rootScope); -    $rootScope.name = 'World'; +    element = $compile('<pre>Hello {{name}}!</pre>')($rootScope); +    $rootScope.name = "World";      $rootScope.$apply(); -    expect(      sortedHtml(element)).toBe( -      '<pre ng:bind-template="Hello {{name}}!">Hello World!</pre>'); +    assertEquals( +      '<pre>Hello World!</pre>', +      sortedHtml(element));    }));    it('FillInOptionValueWhenMissing', inject(function($rootScope, $compile) { -    var element = $compile( +    element = $compile(          '<select ng:model="foo">' +            '<option selected="true">{{a}}</option>' +            '<option value="">{{b}}</option>' + @@ -458,7 +418,7 @@ describe('Binder', function() {    }));    it('DeleteAttributeIfEvaluatesFalse', inject(function($rootScope, $compile) { -    var element = $compile( +    element = $compile(        '<div>' +          '<input ng:model="a0" ng:bind-attr="{disabled:\'{{true}}\'}">' +          '<input ng:model="a1" ng:bind-attr="{disabled:\'{{false}}\'}">' + @@ -485,7 +445,7 @@ describe('Binder', function() {        $exceptionHandlerProvider.mode('log');      });      inject(function($rootScope, $exceptionHandler, $log, $compile) { -      var element = $compile( +      element = $compile(          '<div>' +            '<input type="button" ng:click="greeting=\'ABC\'"/>' +            '<input type="button" ng:click=":garbage:"/>' + @@ -505,7 +465,7 @@ describe('Binder', function() {    });    it('ItShouldSelectTheCorrectRadioBox', inject(function($rootScope, $compile) { -    var element = $compile( +    element = $compile(        '<div>' +          '<input type="radio" ng:model="sex" value="female">' +          '<input type="radio" ng:model="sex" value="male">' + @@ -527,7 +487,7 @@ describe('Binder', function() {    }));    it('ItShouldRepeatOnHashes', inject(function($rootScope, $compile) { -    var element = $compile( +    element = $compile(        '<ul>' +          '<li ng:repeat="(k,v) in {a:0,b:1}" ng:bind=\"k + v\"></li>' +        '</ul>')($rootScope); @@ -541,7 +501,7 @@ describe('Binder', function() {    }));    it('ItShouldFireChangeListenersBeforeUpdate', inject(function($rootScope, $compile) { -    var element = $compile('<div ng:bind="name"></div>')($rootScope); +    element = $compile('<div ng:bind="name"></div>')($rootScope);      $rootScope.name = '';      $rootScope.$watch('watched', 'name=123');      $rootScope.watched = 'change'; @@ -551,7 +511,7 @@ describe('Binder', function() {    }));    it('ItShouldHandleMultilineBindings', inject(function($rootScope, $compile) { -    var element = $compile('<div>{{\n 1 \n + \n 2 \n}}</div>')($rootScope); +    element = $compile('<div>{{\n 1 \n + \n 2 \n}}</div>')($rootScope);      $rootScope.$apply();      expect(element.text()).toBe('3');    })); diff --git a/test/ScenarioSpec.js b/test/ScenarioSpec.js index 986e2121..2070d301 100644 --- a/test/ScenarioSpec.js +++ b/test/ScenarioSpec.js @@ -1,23 +1,30 @@  'use strict';  describe("ScenarioSpec: Compilation", function() { +  var element; + +  afterEach(function() { +    dealoc(element); +  }); + +    describe('compilation', function() {      it("should compile dom node and return scope", inject(function($rootScope, $compile) {        var node = jqLite('<div ng:init="a=1">{{b=a+1}}</div>')[0]; -      $compile(node)($rootScope); +      element = $compile(node)($rootScope);        $rootScope.$digest();        expect($rootScope.a).toEqual(1);        expect($rootScope.b).toEqual(2);      }));      it("should compile jQuery node and return scope", inject(function($rootScope, $compile) { -      var element = $compile(jqLite('<div>{{a=123}}</div>'))($rootScope); +      element = $compile(jqLite('<div>{{a=123}}</div>'))($rootScope);        $rootScope.$digest();        expect(jqLite(element).text()).toEqual('123');      }));      it("should compile text node and return scope", inject(function($rootScope, $compile) { -      var element = $compile('<div>{{a=123}}</div>')($rootScope); +      element = $compile('<div>{{a=123}}</div>')($rootScope);        $rootScope.$digest();        expect(jqLite(element).text()).toEqual('123');      })); diff --git a/test/directivesSpec.js b/test/directivesSpec.js index 5bd5f5bd..9dee4860 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -1,6 +1,16 @@  'use strict';  describe("directive", function() { +  var element; + +  beforeEach(function() { +    element = null; +  }); + +  afterEach(function() { +    dealoc(element); +  }); +    var $filterProvider, element; @@ -19,7 +29,7 @@ describe("directive", function() {    describe('ng:bind', function() {      it('should set text', inject(function($rootScope, $compile) { -      var element = $compile('<div ng:bind="a"></div>')($rootScope); +      element = $compile('<div ng:bind="a"></div>')($rootScope);        expect(element.text()).toEqual('');        $rootScope.a = 'misko';        $rootScope.$digest(); @@ -28,47 +38,40 @@ describe("directive", function() {      }));      it('should set text to blank if undefined', inject(function($rootScope, $compile) { -      var element = $compile('<div ng:bind="a"></div>')($rootScope); +      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) { -      var element = $compile('<div ng:bind="html|html"></div>')($rootScope); +      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) { -      var element = $compile('<div ng:bind="html|html:\'unsafe\'"></div>')($rootScope); +      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 set element element', inject(function($rootScope, $compile) { -      $filterProvider.register('myElement', valueFn(function() { -        return jqLite('<a>hello</a>'); -      })); -      var element = $compile('<div ng:bind="0|myElement"></div>')($rootScope); -      $rootScope.$digest(); -      expect(lowercase(element.html())).toEqual('<a>hello</a>'); -    })); - -      it('should suppress rendering of falsy values', inject(function($rootScope, $compile) { -      var element = $compile('<div>{{ null }}{{ undefined }}{{ "" }}-{{ 0 }}{{ false }}</div>')($rootScope); +      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) { -      var element = $compile('<div>{{ {key:"value", $$key:"hide"}  }}</div>')($rootScope); +      element = $compile('<div>{{ {key:"value", $$key:"hide"}  }}</div>')($rootScope);        $rootScope.$digest();        expect(fromJson(element.text())).toEqual({key:'value'});      })); @@ -76,27 +79,15 @@ describe("directive", function() {    describe('ng:bind-template', function() {      it('should ng:bind-template', inject(function($rootScope, $compile) { -      var element = $compile('<div ng:bind-template="Hello {{name}}!"></div>')($rootScope); +      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 have $element set to current bind element', inject(function($rootScope, $compile) { -      var innerText; -      $filterProvider.register('myFilter', valueFn(function(text) { -        innerText = innerText || this.$element.text(); -        return text; -      })); -      var element = $compile('<div>before<span ng:bind-template="{{\'HELLO\'|myFilter}}">INNER</span>after</div>')($rootScope); -      $rootScope.$digest(); -      expect(element.text()).toEqual("beforeHELLOafter"); -      expect(innerText).toEqual('INNER'); -    })); -      it('should render object as JSON ignore $$', inject(function($rootScope, $compile) { -      var element = $compile('<pre>{{ {key:"value", $$key:"hide"}  }}</pre>')($rootScope); +      element = $compile('<pre>{{ {key:"value", $$key:"hide"}  }}</pre>')($rootScope);        $rootScope.$digest();        expect(fromJson(element.text())).toEqual({key:'value'});      })); @@ -105,39 +96,40 @@ describe("directive", function() {    describe('ng:bind-attr', function() {      it('should bind attributes', inject(function($rootScope, $compile) { -      var element = $compile('<div ng:bind-attr="{src:\'http://localhost/mysrc\', alt:\'myalt\'}"/>')($rootScope); +      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) { -      var element = $compile('<img alt="{{ {a:1} }}"/>')($rootScope); +      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) { -    var 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); -  })); +    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) { -      var element = $compile('<div ng:click="clicked = true"></div>')($rootScope); +      element = $compile('<div ng:click="clicked = true"></div>')($rootScope);        $rootScope.$digest();        expect($rootScope.clicked).toBeFalsy(); @@ -146,14 +138,12 @@ describe("directive", function() {      }));      it('should stop event propagation', inject(function($rootScope, $compile) { -      var element = $compile('<div ng:click="outer = true"><div ng:click="inner = true"></div></div>')($rootScope); +      element = $compile('<div ng:click="outer = true"><div ng:click="inner = true"></div></div>')($rootScope);        $rootScope.$digest();        expect($rootScope.outer).not.toBeDefined();        expect($rootScope.inner).not.toBeDefined(); -      var innerDiv = element.children()[0]; - -      browserTrigger(innerDiv, 'click'); +      browserTrigger(element.find('div'), 'click');        expect($rootScope.outer).not.toBeDefined();        expect($rootScope.inner).toEqual(true);      })); @@ -162,7 +152,7 @@ describe("directive", function() {    describe('ng:submit', function() {      it('should get called on form submit', inject(function($rootScope, $compile) { -      var element = $compile('<form action="" ng:submit="submitted = true">' + +      element = $compile('<form action="" ng:submit="submitted = true">' +          '<input type="submit"/>' +          '</form>')($rootScope);        $rootScope.$digest(); @@ -175,7 +165,7 @@ describe("directive", function() {    describe('ng:class', function() {      it('should add new and remove old classes dynamically', inject(function($rootScope, $compile) { -      var element = $compile('<div class="existing" ng:class="dynClass"></div>')($rootScope); +      element = $compile('<div class="existing" ng:class="dynClass"></div>')($rootScope);        $rootScope.dynClass = 'A';        $rootScope.$digest();        expect(element.hasClass('existing')).toBe(true); @@ -196,7 +186,7 @@ describe("directive", function() {      it('should support adding multiple classes via an array', inject(function($rootScope, $compile) { -      var element = $compile('<div class="existing" ng:class="[\'A\', \'B\']"></div>')($rootScope); +      element = $compile('<div class="existing" ng:class="[\'A\', \'B\']"></div>')($rootScope);        $rootScope.$digest();        expect(element.hasClass('existing')).toBeTruthy();        expect(element.hasClass('A')).toBeTruthy(); @@ -227,7 +217,7 @@ describe("directive", function() {      it('should support adding multiple classes via a space delimited string', inject(function($rootScope, $compile) { -      var element = $compile('<div class="existing" ng:class="\'A B\'"></div>')($rootScope); +      element = $compile('<div class="existing" ng:class="\'A B\'"></div>')($rootScope);        $rootScope.$digest();        expect(element.hasClass('existing')).toBeTruthy();        expect(element.hasClass('A')).toBeTruthy(); @@ -236,7 +226,7 @@ describe("directive", function() {      it('should preserve class added post compilation with pre-existing classes', inject(function($rootScope, $compile) { -      var element = $compile('<div class="existing" ng:class="dynClass"></div>')($rootScope); +      element = $compile('<div class="existing" ng:class="dynClass"></div>')($rootScope);        $rootScope.dynClass = 'A';        $rootScope.$digest();        expect(element.hasClass('existing')).toBe(true); @@ -253,7 +243,7 @@ describe("directive", function() {      it('should preserve class added post compilation without pre-existing classes"', inject(function($rootScope, $compile) { -      var element = $compile('<div ng:class="dynClass"></div>')($rootScope); +      element = $compile('<div ng:class="dynClass"></div>')($rootScope);        $rootScope.dynClass = 'A';        $rootScope.$digest();        expect(element.hasClass('A')).toBe(true); @@ -269,119 +259,116 @@ describe("directive", function() {      it('should preserve other classes with similar name"', inject(function($rootScope, $compile) { -      var element = $compile('<div class="ui-panel ui-selected" ng:class="dynCls"></div>')($rootScope); +      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-directive foo'); +      expect(element[0].className).toBe('ui-panel ui-selected foo');      }));      it('should not add duplicate classes', inject(function($rootScope, $compile) { -      var element = $compile('<div class="panel bar" ng:class="dynCls"></div>')($rootScope); +      element = $compile('<div class="panel bar" ng:class="dynCls"></div>')($rootScope);        $rootScope.dynCls = 'panel';        $rootScope.$digest(); -      expect(element[0].className).toBe('panel bar ng-directive'); +      expect(element[0].className).toBe('panel bar');      }));      it('should remove classes even if it was specified via class attribute', inject(function($rootScope, $compile) { -      var element = $compile('<div class="panel bar" ng:class="dynCls"></div>')($rootScope); +      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-directive window'); +      expect(element[0].className).toBe('bar window');      }));      it('should remove classes even if they were added by another code', inject(function($rootScope, $compile) { -      var element = $compile('<div ng:class="dynCls"></div>')($rootScope); +      element = $compile('<div ng:class="dynCls"></div>')($rootScope);        $rootScope.dynCls = 'foo';        $rootScope.$digest();        element.addClass('foo');        $rootScope.dynCls = '';        $rootScope.$digest(); -      expect(element[0].className).toBe('ng-directive');      }));      it('should convert undefined and null values to an empty string', inject(function($rootScope, $compile) { -      var element = $compile('<div ng:class="dynCls"></div>')($rootScope); +      element = $compile('<div ng:class="dynCls"></div>')($rootScope);        $rootScope.dynCls = [undefined, null];        $rootScope.$digest(); -      expect(element[0].className).toBe('ng-directive');      })); -  }); -  it('should ng:class odd/even', inject(function($rootScope, $compile) { -    var 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 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) { -    var 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 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) { -    var 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(); -  })); +    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) { -      var element = $compile('<div ng:style="{height: \'40px\'}"></div>')($rootScope); +      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) { -      var element = $compile('<div ng:style="myStyle"></div>')($rootScope); +      element = $compile('<div ng:style="myStyle"></div>')($rootScope);        $rootScope.$digest();        expect(element.hasClass('ng-exception')).toBeFalsy();      })); @@ -454,8 +441,8 @@ describe("directive", function() {    describe('ng:show', function() {      it('should show and hide an element', inject(function($rootScope, $compile) { -      var element = jqLite('<div ng:show="exp"></div>'); -      var element = $compile(element)($rootScope); +      element = jqLite('<div ng:show="exp"></div>'); +      element = $compile(element)($rootScope);        $rootScope.$digest();        expect(isCssVisible(element)).toEqual(false);        $rootScope.exp = true; @@ -465,8 +452,8 @@ describe("directive", function() {      it('should make hidden element visible', inject(function($rootScope, $compile) { -      var element = jqLite('<div style="display: none" ng:show="exp"></div>'); -      var element = $compile(element)($rootScope); +      element = jqLite('<div style="display: none" ng:show="exp"></div>'); +      element = $compile(element)($rootScope);        expect(isCssVisible(element)).toBe(false);        $rootScope.exp = true;        $rootScope.$digest(); @@ -476,8 +463,8 @@ describe("directive", function() {    describe('ng:hide', function() {      it('should hide an element', inject(function($rootScope, $compile) { -      var element = jqLite('<div ng:hide="exp"></div>'); -      var element = $compile(element)($rootScope); +      element = jqLite('<div ng:hide="exp"></div>'); +      element = $compile(element)($rootScope);        expect(isCssVisible(element)).toBe(true);        $rootScope.exp = true;        $rootScope.$digest(); @@ -552,7 +539,7 @@ describe("directive", function() {    describe('ng:cloak', function() {      it('should get removed when an element is compiled', inject(function($rootScope, $compile) { -      var element = jqLite('<div ng:cloak></div>'); +      element = jqLite('<div ng:cloak></div>');        expect(element.attr('ng:cloak')).toBe('');        $compile(element);        expect(element.attr('ng:cloak')).toBeUndefined(); @@ -560,7 +547,7 @@ describe("directive", function() {      it('should remove ng-cloak class from a compiled element', inject(function($rootScope, $compile) { -      var element = jqLite('<div ng:cloak class="foo ng-cloak bar"></div>'); +      element = jqLite('<div ng:cloak class="foo ng-cloak bar"></div>');        expect(element.hasClass('foo')).toBe(true);        expect(element.hasClass('ng-cloak')).toBe(true); diff --git a/test/markupSpec.js b/test/markupSpec.js index 0dcbbfe9..6f8e518e 100644 --- a/test/markupSpec.js +++ b/test/markupSpec.js @@ -1,33 +1,38 @@  'use strict';  describe("markups", function() { +  var element; + +  afterEach(function() { +    dealoc(element); +  });    it('should translate {{}} in text', inject(function($rootScope, $compile) { -    var element = $compile('<div>hello {{name}}!</div>')($rootScope) -    expect(sortedHtml(element)).toEqual('<div>hello <span ng:bind="name"></span>!</div>'); +    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 <span ng:bind="name">Misko</span>!</div>'); +    expect(sortedHtml(element)).toEqual('<div>hello Misko!</div>');    }));    it('should translate {{}} in terminal nodes', inject(function($rootScope, $compile) { -    var element = $compile('<select ng:model="x"><option value="">Greet {{name}}!</option></select>')($rootScope) +    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 ng:bind-template="Greet {{name}}!">Greet !</option>' + +                '<option>Greet !</option>' +                '</select>');      $rootScope.name = 'Misko';      $rootScope.$digest();      expect(sortedHtml(element).replace(' selected="true"', '')).        toEqual('<select ng:model="x">' + -                '<option ng:bind-template="Greet {{name}}!">Greet Misko!</option>' + +                '<option>Greet Misko!</option>' +                '</select>');    }));    it('should translate {{}} in attributes', inject(function($rootScope, $compile) { -    var element = $compile('<div src="http://server/{{path}}.png"/>')($rootScope) -    expect(element.attr('ng:bind-attr')).toEqual('{"src":"http://server/{{path}}.png"}'); +    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"); @@ -56,36 +61,38 @@ describe("markups", function() {      it('should populate value attribute on OPTION', inject(function($rootScope, $compile) { -      var element = $compile('<select ng:model="x"><option>abc</option></select>')($rootScope) +      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) { -      var element = $compile('<select ng:model="x"><option value="abc">xyz</option></select>')($rootScope) +      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) { -      var element = $compile('<select ng:model="x"><option attr="\ntext\n" \n>\nabc\n</option></select>')($rootScope) +      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; -      var element = $compile('<select ng:model="x"><option>\n</option></select>')($rootScope) +      element = $compile('<select ng:model="x"><option>\n</option></select>')($rootScope)        expect(element).toHaveValue('\n');      }));    });    it('should bind href', inject(function($rootScope, $compile) { -    var element = $compile('<a ng:href="{{url}}"></a>')($rootScope) -    expect(sortedHtml(element)).toEqual('<a ng:bind-attr="{"href":"{{url}}"}"></a>'); +    element = $compile('<a ng:href="{{url}}"></a>')($rootScope) +    $rootScope.url = 'http://server' +    $rootScope.$digest(); +    expect(element.attr('href')).toEqual('http://server');    }));    it('should bind disabled', inject(function($rootScope, $compile) { -    var element = $compile('<button ng:disabled="{{isDisabled}}">Button</button>')($rootScope) +    element = $compile('<button ng:disabled="{{isDisabled}}">Button</button>')($rootScope)      $rootScope.isDisabled = false;      $rootScope.$digest();      expect(element.attr('disabled')).toBeFalsy(); @@ -95,7 +102,7 @@ describe("markups", function() {    }));    it('should bind checked', inject(function($rootScope, $compile) { -    var element = $compile('<input type="checkbox" ng:checked="{{isChecked}}" />')($rootScope) +    element = $compile('<input type="checkbox" ng:checked="{{isChecked}}" />')($rootScope)      $rootScope.isChecked = false;      $rootScope.$digest();      expect(element.attr('checked')).toBeFalsy(); @@ -105,7 +112,7 @@ describe("markups", function() {    }));    it('should bind selected', inject(function($rootScope, $compile) { -    var element = $compile('<select><option value=""></option><option ng:selected="{{isSelected}}">Greetings!</option></select>')($rootScope) +    element = $compile('<select><option value=""></option><option ng:selected="{{isSelected}}">Greetings!</option></select>')($rootScope)      jqLite(document.body).append(element)      $rootScope.isSelected=false;      $rootScope.$digest(); @@ -116,7 +123,7 @@ describe("markups", function() {    }));    it('should bind readonly', inject(function($rootScope, $compile) { -    var element = $compile('<input type="text" ng:readonly="{{isReadonly}}" />')($rootScope) +    element = $compile('<input type="text" ng:readonly="{{isReadonly}}" />')($rootScope)      $rootScope.isReadonly=false;      $rootScope.$digest();      expect(element.attr('readOnly')).toBeFalsy(); @@ -126,7 +133,7 @@ describe("markups", function() {    }));    it('should bind multiple', inject(function($rootScope, $compile) { -    var element = $compile('<select ng:multiple="{{isMultiple}}"></select>')($rootScope) +    element = $compile('<select ng:multiple="{{isMultiple}}"></select>')($rootScope)      $rootScope.isMultiple=false;      $rootScope.$digest();      expect(element.attr('multiple')).toBeFalsy(); @@ -136,38 +143,37 @@ describe("markups", function() {    }));    it('should bind src', inject(function($rootScope, $compile) { -    var element = $compile('<div ng:src="{{url}}" />')($rootScope) +    element = $compile('<div ng:src="{{url}}" />')($rootScope)      $rootScope.url = 'http://localhost/';      $rootScope.$digest();      expect(element.attr('src')).toEqual('http://localhost/');    }));    it('should bind href and merge with other attrs', inject(function($rootScope, $compile) { -    var element = $compile('<a ng:href="{{url}}" rel="{{rel}}"></a>')($rootScope) -    expect(sortedHtml(element)).toEqual('<a ng:bind-attr="{"href":"{{url}}","rel":"{{rel}}"}"></a>'); +    element = $compile('<a ng:href="{{url}}" rel="{{rel}}"></a>')($rootScope); +    $rootScope.url = 'http://server'; +    $rootScope.rel = 'REL'; +    $rootScope.$digest(); +    expect(element.attr('href')).toEqual('http://server'); +    expect(element.attr('rel')).toEqual('REL');    })); -  it('should bind Text with no Bindings', inject(function($compile) { -    var $rootScope; -    function newScope (){ -      return $rootScope = angular.injector(['ng']).get('$rootScope'); -    } +  it('should bind Text with no Bindings', inject(function($compile, $rootScope) {      forEach(['checked', 'disabled', 'multiple', 'readonly', 'selected'], function(name) { -      var element = $compile('<div ng:' + name + '="some"></div>')(newScope()) -      expect(element.attr('ng:bind-attr')).toBe('{"' + name +'":"some"}'); +      element = $compile('<div ng:' + name + '="some"></div>')($rootScope)        $rootScope.$digest();        expect(element.attr(name)).toBe(name);        dealoc(element);      }); -    var element = $compile('<div ng:src="some"></div>')(newScope()) +    element = $compile('<div ng:src="some"></div>')($rootScope)      $rootScope.$digest(); -    expect(sortedHtml(element)).toEqual('<div ng:bind-attr="{"src":"some"}" src="some"></div>'); +    expect(element.attr('src')).toEqual('some');      dealoc(element); -    var element = $compile('<div ng:href="some"></div>')(newScope()) +    element = $compile('<div ng:href="some"></div>')($rootScope)      $rootScope.$digest(); -    expect(sortedHtml(element)).toEqual('<div href="some" ng:bind-attr="{"href":"some"}"></div>'); +    expect(element.attr('href')).toEqual('some');      dealoc(element);    }));  }); diff --git a/test/sanitizerSpec.js b/test/sanitizerSpec.js index 7467a833..a33d8992 100644 --- a/test/sanitizerSpec.js +++ b/test/sanitizerSpec.js @@ -2,9 +2,13 @@  describe('HTML', function() { -  function expectHTML(html) { -    return expect(new HTML(html).get()); -  } +  var expectHTML; + +  beforeEach(inject(function($sanitize) { +    expectHTML = function(html){ +      return expect($sanitize(html)); +    }; +  }));    describe('htmlParser', function() {      var handler, start, text; diff --git a/test/scenario/dslSpec.js b/test/scenario/dslSpec.js index 79d479bb..c757d8a4 100644 --- a/test/scenario/dslSpec.js +++ b/test/scenario/dslSpec.js @@ -1,8 +1,13 @@  'use strict';  describe("angular.scenario.dsl", function() { +  var element;    var $window, $root; -  var application, eventLog; +  var eventLog; + +  afterEach(function() { +    dealoc(element); +  });    beforeEach(inject(function($injector) {      eventLog = []; @@ -393,28 +398,26 @@ describe("angular.scenario.dsl", function() {      describe('Repeater', function() {        var chain; -      beforeEach(function() { -        doc.append( -          '<ul>' + -          '  <li><span ng:bind="name" class="ng-binding">misko</span>' + -          '    <span ng:bind="test && gender" class="ng-binding">male</span></li>' + -          '  <li><span ng:bind="name" class="ng-binding">felisa</span>' + -          '    <span ng:bind="gender | uppercase" class="ng-binding">female</span></li>' + -          '</ul>' -        ); +      beforeEach(inject(function($compile, $rootScope) { +        element = $compile( +          '<ul><li ng-repeat="i in items">{{i.name}}  {{i.gender}}</li></ul>')($rootScope); +        $rootScope.items = [{name:'misko', gender:'male'}, {name:'felisa', gender:'female'}]; +        $rootScope.$apply(); +        doc.append(element);          chain = $root.dsl.repeater('ul li'); -      }); +      }));        it('should get the row count', function() {          chain.count();          expect($root.futureResult).toEqual(2);        }); -      it('should return 0 if repeater doesnt match', function() { -        doc.find('ul').html(''); +      it('should return 0 if repeater doesnt match', inject(function($rootScope) { +        $rootScope.items = []; +        $rootScope.$apply();          chain.count();          expect($root.futureResult).toEqual(0); -      }); +      }));        it('should get a row of bindings', function() {          chain.row(1); @@ -422,7 +425,7 @@ describe("angular.scenario.dsl", function() {        });        it('should get a column of bindings', function() { -        chain.column('gender'); +        chain.column('i.gender');          expect($root.futureResult).toEqual(['male', 'female']);        }); @@ -437,45 +440,60 @@ describe("angular.scenario.dsl", function() {      });      describe('Binding', function() { -      it('should select binding by name', function() { -        doc.append('<span class="ng-binding" ng:bind="foo.bar">some value</span>'); +      var compile; + +      beforeEach(inject(function($compile, $rootScope) { +        compile = function(html, value) { +          element = $compile(html)($rootScope); +          doc.append(element); +          $rootScope.foo = {bar: value || 'some value'}; +          $rootScope.$apply(); +        }; +      })); + + +      it('should select binding in interpolation', function() { +        compile('<span>{{ foo.bar }}</span>');          $root.dsl.binding('foo.bar');          expect($root.futureResult).toEqual('some value');        }); -      it('should select binding by regexp', function() { -        doc.append('<span class="ng-binding" ng:bind="foo.bar">some value</span>'); -        $root.dsl.binding(/^foo\..+/); +      it('should select binding in multiple interpolations', function() { +        compile('<span>{{ foo.bar }}<hr/> {{ true }}</span>'); +        $root.dsl.binding('foo.bar');          expect($root.futureResult).toEqual('some value'); + +        $root.dsl.binding('true'); +        expect($root.futureResult).toEqual('true');        }); -      it('should return value for input elements', function() { -        doc.append('<input type="text" class="ng-binding" ng:bind="foo.bar" value="some value"/>'); +      it('should select binding by name', function() { +        compile('<span ng:bind=" foo.bar "></span>');          $root.dsl.binding('foo.bar');          expect($root.futureResult).toEqual('some value');        }); -      it('should return value for textarea elements', function() { -        doc.append('<textarea class="ng-binding" ng:bind="foo.bar">some value</textarea>'); -        $root.dsl.binding('foo.bar'); +      it('should select binding by regexp', function() { +        compile('<span ng:bind="foo.bar">some value</span>'); +        $root.dsl.binding(/^foo\..+/);          expect($root.futureResult).toEqual('some value');        });        it('should return innerHTML for all the other elements', function() { -        doc.append('<div class="ng-binding" ng:bind="foo.bar">some <b>value</b></div>'); +        compile('<div ng-bind-html="foo.bar"></div>', 'some <b>value</b>');          $root.dsl.binding('foo.bar');          expect($root.futureResult.toLowerCase()).toEqual('some <b>value</b>');        });        it('should select binding in template by name', function() { -        doc.append('<pre class="ng-binding" ng:bind-template="foo {{bar}} baz">foo some baz</pre>'); -        $root.dsl.binding('bar'); -        expect($root.futureResult).toEqual('foo some baz'); +        compile('<pre ng:bind-template="foo {{foo.bar}} baz"></pre>', 'bar'); +        $root.dsl.binding('foo.bar'); +        expect($root.futureResult).toEqual('bar');        });        it('should match bindings by substring match', function() { -        doc.append('<pre class="ng-binding" ng:bind="foo.bar() && test.baz() | filter">binding value</pre>'); -        $root.dsl.binding('test.baz'); +        compile('<pre ng:bind="foo.bar | filter"></pre>', 'binding value'); +        $root.dsl.binding('foo . bar');          expect($root.futureResult).toEqual('binding value');        }); @@ -485,7 +503,7 @@ describe("angular.scenario.dsl", function() {        });        it('should return error if no binding matches', function() { -        doc.append('<span class="ng-binding" ng:bind="foo">some value</span>'); +        compile('<span ng:bind="foo">some value</span>');          $root.dsl.binding('foo.bar');          expect($root.futureError).toMatch(/did not match/);        }); diff --git a/test/service/filter/filtersSpec.js b/test/service/filter/filtersSpec.js index cc006447..98651c58 100644 --- a/test/service/filter/filtersSpec.js +++ b/test/service/filter/filtersSpec.js @@ -153,14 +153,6 @@ describe('filters', function() {      });    }); -  describe('html', function() { -    it('should do basic filter', function() { -      var html = filter('html')("a<b>c</b>d"); -      expect(html instanceof HTML).toBeTruthy(); -      expect(html.html).toEqual("a<b>c</b>d"); -    }); -  }); -    describe('linky', function() {      var linky; @@ -169,7 +161,7 @@ describe('filters', function() {      }));      it('should do basic filter', function() { -      expect(linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c").html). +      expect(linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c")).          toEqual('<a href="http://ab/">http://ab/</a> ' +                  '(<a href="http://a/">http://a/</a>) ' +                  '<<a href="http://a/">http://a/</a>> ' + @@ -178,11 +170,11 @@ describe('filters', function() {      });      it('should handle mailto:', function() { -      expect(linky("mailto:me@example.com").html). +      expect(linky("mailto:me@example.com")).                        toEqual('<a href="mailto:me@example.com">me@example.com</a>'); -      expect(linky("me@example.com").html). +      expect(linky("me@example.com")).                        toEqual('<a href="mailto:me@example.com">me@example.com</a>'); -      expect(linky("send email to me@example.com, but").html). +      expect(linky("send email to me@example.com, but")).          toEqual('send email to <a href="mailto:me@example.com">me@example.com</a>, but');      });    }); diff --git a/test/service/logSpec.js b/test/service/logSpec.js index 98158ae0..df2bc933 100644 --- a/test/service/logSpec.js +++ b/test/service/logSpec.js @@ -69,7 +69,7 @@ describe('$log', function() {        e.stack = undefined;        $log = new $LogProvider().$get[1]({console:{error:function() { -        errorArgs = arguments; +        errorArgs = [].slice.call(arguments, 0);        }}});      }); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index 85c844cb..1b4f11ba 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -61,9 +61,19 @@ afterEach(function() {  function dealoc(obj) {    if (obj) { -    var element = obj.$element || obj || {}; -    if (element.nodeName) element = jqLite(element); -    if (element.dealoc) element.dealoc(); +    if (isElement(obj)) { +      var element = obj; +      if (element.nodeName) element = jqLite(element); +      if (element.dealoc) element.dealoc(); +    } else { +      for(var key in jqCache) { +        var value = jqCache[key]; +        if (value.$scope == obj) { +          delete jqCache[key]; +        } +      } +    } +    }  } diff --git a/test/widget/inputSpec.js b/test/widget/inputSpec.js index 3e073d95..3b3aa282 100644 --- a/test/widget/inputSpec.js +++ b/test/widget/inputSpec.js @@ -51,7 +51,6 @@ describe('widget: input', function() {      it('should bind update scope from model', function() {        createInput(); -      expect(scope.form.name.$required).toBe(false);        scope.name = 'misko';        scope.$digest();        expect(inputElement.val()).toEqual('misko'); @@ -60,7 +59,6 @@ describe('widget: input', function() {      it('should require', function() {        createInput({required:''}); -      expect(scope.form.name.$required).toBe(true);        scope.$digest();        expect(scope.form.name.$valid).toBe(false);        scope.name = 'misko'; @@ -159,7 +157,7 @@ describe('widget: input', function() {                  '</div>');          scope.obj = { abc: { name: 'Misko'} };          scope.$digest(); -        expect(scope.$element.find('input').val()).toEqual('Misko'); +        expect(element.find('input').val()).toEqual('Misko');        }); @@ -181,7 +179,7 @@ describe('widget: input', function() {          it("should render as blank if null", function() {            compile('<input type="text" ng:model="age" ng:format="number" ng:init="age=null"/>');            expect(scope.age).toBeNull(); -          expect(scope.$element[0].value).toEqual(''); +          expect(element[0].value).toEqual('');          }); @@ -189,19 +187,19 @@ describe('widget: input', function() {            compile('<input type="number" ng:model="age"/>');            scope.age = 123;            scope.$digest(); -          expect(scope.$element.val()).toEqual('123'); +          expect(element.val()).toEqual('123');            try {              // to allow non-number values, we have to change type so that              // the browser which have number validation will not interfere with              // this test. IE8 won't allow it hence the catch. -            scope.$element[0].setAttribute('type', 'text'); +            element[0].setAttribute('type', 'text');            } catch (e){} -          scope.$element.val('123X'); -          browserTrigger(scope.$element, 'change'); +          element.val('123X'); +          browserTrigger(element, 'change');            defer.flush(); -          expect(scope.$element.val()).toEqual('123X'); +          expect(element.val()).toEqual('123X');            expect(scope.age).toEqual(123); -          expect(scope.$element).toBeInvalid(); +          expect(element).toBeInvalid();          }); @@ -211,28 +209,28 @@ describe('widget: input', function() {            // the user from ever typying ','.            compile('<input type="list" ng:model="list"/>'); -          scope.$element.val('a '); -          browserTrigger(scope.$element, 'change'); +          element.val('a '); +          browserTrigger(element, 'change');            defer.flush(); -          expect(scope.$element.val()).toEqual('a '); +          expect(element.val()).toEqual('a ');            expect(scope.list).toEqual(['a']); -          scope.$element.val('a ,'); -          browserTrigger(scope.$element, 'change'); +          element.val('a ,'); +          browserTrigger(element, 'change');            defer.flush(); -          expect(scope.$element.val()).toEqual('a ,'); +          expect(element.val()).toEqual('a ,');            expect(scope.list).toEqual(['a']); -          scope.$element.val('a , '); -          browserTrigger(scope.$element, 'change'); +          element.val('a , '); +          browserTrigger(element, 'change');            defer.flush(); -          expect(scope.$element.val()).toEqual('a , '); +          expect(element.val()).toEqual('a , ');            expect(scope.list).toEqual(['a']); -          scope.$element.val('a , b'); -          browserTrigger(scope.$element, 'change'); +          element.val('a , b'); +          browserTrigger(element, 'change');            defer.flush(); -          expect(scope.$element.val()).toEqual('a , b'); +          expect(element.val()).toEqual('a , b');            expect(scope.list).toEqual(['a', 'b']);          }); @@ -240,7 +238,7 @@ describe('widget: input', function() {          it("should come up blank when no value specified", function() {            compile('<input type="number" ng:model="age"/>');            scope.$digest(); -          expect(scope.$element.val()).toEqual(''); +          expect(element.val()).toEqual('');            expect(scope.age).toEqual(null);          });        }); @@ -250,7 +248,7 @@ describe('widget: input', function() {          it("should format booleans", function() {            compile('<input type="checkbox" ng:model="name" ng:init="name=false"/>');            expect(scope.name).toBe(false); -          expect(scope.$element[0].checked).toBe(false); +          expect(element[0].checked).toBe(false);          }); @@ -270,15 +268,15 @@ describe('widget: input', function() {            scope.name='y';            scope.$digest(); -          expect(scope.$element[0].checked).toBe(true); +          expect(element[0].checked).toBe(true);            scope.name='n';            scope.$digest(); -          expect(scope.$element[0].checked).toBe(false); +          expect(element[0].checked).toBe(false);            scope.name='abc';            scope.$digest(); -          expect(scope.$element[0].checked).toBe(false); +          expect(element[0].checked).toBe(false);            browserTrigger(element);            expect(scope.name).toEqual('y'); @@ -302,7 +300,6 @@ describe('widget: input', function() {      it("should process required", inject(function($formFactory) {        compile('<input type="text" ng:model="price" name="p" required/>', jqLite(document.body)); -      expect($formFactory.rootForm.p.$required).toBe(true);        expect(element.hasClass('ng-invalid')).toBeTruthy();        scope.price = 'xxx'; @@ -394,7 +391,7 @@ describe('widget: input', function() {          '</div>');          expect(scope.choose).toEqual('C'); -        var inputs = scope.$element.find('input'); +        var inputs = element.find('input');          expect(inputs[1].checked).toBe(false);          expect(inputs[2].checked).toBe(true);        }); @@ -408,7 +405,7 @@ describe('widget: input', function() {          '</div>');          expect(scope.choose).toEqual('A'); -        var inputs = scope.$element.find('input'); +        var inputs = element.find('input');          expect(inputs[0].checked).toBe(true);          expect(inputs[1].checked).toBe(false);        }); @@ -421,7 +418,7 @@ describe('widget: input', function() {                '       type="radio" ng:model="choice" value="{{item}}" name="choice">'+              '</li>'); -        var inputs = scope.$element.find('input'); +        var inputs = element.find('input');          expect(inputs[0].checked).toBe(false);          expect(inputs[1].checked).toBe(false); @@ -435,7 +432,7 @@ describe('widget: input', function() {            function($rootScope, $compile){          $rootScope.choice = 'b';          $rootScope.items = ['a', 'b']; -        var element = $compile( +        element = $compile(              '<li>'+                '<input ng:repeat="item in items" ' +                '       type="radio" ng:model="choice" value="{{item}}" name="choice">'+ @@ -465,21 +462,22 @@ describe('widget: input', function() {          $rootScope.value = undefined;          $rootScope.$digest();          expect(element.val()).toEqual(''); +        dealoc(element);        }));      });      it('should ignore text widget which have no name', function() {        compile('<input type="text"/>'); -      expect(scope.$element.attr('ng-exception')).toBeFalsy(); -      expect(scope.$element.hasClass('ng-exception')).toBeFalsy(); +      expect(element.attr('ng-exception')).toBeFalsy(); +      expect(element.hasClass('ng-exception')).toBeFalsy();      });      it('should ignore checkbox widget which have no name', function() {        compile('<input type="checkbox"/>'); -      expect(scope.$element.attr('ng-exception')).toBeFalsy(); -      expect(scope.$element.hasClass('ng-exception')).toBeFalsy(); +      expect(element.attr('ng-exception')).toBeFalsy(); +      expect(element.hasClass('ng-exception')).toBeFalsy();      }); @@ -506,6 +504,7 @@ describe('widget: input', function() {        expect(formFactory).toBe($formFactory);        expect(input[0]).toBe(element[0]); +      dealoc(element);      }));      it('should throw an error of Controller not declared in scope', inject(function($rootScope, $compile) { @@ -530,13 +529,13 @@ describe('widget: input', function() {          forEach(validList, function(value){            it('should validate "' + value + '"', function() {              setup(value); -            expect(scope.$element).toBeValid(); +            expect(element).toBeValid();            });          });          forEach(invalidList, function(value){            it('should NOT validate "' + value + '"', function() {              setup(value); -            expect(scope.$element).toBeInvalid(); +            expect(element).toBeInvalid();            });          }); @@ -553,10 +552,10 @@ describe('widget: input', function() {              // to allow non-number values, we have to change type so that              // the browser which have number validation will not interfere with              // this test. IE8 won't allow it hence the catch. -            scope.$element[0].setAttribute('type', 'text'); +            element[0].setAttribute('type', 'text');            } catch (e){}            if (value != undefined) { -            scope.$element.val(value); +            element.val(value);              browserTrigger(element, 'keydown');              defer.flush();            } diff --git a/test/widget/selectSpec.js b/test/widget/selectSpec.js index 5d92e674..7206ade8 100644 --- a/test/widget/selectSpec.js +++ b/test/widget/selectSpec.js @@ -13,7 +13,7 @@ describe('select', function() {        } else {          element = jqLite(html);        } -      $compile(element)($rootScope); +      element = $compile(element)($rootScope);        scope.$apply();        return scope;      }; @@ -37,7 +37,7 @@ describe('select', function() {        scope.b = 'bar';        scope.$digest(); -      expect(scope.$element.text()).toBe('foobarC'); +      expect(element.text()).toBe('foobarC');      });      it('should require', inject(function($formFactory) { diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 88d9e1b8..6e115a36 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -1,9 +1,15 @@  'use strict';  describe('widget', function() { -  describe('ng:switch', function() { +  var element; + +  afterEach(function(){ +    dealoc(element); +  }); + +  describe('ng:switch', inject(function($rootScope, $compile) {      it('should switch on value change', inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ng:switch on="select">' +            '<div ng:switch-when="1">first:{{name}}</div>' +            '<div ng:switch-when="2">second:{{name}}</div>' + @@ -29,7 +35,7 @@ describe('widget', function() {      it('should switch on switch-when-default', inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ng:switch on="select">' +            '<div ng:switch-when="1">one</div>' +            '<div ng:switch-default>other</div>' + @@ -43,7 +49,7 @@ describe('widget', function() {      it('should call change on switch', inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ng:switch on="url" change="name=\'works\'">' +            '<div ng:switch-when="a">{{name}}</div>' +          '</ng:switch>')($rootScope); @@ -52,7 +58,7 @@ describe('widget', function() {        expect($rootScope.name).toEqual(undefined);        expect(element.text()).toEqual('works');      })); -  }); +  }));    describe('ng:include', function() { @@ -66,7 +72,7 @@ describe('widget', function() {      it('should include on external file', inject(putIntoCache('myUrl', '{{name}}'),          function($rootScope, $compile, $browser) { -      var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>'); +      element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');        element = $compile(element)($rootScope);        $rootScope.childScope = $rootScope.$new();        $rootScope.childScope.name = 'misko'; @@ -79,7 +85,7 @@ describe('widget', function() {      it('should remove previously included text if a falsy value is bound to src', inject(            putIntoCache('myUrl', '{{name}}'),            function($rootScope, $compile, $browser) { -      var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>'); +      element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');        element = $compile(element)($rootScope);        $rootScope.childScope = $rootScope.$new();        $rootScope.childScope.name = 'igor'; @@ -97,7 +103,7 @@ describe('widget', function() {      it('should allow this for scope', inject(putIntoCache('myUrl', '{{"abc"}}'),            function($rootScope, $compile, $browser) { -      var element = jqLite('<ng:include src="url" scope="this"></ng:include>'); +      element = jqLite('<ng:include src="url" scope="this"></ng:include>');        element = $compile(element)($rootScope);        $rootScope.url = 'myUrl';        $rootScope.$digest(); @@ -115,7 +121,7 @@ describe('widget', function() {      it('should evaluate onload expression when a partial is loaded', inject(          putIntoCache('myUrl', 'my partial'),          function($rootScope, $compile, $browser) { -      var element = jqLite('<ng:include src="url" onload="loaded = true"></ng:include>'); +      element = jqLite('<ng:include src="url" onload="loaded = true"></ng:include>');        element = $compile(element)($rootScope);        expect($rootScope.loaded).not.toBeDefined(); @@ -130,7 +136,7 @@ describe('widget', function() {      it('should destroy old scope', inject(putIntoCache('myUrl', 'my partial'),            function($rootScope, $compile, $browser) { -      var element = jqLite('<ng:include src="url"></ng:include>'); +      element = jqLite('<ng:include src="url"></ng:include>');        element = $compile(element)($rootScope);        expect($rootScope.$$childHead).toBeFalsy(); @@ -147,7 +153,7 @@ describe('widget', function() {      it('should do xhr request and cache it',          inject(function($rootScope, $httpBackend, $compile, $browser) { -      var element = $compile('<ng:include src="url"></ng:include>')($rootScope); +      element = $compile('<ng:include src="url"></ng:include>')($rootScope);        $httpBackend.expect('GET', 'myUrl').respond('my partial');        $rootScope.url = 'myUrl'; @@ -168,7 +174,7 @@ describe('widget', function() {      it('should clear content when error during xhr request',          inject(function($httpBackend, $compile, $rootScope) { -      var element = $compile('<ng:include src="url">content</ng:include>')($rootScope); +      element = $compile('<ng:include src="url">content</ng:include>')($rootScope);        $httpBackend.expect('GET', 'myUrl').respond(404, '');        $rootScope.url = 'myUrl'; @@ -182,7 +188,7 @@ describe('widget', function() {      it('should be async even if served from cache', inject(            putIntoCache('myUrl', 'my partial'),            function($rootScope, $compile, $browser) { -      var element = $compile('<ng:include src="url"></ng:include>')($rootScope); +      element = $compile('<ng:include src="url"></ng:include>')($rootScope);        $rootScope.url = 'myUrl'; @@ -199,8 +205,8 @@ describe('widget', function() {      it('should discard pending xhr callbacks if a new template is requested before the current ' +          'finished loading', inject(function($rootScope, $compile, $httpBackend) { -      var element = jqLite("<ng:include src='templateUrl'></ng:include>"), -          log = []; +      element = jqLite("<ng:include src='templateUrl'></ng:include>"); +      var log = [];        $rootScope.templateUrl = 'myUrl1';        $rootScope.logger = function(msg) { @@ -234,7 +240,7 @@ describe('widget', function() {        function compileAndLink(tpl) {          return function($compile, $rootScope) { -          $compile(tpl)($rootScope); +          element = $compile(tpl)($rootScope);          };        } @@ -294,7 +300,7 @@ describe('widget', function() {            preventDefaultCalled = false,            event; -      var element = $compile('<a href="">empty link</a>')($rootScope); +      element = $compile('<a href="">empty link</a>')($rootScope);        if (msie < 9) { @@ -327,7 +333,7 @@ describe('widget', function() {    describe('@ng:repeat', function() {      it('should ng:repeat over array', inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ul>' +            '<li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li>' +          '</ul>')($rootScope); @@ -355,7 +361,7 @@ describe('widget', function() {      it('should ng:repeat over object', inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ul>' +            '<li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li>' +          '</ul>')($rootScope); @@ -370,7 +376,7 @@ describe('widget', function() {        Class.prototype.abc = function() {};        Class.prototype.value = 'abc'; -      var element = $compile( +      element = $compile(          '<ul>' +            '<li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li>' +          '</ul>')($rootScope); @@ -383,7 +389,7 @@ describe('widget', function() {      it('should error on wrong parsing of ng:repeat', inject(function($rootScope, $compile, $log) {        expect(function() { -        var element = $compile('<ul><li ng:repeat="i dont parse"></li></ul>')($rootScope); +        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(); @@ -392,7 +398,7 @@ describe('widget', function() {      it('should expose iterator offset as $index when iterating over arrays',          inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ul>' +            '<li ng:repeat="item in items" ng:bind="item + $index + \'|\'"></li>' +          '</ul>')($rootScope); @@ -404,7 +410,7 @@ describe('widget', function() {      it('should expose iterator offset as $index when iterating over objects',          inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ul>' +            '<li ng:repeat="(key, val) in items" ng:bind="key + \':\' + val + $index + \'|\'"></li>' +          '</ul>')($rootScope); @@ -416,7 +422,7 @@ describe('widget', function() {      it('should expose iterator position as $position when iterating over arrays',          inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ul>' +            '<li ng:repeat="item in items" ng:bind="item + \':\' + $position + \'|\'"></li>' +          '</ul>')($rootScope); @@ -437,7 +443,7 @@ describe('widget', function() {      it('should expose iterator position as $position when iterating over objects',          inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ul>' +            '<li ng:repeat="(key, val) in items" ng:bind="key + \':\' + val + \':\' + $position + \'|\'">' +            '</li>' + @@ -454,7 +460,7 @@ describe('widget', function() {      it('should ignore $ and $$ properties', inject(function($rootScope, $compile) { -      var element = $compile('<ul><li ng:repeat="i in items">{{i}}|</li></ul>')($rootScope); +      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'; @@ -465,7 +471,7 @@ describe('widget', function() {      it('should repeat over nested arrays', inject(function($rootScope, $compile) { -      var element = $compile( +      element = $compile(          '<ul>' +            '<li ng:repeat="subgroup in groups">' +              '<div ng:repeat="group in subgroup">{{group}}|</div>X' + @@ -480,7 +486,7 @@ describe('widget', function() {      it('should ignore non-array element properties when iterating over an array',          inject(function($rootScope, $compile) { -      var element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); +      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() {}; @@ -492,7 +498,7 @@ describe('widget', function() {      it('should iterate over non-existent elements of a sparse array',          inject(function($rootScope, $compile) { -      var element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); +      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'; @@ -503,7 +509,7 @@ describe('widget', function() {      it('should iterate over all kinds of types', inject(function($rootScope, $compile) { -      var element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope); +      element = $compile('<ul><li ng:repeat="item in array">{{item}}|</li></ul>')($rootScope);        $rootScope.array = ['a', 1, null, undefined, {}];        $rootScope.$digest(); @@ -512,7 +518,7 @@ describe('widget', function() {      describe('stability', function() { -      var a, b, c, d, lis, element; +      var a, b, c, d, lis;        beforeEach(inject(function($rootScope, $compile) {          element = $compile( @@ -602,7 +608,7 @@ describe('widget', function() {    describe('@ng:non-bindable', function() {      it('should prevent compilation of the owning element and its children',          inject(function($rootScope, $compile) { -      var element = $compile('<div ng:non-bindable><span ng:bind="name"></span></div>')($rootScope); +      element = $compile('<div ng:non-bindable><span ng:bind="name"></span></div>')($rootScope);        $rootScope.name =  'misko';        $rootScope.$digest();        expect(element.text()).toEqual(''); @@ -611,7 +617,6 @@ describe('widget', function() {    describe('ng:view', function() { -    var element;      beforeEach(inject(function($rootScope, $compile) {        element = $compile('<ng:view></ng:view>')($rootScope);      })); @@ -658,7 +663,7 @@ describe('widget', function() {        $location.path('/unknown');        $rootScope.$digest(); -      expect($rootScope.$element.text()).toEqual(''); +      expect(element.text()).toEqual('');      })); @@ -675,12 +680,13 @@ describe('widget', function() {        $rootScope.parentVar = 'new parent';        $rootScope.$digest(); -      expect($rootScope.$element.text()).toEqual('new parent'); +      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']);        var myApp = injector.get('$rootScope');        var $httpBackend = injector.get('$httpBackend'); @@ -690,7 +696,7 @@ describe('widget', function() {        var $route = injector.get('$route');        $route.when('/foo', {controller: angular.noop, template: 'viewPartial.html'}); -      var element = injector.get('$compile')( +      element = injector.get('$compile')(            '<div>' +              'include: <ng:include src="\'includePartial.html\'"> </ng:include>' +            '</div>')(myApp); @@ -699,15 +705,16 @@ describe('widget', function() {        $httpBackend.expect('GET', 'viewPartial.html').respond('content');        $httpBackend.flush(); -      expect(myApp.$element.text()).toEqual('include: view: content'); +      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', -       inject(function($rootScope, $compile, $location, $httpBackend, $route, $browser) { +       inject(function($rootScope, $compile, $location, $httpBackend, $route) {        //this is a test for a regression that was introduced by making the ng:view cache sync        $route.when('/foo', {controller: ParentCtrl, template: 'viewPartial.html'}); @@ -750,7 +757,7 @@ describe('widget', function() {        $route.when('/foo', {template: 'myUrl1'});        $route.when('/bar', {template: 'myUrl2'}); -      expect($rootScope.$element.text()).toEqual(''); +      expect(element.text()).toEqual('');        $location.path('/foo');        $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); @@ -760,7 +767,7 @@ describe('widget', function() {        $rootScope.$digest();        $httpBackend.flush(); // now that we have two requests pending, flush! -      expect($rootScope.$element.text()).toEqual('2'); +      expect(element.text()).toEqual('2');      })); @@ -770,12 +777,12 @@ describe('widget', function() {        $location.path('/foo');        $httpBackend.expect('GET', 'myUrl1').respond(404, ''); -      $rootScope.$element.text('content'); +      element.text('content');        $rootScope.$digest();        $httpBackend.flush(); -      expect($rootScope.$element.text()).toBe(''); +      expect(element.text()).toBe('');      })); @@ -800,7 +807,6 @@ describe('widget', function() {    describe('ng:pluralize', function() {      describe('deal with pluralized strings without offset', function() { -       var element;         beforeEach(inject(function($rootScope, $compile) {            element = $compile(              '<ng:pluralize count="email"' + @@ -892,7 +898,7 @@ describe('widget', function() {      describe('deal with pluralized strings with offset', function() {        it('should show single/plural strings with offset', inject(function($rootScope, $compile) { -        var element = $compile( +        element = $compile(            "<ng:pluralize count=\"viewCount\"  offset=2 " +                "when=\"{'0': 'Nobody is viewing.'," +                        "'1': '{{p1}} is viewing.'," + | 
