From 9ee2cdff44e7d496774b340de816344126c457b3 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Tue, 22 Nov 2011 21:28:39 -0800 Subject: refactor(directives): connect new compiler - turn everything into a directive --- css/angular.css | 3 +- docs/content/api/angular.inputType.ngdoc | 2 +- docs/content/guide/dev_guide.expressions.ngdoc | 16 +- docs/content/guide/dev_guide.forms.ngdoc | 49 +- docs/src/templates/doc_widgets.js | 381 ++++++++-------- docs/src/templates/docs.js | 11 +- src/Angular.js | 13 +- src/AngularPublic.js | 45 +- src/Injector.js | 11 +- src/directives.js | 308 ++++++------- src/jqLite.js | 11 +- src/markups.js | 128 +----- src/scenario/Scenario.js | 76 +++- src/scenario/dsl.js | 6 +- src/service/compiler.js | 6 +- src/service/filter.js | 1 - src/service/filter/filter.js | 6 +- src/service/filter/filters.js | 118 +---- src/service/filter/orderBy.js | 2 +- src/widget/form.js | 51 ++- src/widget/input.js | 89 ++-- src/widget/select.js | 554 ++++++++++++----------- src/widgets.js | 591 +++++++++++++------------ test/AngularSpec.js | 49 +- test/BinderSpec.js | 130 ++---- test/ScenarioSpec.js | 13 +- test/directivesSpec.js | 247 +++++------ test/markupSpec.js | 72 +-- test/sanitizerSpec.js | 10 +- test/scenario/dslSpec.js | 82 ++-- test/service/filter/filtersSpec.js | 16 +- test/service/logSpec.js | 2 +- test/testabilityPatch.js | 16 +- test/widget/inputSpec.js | 79 ++-- test/widget/selectSpec.js | 4 +- test/widgetsSpec.js | 94 ++-- 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'}]"> Search: - - - - - + + + + + + + + +
NamePhone
{{friend.name}}{{friend.phone}}
NamePhone
{{friend.name}}{{friend.phone}}
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. - +
@@ -337,7 +338,7 @@ This example shows how to implement a custom HTML editor widget in Angular. HTML:

-
editorForm = {{editorForm}}
+
editorForm = {{editorForm|json}}
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 @@ ' \n' + ''; - 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(/]*>([\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('
    '); - - // show source tab, if not disabled - if (showSource) { - tabs.append( - '
  • Source

  • ' + - '
  • ' + - jsFiddleButton(jsfiddle) + // may or may not have value - '
  • '); - } - // show live preview tab - var livePreviewTab; - tabs.append('
  • Live Preview

  • '); - tabs.append(livePreviewTab = angular.element('
  • ' + htmlSrc +'
  • ')); - // show scenario tab, if present - if (scenario.text()) { - tabs.append( - '
  • Scenario Test

  • ' + - '
  • ' + scenario.text() + '
  • '); - } - - tabs.find('li').eq(1).find('pre').text( - HTML_TEMPLATE. - replace('_SCRIPT_SOURCE_', scriptSrc ? ' \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(/]*>([\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('
      '); + + // show source tab, if not disabled + if (showSource) { + tabs.append( + '
    • Source

    • ' + + '
    • ' + + jsFiddleButton(jsfiddle) + // may or may not have value + '
    • '); + } + // show live preview tab + var livePreviewTab; + tabs.append('
    • Live Preview

    • '); + tabs.append(livePreviewTab = angular.element('
    • ' + htmlSrc +'
    • ')); + // show scenario tab, if present + if (scenario.text()) { + tabs.append( + '
    • Scenario Test

    • ' + + '
    • ' + scenario.text() + '
    • '); + } - element.html(''); - element.append(tabs); + tabs.find('li').eq(1).find('pre').text( + HTML_TEMPLATE. + replace('_SCRIPT_SOURCE_', scriptSrc ? ' \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 '
      ' + + (fixJsFiddleIssue132 ? '' : '') + + '' + + '' + + '' + + '' + + '' + + '
      '; + } else { + //use existing fiddle + fiddleUrl = "http://jsfiddle.net" + jsfiddle; + return '
      ' + + '' + + '
      '; + } } - - return '
      ' + - (fixJsFiddleIssue132 ? '' : '') + - '' + - '' + - '' + - '' + - '' + - '
      '; - } else { - //use existing fiddle - fiddleUrl = "http://jsfiddle.net" + jsfiddle; - return '
      ' + - '' + - '
      '; - } + }; } - return ''; } }]); @@ -163,93 +166,95 @@ return lines.join('\n'); } - var HTML_TPL = - '

      Workspace Reset Instructions  ➤

      ' + - '
      ' + - '
      ' + - '
        ' + - '
      ' + - '
      ' + - '
      ' + - - '
      ' + + $compileProvider.directive('docTutorialInstructions', function() { + var HTML_NAV = '
    • {title}
    • '; + var HTML_CONTENT = '
      {content}
      '; + + var HTML_TPL = + '

      Workspace Reset Instructions  ➤

      ' + + '
      ' + + '
      ' + + '
        ' + + '
      ' + + '
      ' + + '
      ' + + + '
      ' + + '
      '; + + var DEFAULT_NAV = + '
    • Git on Mac/Linux
    • ' + + '
    • Git on Windows
    • ' + + '
    • Snapshots on Mac/Linux
    • ' + + '
    • Snapshots on Windows
    • '; + + var DEFAULT_CONTENT = + '
      ' + + '
        ' + + '
      1. Reset the workspace to step {step}.

        ' + + '
         git checkout -f step-{step}
      2. ' + + '
      3. Refresh your browser or check the app out on angular\'s server.

      4. ' + + '
      ' + + '
      ' + + + '
      ' + + '
        ' + + '
      1. Reset the workspace to step {step}.

        ' + + '
         git checkout -f step-{step}
      2. ' + + '
      3. Refresh your browser or check the app out on angular\'s server.

      4. ' + + '
      ' + + '
      ' + + + '
      ' + + '
        ' + + '
      1. Reset the workspace to step {step}.

        ' + + '
         ./goto_step.sh {step}
      2. ' + + '
      3. Refresh your browser or check the app out on angular\'s server.

      4. ' + + '
      ' + + '
      ' + + + '
      ' + + '
        ' + + '
      1. Reset the workspace to step {step}.

        ' + + '
         ./goto_step.bat {step}
      2. ' + + '
      3. Refresh your browser or check the app out on angular\'s server.

      4. ' + + '
      ' + '
      '; - var HTML_NAV = '
    • {title}
    • '; - var HTML_CONTENT = '
      {content}
      '; - - var DEFAULT_NAV = - '
    • Git on Mac/Linux
    • ' + - '
    • Git on Windows
    • ' + - '
    • Snapshots on Mac/Linux
    • ' + - '
    • Snapshots on Windows
    • '; - - var DEFAULT_CONTENT = - '
      ' + - '
        ' + - '
      1. Reset the workspace to step {step}.

        ' + - '
         git checkout -f step-{step}
      2. ' + - '
      3. Refresh your browser or check the app out on angular\'s server.

      4. ' + - '
      ' + - '
      ' + - - '
      ' + - '
        ' + - '
      1. Reset the workspace to step {step}.

        ' + - '
         git checkout -f step-{step}
      2. ' + - '
      3. Refresh your browser or check the app out on angular\'s server.

      4. ' + - '
      ' + - '
      ' + - - '
      ' + - '
        ' + - '
      1. Reset the workspace to step {step}.

        ' + - '
         ./goto_step.sh {step}
      2. ' + - '
      3. Refresh your browser or check the app out on angular\'s server.

      4. ' + - '
      ' + - '
      ' + - - '
      ' + - '
        ' + - '
      1. Reset the workspace to step {step}.

        ' + - '
         ./goto_step.bat {step}
      2. ' + - '
      3. Refresh your browser or check the app out on angular\'s server.

      4. ' + - '
      ' + - '
      '; - - 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 @@ */ -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){ */ -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) { */ -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){ 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'); }); */ -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){ */ -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,17 +434,31 @@ 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 @@ -496,48 +496,42 @@ angularDirective("ng:click", function(expression, element){ 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"]'); }); */ -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) { */ -angularDirective("ng:class", ngClass(function() {return true;})); +var ngClassDirective = classDirective('', true); /** * @ngdoc directive @@ -624,7 +618,7 @@ angularDirective("ng:class", ngClass(function() {return true;})); */ -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;})); */ -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;})); */ -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){ */ -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){ */ -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) { * */ -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 ``. - * - * Create custom markup like this: - * - *
      - *   angular.markup('newMarkup', function(text, textNode, parentElement){
      - *     //tranformation code
      - *   });
      - * 
      - * - * 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 - * - *
      - *   angular.attrMarkup('newAttrMarkup', function(attrValue, attrName, element){
      - *     //tranformation code
      - *   });
      - * 
      - * - * 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(''); - newElement.attr('ng:bind', exp); - } else { - newElement = jqLite(document.createTextNode(text)); - } - if (msie && text.charAt(0) == ' ') { - newElement = jqLite(' '); - 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 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 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']); }); 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) { it('should jsonify filtered objects', function() { - expect(binding('| json')).toBe('{\n "name":"value"}'); + expect(binding("{'name':'value'}")).toBe('{\n "name":"value"}'); }); @@ -420,108 +420,6 @@ var lowercaseFilter = valueFn(lowercase); 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 - - - -
      - Snippet: - - - - - - - - - - - - - - - - - - - - - -
      FilterSourceRendered
      html filter -
      <div ng:bind="snippet | html">
      </div>
      -
      -
      -
      no filter
      <div ng:bind="snippet">
      </div>
      unsafe html filter
      <div ng:bind="snippet | html:'unsafe'">
      </div>
      -
      -
      - - it('should sanitize the html snippet ', function() { - expect(using('#html-filter').binding('snippet | html')). - toBe('

      an html\nclick here\nsnippet

      '); - }); - - it('should escape snippet without any filter', function() { - expect(using('#escaped-html').binding('snippet')). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); - - it('should inline raw snippet if filtered as unsafe', function() { - expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")). - toBe("

      an html\n" + - "click here\n" + - "snippet

      "); - }); - - it('should update', function() { - input('snippet').enter('new text'); - expect(using('#html-filter').binding('snippet | html')).toBe('new text'); - expect(using('#escaped-html').binding('snippet')).toBe("new <b>text</b>"); - expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new text'); - }); -
      -
      - */ -//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 @@ -558,10 +456,10 @@ function htmlFilter() { linky filter -
      <div ng:bind="snippet | linky">
      </div>
      +
      <div ng:bind-html="snippet | linky">
      </div>
      -
      +
      @@ -574,10 +472,10 @@ function htmlFilter() { it('should linkify the snippet with urls', function() { expect(using('#linky-filter').binding('snippet | linky')). - toBe('Pretty text with some links:\n' + - 'http://angularjs.org/,\n' + - 'us@somewhere.org,\n' + - 'another@somewhere.org,\n' + + toBe('Pretty text with some links: ' + + 'http://angularjs.org/, ' + + 'us@somewhere.org, ' + + 'another@somewhere.org, ' + 'and one more: ftp://127.0.0.1/.'); }); @@ -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 @@ 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 @@ */ -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) { */ -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( 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( */ -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 @@ 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'); }); */ - - //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('
      @@ -87,64 +87,62 @@ */ -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){ */ -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.: * Save */ -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 link 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() { */ -angularWidget('@ng:repeat', function(expression, element){ - element.removeAttr('ng:repeat'); - element.replaceWith(jqLite('')); - 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('')); + 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){ */ -angularWidget("@ng:non-bindable", noop); +var ngNonBindableDirective = valueFn({ terminal: true }); /** @@ -564,49 +571,48 @@ angularWidget("@ng:non-bindable", noop); */ -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) { Without Offset: - -
      +
      With Offset(2): - - + 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.'); }); */ -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('
      ' + - '' + - '' + - '' + - 'xx4' + - '
      '); - $compile(el); - expect(lowercase(sortedHtml(el))).toBe('
      ' + - '+xx1+' + - '+xx2+' + - '+xx3+' + - '-xx4-' + - '
      '); - })); - }); - - 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('
      {{greeting = "hello world"}}
      '); - $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('
      {{greeting = "hello world"}}
      '); - $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('
      {{greeting = "hello world"}}
      '); - 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('
      {{greeting = "hello world"}}
      '); - 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('
      text
      ')).toEqual(''); }); }); + + 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('
      x
      ')($rootScope); + element = $compile('
      x
      ')($rootScope); $rootScope.model = {a:123}; $rootScope.$apply(); expect(element.text()).toBe('123'); })); - it('ReplaceBindingInTextWithSpan preserve surounding text', function() { - expect(this.compileToHtml('a{{b}}c')).toBe('ac'); - }); - - it('ReplaceBindingInTextWithSpan', function() { - expect(this.compileToHtml('{{b}}')).toBe(''); - }); - - it('BindingSpaceConfusesIE', inject(function($rootScope, $compile) { - if (!msie) return; - var span = document.createElement('span'); - span.innerHTML = ' '; - var nbsp = span.firstChild.nodeValue; - expect(this.compileToHtml('{{a}} {{b}}')). - toBe('' + nbsp + ''); - dealoc(($rootScope)); - expect(this.compileToHtml('{{A}} x {{B}} ({{C}})')). - toBe('' + nbsp + 'x ' + - '' + nbsp + '()'); - })); - - it('BindingOfAttributes', inject(function($rootScope, $compile) { - var element = $compile('')($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('')($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('')($rootScope); expect(a[0].nodeName).toBe('A'); expect(a.attr('ng:bind-attr')).toBeFalsy(); })); - it('ExistingAttrbindingIsAppended', inject(function($rootScope, $compile) { - var a = $compile('')($rootScope); - expect(a.attr('ng:bind-attr')).toBe('{"b":"{{def}}","href":"http://s/{{abc}}"}'); - })); - it('AttributesAreEvaluated', inject(function($rootScope, $compile) { var a = $compile('')($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( '')($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('')($rootScope); + var log = ""; + element = $compile('')($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('')($rootScope); + element = $compile('')($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( '
        ' + '
      • ' + '
      ')($rootScope); @@ -198,8 +157,8 @@ describe('Binder', function() { }); it('RepeaterAdd', inject(function($rootScope, $compile, $browser) { - var element = $compile('
      ')($rootScope); - $rootScope.items = [{x: 'a'}, {x: 'b'}]; + element = $compile('
      ')($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('
      {{i}}
      ')($rootScope); + element = $compile('
      {{i}}
      ')($rootScope); var items = {}; $rootScope.items = items; @@ -234,7 +193,7 @@ describe('Binder', function() { $exceptionHandlerProvider.mode('log'); }); inject(function($rootScope, $exceptionHandler, $compile) { - $compile('
      {{error.throw()}}
      ', null, true)($rootScope); + element = $compile('
      {{error.throw()}}
      ', 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( '
      ' + '
      ' + '
        ' + @@ -290,21 +249,21 @@ describe('Binder', function() { expect(sortedHtml(element)).toBe( '
        '+ '<#comment>'+ - '
        '+ + '
        '+ '<#comment>'+ - '
          '+ - '
            '+ + '
              '+ + '
                '+ '
                '+ - '
                '+ + '
                '+ '<#comment>'+ - '
                  '+ - '
                    '+ + '
                      '+ + '
                        '+ '
                        ' + '
                        '); })); it('HideBindingExpression', inject(function($rootScope, $compile) { - var element = $compile('
                        ')($rootScope); + element = $compile('
                        ')($rootScope); $rootScope.hidden = 3; $rootScope.$apply(); @@ -318,7 +277,7 @@ describe('Binder', function() { })); it('HideBinding', inject(function($rootScope, $compile) { - var element = $compile('
                        ')($rootScope); + element = $compile('
                        ')($rootScope); $rootScope.hidden = 'true'; $rootScope.$apply(); @@ -337,7 +296,7 @@ describe('Binder', function() { })); it('ShowBinding', inject(function($rootScope, $compile) { - var element = $compile('
                        ')($rootScope); + element = $compile('
                        ')($rootScope); $rootScope.show = 'true'; $rootScope.$apply(); @@ -357,7 +316,7 @@ describe('Binder', function() { it('BindClass', inject(function($rootScope, $compile) { - var element = $compile('
                        ')($rootScope); + element = $compile('
                        ')($rootScope); $rootScope.clazz = 'testClass'; $rootScope.$apply(); @@ -371,7 +330,7 @@ describe('Binder', function() { })); it('BindClassEvenOdd', inject(function($rootScope, $compile) { - var element = $compile( + element = $compile( '
                        ' + '
                        ' + '
                        ')($rootScope); @@ -387,7 +346,7 @@ describe('Binder', function() { })); it('BindStyle', inject(function($rootScope, $compile) { - var element = $compile('
                        ')($rootScope); + element = $compile('
                        ')($rootScope); $rootScope.$eval('style={height: "10px"}'); $rootScope.$apply(); @@ -413,28 +372,29 @@ describe('Binder', function() { }); it('ShoulIgnoreVbNonBindable', inject(function($rootScope, $compile) { - var element = $compile( - '
                        {{a}}' + - '
                        {{a}}
                        ' + - '
                        {{b}}
                        ' + - '
                        {{c}}
                        ' + - '
                        ')($rootScope); + element = $compile( + "
                        {{a}}" + + "
                        {{a}}
                        " + + "
                        {{b}}
                        " + + "
                        {{c}}
                        " + + "
                        ")($rootScope); $rootScope.a = 123; $rootScope.$apply(); expect(element.text()).toBe('123{{a}}{{b}}{{c}}'); })); it('ShouldTemplateBindPreElements', inject(function ($rootScope, $compile) { - var element = $compile('
                        Hello {{name}}!
                        ')($rootScope); - $rootScope.name = 'World'; + element = $compile('
                        Hello {{name}}!
                        ')($rootScope); + $rootScope.name = "World"; $rootScope.$apply(); - expect( sortedHtml(element)).toBe( - '
                        Hello World!
                        '); + assertEquals( + '
                        Hello World!
                        ', + sortedHtml(element)); })); it('FillInOptionValueWhenMissing', inject(function($rootScope, $compile) { - var element = $compile( + element = $compile( '' + '' + @@ -485,7 +445,7 @@ describe('Binder', function() { $exceptionHandlerProvider.mode('log'); }); inject(function($rootScope, $exceptionHandler, $log, $compile) { - var element = $compile( + element = $compile( '
                        ' + '' + '' + @@ -505,7 +465,7 @@ describe('Binder', function() { }); it('ItShouldSelectTheCorrectRadioBox', inject(function($rootScope, $compile) { - var element = $compile( + element = $compile( '
                        ' + '' + '' + @@ -527,7 +487,7 @@ describe('Binder', function() { })); it('ItShouldRepeatOnHashes', inject(function($rootScope, $compile) { - var element = $compile( + element = $compile( '
                          ' + '
                        • ' + '
                        ')($rootScope); @@ -541,7 +501,7 @@ describe('Binder', function() { })); it('ItShouldFireChangeListenersBeforeUpdate', inject(function($rootScope, $compile) { - var element = $compile('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        {{\n 1 \n + \n 2 \n}}
                        ')($rootScope); + element = $compile('
                        {{\n 1 \n + \n 2 \n}}
                        ')($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('
                        {{b=a+1}}
                        ')[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('
                        {{a=123}}
                        '))($rootScope); + element = $compile(jqLite('
                        {{a=123}}
                        '))($rootScope); $rootScope.$digest(); expect(jqLite(element).text()).toEqual('123'); })); it("should compile text node and return scope", inject(function($rootScope, $compile) { - var element = $compile('
                        {{a=123}}
                        ')($rootScope); + element = $compile('
                        {{a=123}}
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($rootScope); $rootScope.html = '
                        hello
                        '; $rootScope.$digest(); expect(lowercase(element.html())).toEqual('
                        hello
                        '); })); it('should set unsafe html', inject(function($rootScope, $compile) { - var element = $compile('
                        ')($rootScope); + element = $compile('
                        ')($rootScope); $rootScope.html = '
                        hello
                        '; $rootScope.$digest(); expect(lowercase(element.html())).toEqual('
                        hello
                        '); })); - it('should set element element', inject(function($rootScope, $compile) { - $filterProvider.register('myElement', valueFn(function() { - return jqLite('hello'); - })); - var element = $compile('
                        ')($rootScope); - $rootScope.$digest(); - expect(lowercase(element.html())).toEqual('hello'); - })); - - it('should suppress rendering of falsy values', inject(function($rootScope, $compile) { - var element = $compile('
                        {{ null }}{{ undefined }}{{ "" }}-{{ 0 }}{{ false }}
                        ')($rootScope); + element = $compile('
                        {{ null }}{{ undefined }}{{ "" }}-{{ 0 }}{{ false }}
                        ')($rootScope); $rootScope.$digest(); expect(element.text()).toEqual('-0false'); })); it('should render object as JSON ignore $$', inject(function($rootScope, $compile) { - var element = $compile('
                        {{ {key:"value", $$key:"hide"} }}
                        ')($rootScope); + element = $compile('
                        {{ {key:"value", $$key:"hide"} }}
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        beforeINNERafter
                        ')($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('
                        {{ {key:"value", $$key:"hide"}  }}
                        ')($rootScope); + element = $compile('
                        {{ {key:"value", $$key:"hide"}  }}
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('{{ {a:1} }}')($rootScope); + element = $compile('{{ {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('')($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('')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ' + + element = $compile('' + '' + '
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                        ')($rootScope); + element = $compile('
                        ')($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('
                          • ')($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('
                              • ')($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('
                                  ' + - '
                                • ' + - '
                                    ')($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('
                                      ' + + '
                                    • ' + + '
                                        ')($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('
                                          ' + - '
                                        • ' + - '
                                            ')($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('
                                              ' + + '
                                            • ' + + '
                                                ')($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('
                                                ')($rootScope); + element = $compile('
                                                ')($rootScope); $rootScope.$digest(); expect(element.css('height')).toEqual('40px'); })); it('should silently ignore undefined style', inject(function($rootScope, $compile) { - var element = $compile('
                                                ')($rootScope); + element = $compile('
                                                ')($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('
                                                '); - var element = $compile(element)($rootScope); + element = jqLite('
                                                '); + 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('
                                                '); - var element = $compile(element)($rootScope); + element = jqLite('
                                                '); + 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('
                                                '); - var element = $compile(element)($rootScope); + element = jqLite('
                                                '); + 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('
                                                '); + element = jqLite('
                                                '); 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('
                                                '); + element = jqLite('
                                                '); 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('
                                                hello {{name}}!
                                                ')($rootScope) - expect(sortedHtml(element)).toEqual('
                                                hello !
                                                '); + element = $compile('
                                                hello {{name}}!
                                                ')($rootScope) + $rootScope.$digest(); + expect(sortedHtml(element)).toEqual('
                                                hello !
                                                '); $rootScope.name = 'Misko'; $rootScope.$digest(); - expect(sortedHtml(element)).toEqual('
                                                hello Misko!
                                                '); + expect(sortedHtml(element)).toEqual('
                                                hello Misko!
                                                '); })); it('should translate {{}} in terminal nodes', inject(function($rootScope, $compile) { - var element = $compile('')($rootScope) + element = $compile('')($rootScope) $rootScope.$digest(); expect(sortedHtml(element).replace(' selected="true"', '')). toEqual(''); $rootScope.name = 'Misko'; $rootScope.$digest(); expect(sortedHtml(element).replace(' selected="true"', '')). toEqual(''); })); it('should translate {{}} in attributes', inject(function($rootScope, $compile) { - var element = $compile('
                                                ')($rootScope) - expect(element.attr('ng:bind-attr')).toEqual('{"src":"http://server/{{path}}.png"}'); + element = $compile('
                                                ')($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('')($rootScope) + element = $compile('')($rootScope) expect(element).toHaveValue('abc'); })); it('should ignore value if already exists', inject(function($rootScope, $compile) { - var element = $compile('')($rootScope) + element = $compile('')($rootScope) expect(element).toHaveValue('abc'); })); it('should set value even if newlines present', inject(function($rootScope, $compile) { - var element = $compile('')($rootScope) + element = $compile('')($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('')($rootScope) + element = $compile('')($rootScope) expect(element).toHaveValue('\n'); })); }); it('should bind href', inject(function($rootScope, $compile) { - var element = $compile('')($rootScope) - expect(sortedHtml(element)).toEqual(''); + element = $compile('')($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('')($rootScope) + element = $compile('')($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('')($rootScope) + element = $compile('')($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('')($rootScope) + element = $compile('')($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('')($rootScope) + element = $compile('')($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('')($rootScope) + element = $compile('')($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('
                                                ')($rootScope) + element = $compile('
                                                ')($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('')($rootScope) - expect(sortedHtml(element)).toEqual(''); + element = $compile('')($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('
                                                ')(newScope()) - expect(element.attr('ng:bind-attr')).toBe('{"' + name +'":"some"}'); + element = $compile('
                                                ')($rootScope) $rootScope.$digest(); expect(element.attr(name)).toBe(name); dealoc(element); }); - var element = $compile('
                                                ')(newScope()) + element = $compile('
                                                ')($rootScope) $rootScope.$digest(); - expect(sortedHtml(element)).toEqual('
                                                '); + expect(element.attr('src')).toEqual('some'); dealoc(element); - var element = $compile('
                                                ')(newScope()) + element = $compile('
                                                ')($rootScope) $rootScope.$digest(); - expect(sortedHtml(element)).toEqual('
                                                '); + 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( - '
                                                  ' + - '
                                                • misko' + - ' male
                                                • ' + - '
                                                • felisa' + - ' female
                                                • ' + - '
                                                ' - ); + beforeEach(inject(function($compile, $rootScope) { + element = $compile( + '
                                                • {{i.name}} {{i.gender}}
                                                ')($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('some value'); + 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('{{ foo.bar }}'); $root.dsl.binding('foo.bar'); expect($root.futureResult).toEqual('some value'); }); - it('should select binding by regexp', function() { - doc.append('some value'); - $root.dsl.binding(/^foo\..+/); + it('should select binding in multiple interpolations', function() { + compile('{{ foo.bar }}
                                                {{ true }}
                                                '); + $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(''); + it('should select binding by name', function() { + compile(''); $root.dsl.binding('foo.bar'); expect($root.futureResult).toEqual('some value'); }); - it('should return value for textarea elements', function() { - doc.append(''); - $root.dsl.binding('foo.bar'); + it('should select binding by regexp', function() { + compile('some value'); + $root.dsl.binding(/^foo\..+/); expect($root.futureResult).toEqual('some value'); }); it('should return innerHTML for all the other elements', function() { - doc.append('
                                                some value
                                                '); + compile('
                                                ', 'some value'); $root.dsl.binding('foo.bar'); expect($root.futureResult.toLowerCase()).toEqual('some value'); }); it('should select binding in template by name', function() { - doc.append('
                                                foo some baz
                                                '); - $root.dsl.binding('bar'); - expect($root.futureResult).toEqual('foo some baz'); + compile('
                                                ', 'bar');
                                                +        $root.dsl.binding('foo.bar');
                                                +        expect($root.futureResult).toEqual('bar');
                                                       });
                                                 
                                                       it('should match bindings by substring match', function() {
                                                -        doc.append('
                                                binding value
                                                '); - $root.dsl.binding('test.baz'); + compile('
                                                ', '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('some value');
                                                +        compile('some value');
                                                         $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')("acd");
                                                -      expect(html instanceof HTML).toBeTruthy();
                                                -      expect(html.html).toEqual("acd");
                                                -    });
                                                -  });
                                                -
                                                   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://1.2/v:~-123. c").html).
                                                +      expect(linky("http://ab/ (http://a/)  http://1.2/v:~-123. c")).
                                                         toEqual('http://ab/ ' +
                                                                 '(http://a/) ' +
                                                                 '<http://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('me@example.com');
                                                -      expect(linky("me@example.com").html).
                                                +      expect(linky("me@example.com")).
                                                                       toEqual('me@example.com');
                                                -      expect(linky("send email to me@example.com, but").html).
                                                +      expect(linky("send email to me@example.com, but")).
                                                         toEqual('send email to me@example.com, 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() {
                                                                 '
                                                '); 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(''); expect(scope.age).toBeNull(); - expect(scope.$element[0].value).toEqual(''); + expect(element[0].value).toEqual(''); }); @@ -189,19 +187,19 @@ describe('widget: input', function() { compile(''); 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(''); - 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(''); 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(''); 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('', 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() { '
                                                '); 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() { '
                                                '); 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">'+ ''); - 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( '
                                              • '+ ''+ @@ -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(''); - 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(''); - 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( '' + '
                                                first:{{name}}
                                                ' + '
                                                second:{{name}}
                                                ' + @@ -29,7 +35,7 @@ describe('widget', function() { it('should switch on switch-when-default', inject(function($rootScope, $compile) { - var element = $compile( + element = $compile( '' + '
                                                one
                                                ' + '
                                                other
                                                ' + @@ -43,7 +49,7 @@ describe('widget', function() { it('should call change on switch', inject(function($rootScope, $compile) { - var element = $compile( + element = $compile( '' + '
                                                {{name}}
                                                ' + '
                                                ')($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(''); + element = jqLite(''); 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(''); + element = jqLite(''); 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(''); + element = jqLite(''); 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(''); + element = jqLite(''); 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(''); + element = jqLite(''); 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('')($rootScope); + element = $compile('')($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('content')($rootScope); + element = $compile('content')($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('')($rootScope); + element = $compile('')($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(""), - log = []; + element = jqLite(""); + 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('empty link')($rootScope); + element = $compile('empty link')($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( '
                                                  ' + '
                                                • ' + '
                                                ')($rootScope); @@ -355,7 +361,7 @@ describe('widget', function() { it('should ng:repeat over object', inject(function($rootScope, $compile) { - var element = $compile( + element = $compile( '
                                                  ' + '
                                                • ' + '
                                                ')($rootScope); @@ -370,7 +376,7 @@ describe('widget', function() { Class.prototype.abc = function() {}; Class.prototype.value = 'abc'; - var element = $compile( + element = $compile( '
                                                  ' + '
                                                • ' + '
                                                ')($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('
                                                ')($rootScope); + element = $compile('
                                                ')($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( '
                                                  ' + '
                                                • ' + '
                                                ')($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( '
                                                  ' + '
                                                • ' + '
                                                ')($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( '
                                                  ' + '
                                                • ' + '
                                                ')($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( '
                                                  ' + '
                                                • ' + '
                                                • ' + @@ -454,7 +460,7 @@ describe('widget', function() { it('should ignore $ and $$ properties', inject(function($rootScope, $compile) { - var element = $compile('
                                                  • {{i}}|
                                                  ')($rootScope); + element = $compile('
                                                  • {{i}}|
                                                  ')($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( '
                                                    ' + '
                                                  • ' + '
                                                    {{group}}|
                                                    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('
                                                    • {{item}}|
                                                    ')($rootScope); + element = $compile('
                                                    • {{item}}|
                                                    ')($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('
                                                    • {{item}}|
                                                    ')($rootScope); + element = $compile('
                                                    • {{item}}|
                                                    ')($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('
                                                    • {{item}}|
                                                    ')($rootScope); + element = $compile('
                                                    • {{item}}|
                                                    ')($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('
                                                    ')($rootScope); + element = $compile('
                                                    ')($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('')($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')( '
                                                    ' + 'include: ' + '
                                                    ')(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('
                                                    {{1+3}}
                                                    '); @@ -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( '