angular-rails-engine 1.2.5.0 → 1.2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +1 -1
  5. data/app/assets/javascripts/angular/angular-animate.js +461 -195
  6. data/app/assets/javascripts/angular/angular-animate.min.js +23 -18
  7. data/app/assets/javascripts/angular/angular-cookies.js +1 -1
  8. data/app/assets/javascripts/angular/angular-cookies.min.js +2 -1
  9. data/app/assets/javascripts/angular/angular-loader.js +2 -2
  10. data/app/assets/javascripts/angular/angular-loader.min.js +3 -2
  11. data/app/assets/javascripts/angular/angular-mocks.js +54 -34
  12. data/app/assets/javascripts/angular/angular-resource.js +36 -5
  13. data/app/assets/javascripts/angular/angular-resource.min.js +9 -8
  14. data/app/assets/javascripts/angular/angular-route.js +25 -15
  15. data/app/assets/javascripts/angular/angular-route.min.js +10 -9
  16. data/app/assets/javascripts/angular/angular-sanitize.js +35 -32
  17. data/app/assets/javascripts/angular/angular-sanitize.min.js +3 -2
  18. data/app/assets/javascripts/angular/angular-scenario.js +1472 -966
  19. data/app/assets/javascripts/angular/angular-touch.js +1 -1
  20. data/app/assets/javascripts/angular/angular-touch.min.js +2 -1
  21. data/app/assets/javascripts/angular/angular.js +1470 -965
  22. data/app/assets/javascripts/angular/angular.min.js +200 -196
  23. data/app/assets/stylesheets/angular/angular-csp.css +18 -0
  24. data/gem-public_cert.pem +11 -10
  25. data/lib/angular-rails-engine.rb +1 -1
  26. data/lib/angular-rails-engine/version.rb +1 -1
  27. metadata +14 -13
  28. metadata.gz.sig +0 -0
  29. data/app/assets/stylesheets/angular-csp.css +0 -24
@@ -1,13 +1,14 @@
1
1
  /*
2
- AngularJS v1.2.5
2
+ AngularJS v1.2.13
3
3
  (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  License: MIT
5
5
  */
6
- (function(h,e,A){'use strict';function u(w,q,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,n){function y(){l&&(l.$destroy(),l=null);g&&(k.leave(g),g=null)}function v(){var b=w.current&&w.current.locals;if(b&&b.$template){var b=a.$new(),f=w.current;g=n(b,function(d){k.enter(d,null,g||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||q()});y()});l=f.scope=b;l.$emit("$viewContentLoaded");l.$eval(h)}else y()}var l,g,t=b.autoscroll,h=b.onload||"";a.$on("$routeChangeSuccess",
7
- v);v()}}}function z(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var n=e(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));n(a)}}}h=e.module("ngRoute",["ng"]).provider("$route",function(){function h(a,c){return e.extend(new (e.extend(function(){},{prototype:a})),c)}function q(a,e){var b=e.caseInsensitiveMatch,
8
- f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&q(a,c));if(a){var b="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";k[b]=e.extend({redirectTo:a},
9
- q(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,n,q,v,l){function g(){var d=t(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!x)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)x=!1,a.$broadcast("$routeChangeStart",d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)?
10
- c.path(u(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?n.get(d):n.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=l.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=b,c=q.get(b,{cache:v}).then(function(a){return a.data})));
11
- e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function t(){var a,b;e.forEach(k,function(f,k){var p;if(p=!b){var s=c.path();p=f.keys;var l={};if(f.regexp)if(s=f.regexp.exec(s)){for(var g=1,q=s.length;g<q;++g){var n=p[g-1],r="string"==typeof s[g]?decodeURIComponent(s[g]):s[g];n&&r&&(l[n.name]=r)}p=l}else p=null;else p=null;
12
- p=a=p}p&&(b=h(f,{params:e.extend({},c.search(),a),pathParams:a}),b.$$route=f)});return b||k[null]&&h(k[null],{params:{},pathParams:{}})}function u(a,c){var b=[];e.forEach((a||"").split(":"),function(a,d){if(0===d)b.push(a);else{var e=a.match(/(\w+)(.*)/),f=e[1];b.push(c[f]);b.push(e[2]||"");delete c[f]}});return b.join("")}var x=!1,r={routes:k,reload:function(){x=!0;a.$evalAsync(g)}};a.$on("$locationChangeSuccess",g);return r}]});h.provider("$routeParams",function(){this.$get=function(){return{}}});
13
- h.directive("ngView",u);h.directive("ngView",z);u.$inject=["$route","$anchorScroll","$animate"];z.$inject=["$compile","$controller","$route"]})(window,window.angular);
6
+ (function(h,e,A){'use strict';function u(w,q,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,n){function y(){l&&(l.$destroy(),l=null);g&&(k.leave(g),g=null)}function v(){var b=w.current&&w.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),f=w.current;g=n(b,function(d){k.enter(d,null,g||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||q()});y()});l=f.scope=b;l.$emit("$viewContentLoaded");l.$eval(h)}else y()}var l,g,t=b.autoscroll,h=b.onload||"";
7
+ a.$on("$routeChangeSuccess",v);v()}}}function z(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var n=e(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));n(a)}}}h=e.module("ngRoute",["ng"]).provider("$route",function(){function h(a,c){return e.extend(new (e.extend(function(){},{prototype:a})),c)}function q(a,
8
+ e){var b=e.caseInsensitiveMatch,f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&q(a,c));if(a){var b="/"==a[a.length-1]?a.substr(0,a.length-
9
+ 1):a+"/";k[b]=e.extend({redirectTo:a},q(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,n,q,v,l){function g(){var d=t(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!x)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)x=!1,a.$broadcast("$routeChangeStart",d,m),(r.current=
10
+ d)&&d.redirectTo&&(e.isString(d.redirectTo)?c.path(u(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?n.get(d):n.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=l.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=b,c=q.get(b,
11
+ {cache:v}).then(function(a){return a.data})));e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function t(){var a,b;e.forEach(k,function(f,k){var p;if(p=!b){var s=c.path();p=f.keys;var l={};if(f.regexp)if(s=f.regexp.exec(s)){for(var g=1,q=s.length;g<q;++g){var n=p[g-1],r="string"==typeof s[g]?decodeURIComponent(s[g]):s[g];
12
+ n&&r&&(l[n.name]=r)}p=l}else p=null;else p=null;p=a=p}p&&(b=h(f,{params:e.extend({},c.search(),a),pathParams:a}),b.$$route=f)});return b||k[null]&&h(k[null],{params:{},pathParams:{}})}function u(a,c){var b=[];e.forEach((a||"").split(":"),function(a,d){if(0===d)b.push(a);else{var e=a.match(/(\w+)(.*)/),f=e[1];b.push(c[f]);b.push(e[2]||"");delete c[f]}});return b.join("")}var x=!1,r={routes:k,reload:function(){x=!0;a.$evalAsync(g)}};a.$on("$locationChangeSuccess",g);return r}]});h.provider("$routeParams",
13
+ function(){this.$get=function(){return{}}});h.directive("ngView",u);h.directive("ngView",z);u.$inject=["$route","$anchorScroll","$animate"];z.$inject=["$compile","$controller","$route"]})(window,window.angular);
14
+ //# sourceMappingURL=angular-route.min.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.2.5
2
+ * @license AngularJS v1.2.13
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -104,35 +104,37 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
104
104
  </table>
105
105
  </div>
106
106
  </doc:source>
107
- <doc:scenario>
107
+ <doc:protractor>
108
108
  it('should sanitize the html snippet by default', function() {
109
- expect(using('#bind-html-with-sanitize').element('div').html()).
109
+ expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
110
110
  toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
111
111
  });
112
112
 
113
113
  it('should inline raw snippet if bound to a trusted value', function() {
114
- expect(using('#bind-html-with-trust').element("div").html()).
114
+ expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
115
115
  toBe("<p style=\"color:blue\">an html\n" +
116
116
  "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
117
117
  "snippet</p>");
118
118
  });
119
119
 
120
120
  it('should escape snippet without any filter', function() {
121
- expect(using('#bind-default').element('div').html()).
121
+ expect(element(by.css('#bind-default div')).getInnerHtml()).
122
122
  toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
123
123
  "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
124
124
  "snippet&lt;/p&gt;");
125
125
  });
126
126
 
127
127
  it('should update', function() {
128
- input('snippet').enter('new <b onclick="alert(1)">text</b>');
129
- expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new <b>text</b>');
130
- expect(using('#bind-html-with-trust').element('div').html()).toBe(
128
+ element(by.model('snippet')).clear();
129
+ element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
130
+ expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
131
+ toBe('new <b>text</b>');
132
+ expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
131
133
  'new <b onclick="alert(1)">text</b>');
132
- expect(using('#bind-default').element('div').html()).toBe(
134
+ expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
133
135
  "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
134
136
  });
135
- </doc:scenario>
137
+ </doc:protractor>
136
138
  </doc:example>
137
139
  */
138
140
  function $SanitizeProvider() {
@@ -211,7 +213,7 @@ var validAttrs = angular.extend({}, uriAttrs, makeMap(
211
213
  'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
212
214
  'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
213
215
  'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
214
- 'scope,scrolling,shape,span,start,summary,target,title,type,'+
216
+ 'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
215
217
  'valign,value,vspace,width'));
216
218
 
217
219
  function makeMap(str) {
@@ -537,37 +539,38 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
537
539
  </tr>
538
540
  </table>
539
541
  </doc:source>
540
- <doc:scenario>
542
+ <doc:protractor>
541
543
  it('should linkify the snippet with urls', function() {
542
- expect(using('#linky-filter').binding('snippet | linky')).
543
- toBe('Pretty text with some links:&#10;' +
544
- '<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
545
- '<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
546
- '<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
547
- 'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
544
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
545
+ toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
546
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
547
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
548
548
  });
549
549
 
550
- it ('should not linkify snippet without the linky filter', function() {
551
- expect(using('#escaped-html').binding('snippet')).
552
- toBe("Pretty text with some links:\n" +
553
- "http://angularjs.org/,\n" +
554
- "mailto:us@somewhere.org,\n" +
555
- "another@somewhere.org,\n" +
556
- "and one more: ftp://127.0.0.1/.");
550
+ it('should not linkify snippet without the linky filter', function() {
551
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
552
+ toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
553
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
554
+ expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
557
555
  });
558
556
 
559
557
  it('should update', function() {
560
- input('snippet').enter('new http://link.');
561
- expect(using('#linky-filter').binding('snippet | linky')).
562
- toBe('new <a href="http://link">http://link</a>.');
563
- expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
558
+ element(by.model('snippet')).clear();
559
+ element(by.model('snippet')).sendKeys('new http://link.');
560
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
561
+ toBe('new http://link.');
562
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
563
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
564
+ .toBe('new http://link.');
564
565
  });
565
566
 
566
567
  it('should work with the target property', function() {
567
- expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
568
- toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
568
+ expect(element(by.id('linky-target')).
569
+ element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
570
+ toBe('http://angularjs.org/');
571
+ expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
569
572
  });
570
- </doc:scenario>
573
+ </doc:protractor>
571
574
  </doc:example>
572
575
  */
573
576
  angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
@@ -1,5 +1,5 @@
1
1
  /*
2
- AngularJS v1.2.5
2
+ AngularJS v1.2.13
3
3
  (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  License: MIT
5
5
  */
@@ -8,6 +8,7 @@ if(0<=c){for(d=f.length-1;d>=c;d--)e.end&&e.end(f[d]);f.length=c}}var b,g,f=[],l
8
8
  a.replace(b[0],""),g=!1}else if(J.test(a)){if(b=a.match(z))a=a.substring(b[0].length),b[0].replace(z,c),g=!1}else K.test(a)&&(b=a.match(A))&&(a=a.substring(b[0].length),b[0].replace(A,d),g=!1);g&&(b=a.indexOf("<"),g=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),e.chars&&e.chars(r(g)))}if(a==l)throw L("badparse",a);l=a}c()}function r(a){if(!a)return"";var e=M.exec(a);a=e[1];var d=e[3];if(e=e[2])n.innerHTML=e.replace(/</g,"&lt;"),e="textContent"in n?n.textContent:n.innerText;return a+e+d}function B(a){return a.replace(/&/g,
9
9
  "&amp;").replace(N,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function s(a,e){var d=!1,c=h.bind(a,a.push);return{start:function(a,g,f){a=h.lowercase(a);!d&&x[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(g,function(d,f){var g=h.lowercase(f),k="img"===a&&"src"===g||"background"===g;!0!==O[g]||!0===D[g]&&!e(d,k)||(c(" "),c(f),c('="'),c(B(d)),c('"'))}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c("</"),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||
10
10
  c(B(a))}}}var L=h.$$minErr("$sanitize"),A=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,z=/^<\s*\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^</,J=/^<\s*\//,H=/\x3c!--(.*?)--\x3e/g,y=/<!DOCTYPE([^>]*?)>/i,I=/<!\[CDATA\[(.*?)]]\x3e/g,N=/([^\#-~| |!])/g,w=k("area,br,col,hr,img,wbr");p=k("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr");q=k("rp,rt");var v=h.extend({},q,p),t=h.extend({},p,k("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),
11
- u=h.extend({},q,k("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),x=k("script,style"),C=h.extend({},w,t,u,v),D=k("background,cite,href,longdesc,src,usemap"),O=h.extend({},D,k("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),
11
+ u=h.extend({},q,k("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),x=k("script,style"),C=h.extend({},w,t,u,v),D=k("background,cite,href,longdesc,src,usemap"),O=h.extend({},D,k("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width")),
12
12
  n=document.createElement("pre"),M=/^(\s*)([\s\S]*?)(\s*)$/;h.module("ngSanitize",[]).provider("$sanitize",function(){this.$get=["$$sanitizeUri",function(a){return function(e){var d=[];F(e,s(d,function(c,b){return!/^unsafe/.test(a(c,b))}));return d.join("")}}]});h.module("ngSanitize").filter("linky",["$sanitize",function(a){var e=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,d=/^mailto:/;return function(c,b){function g(a){a&&m.push(E(a))}function f(a,c){m.push("<a ");h.isDefined(b)&&
13
13
  (m.push('target="'),m.push(b),m.push('" '));m.push('href="');m.push(a);m.push('">');g(c);m.push("</a>")}if(!c)return c;for(var l,k=c,m=[],n,p;l=k.match(e);)n=l[0],l[2]==l[3]&&(n="mailto:"+n),p=l.index,g(k.substr(0,p)),f(n,l[0].replace(d,"")),k=k.substring(p+l[0].length);g(k);return a(m.join(""))}}])})(window,window.angular);
14
+ //# sourceMappingURL=angular-sanitize.min.js.map
@@ -9790,7 +9790,7 @@ if ( typeof module === "object" && module && typeof module.exports === "object"
9790
9790
  })( window );
9791
9791
 
9792
9792
  /**
9793
- * @license AngularJS v1.2.5
9793
+ * @license AngularJS v1.2.13
9794
9794
  * (c) 2010-2014 Google, Inc. http://angularjs.org
9795
9795
  * License: MIT
9796
9796
  */
@@ -9860,7 +9860,7 @@ function minErr(module) {
9860
9860
  return match;
9861
9861
  });
9862
9862
 
9863
- message = message + '\nhttp://errors.angularjs.org/1.2.5/' +
9863
+ message = message + '\nhttp://errors.angularjs.org/1.2.13/' +
9864
9864
  (module ? module + '/' : '') + code;
9865
9865
  for (i = 2; i < arguments.length; i++) {
9866
9866
  message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
@@ -9952,6 +9952,7 @@ function minErr(module) {
9952
9952
  -assertNotHasOwnProperty,
9953
9953
  -getter,
9954
9954
  -getBlockElements,
9955
+ -hasOwnProperty,
9955
9956
 
9956
9957
  */
9957
9958
 
@@ -9967,7 +9968,7 @@ function minErr(module) {
9967
9968
  * @returns {string} Lowercased string.
9968
9969
  */
9969
9970
  var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
9970
-
9971
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
9971
9972
 
9972
9973
  /**
9973
9974
  * @ngdoc function
@@ -10063,7 +10064,8 @@ function isArrayLike(obj) {
10063
10064
  * is the value of an object property or an array element and `key` is the object property key or
10064
10065
  * array element index. Specifying a `context` for the function is optional.
10065
10066
  *
10066
- * Note: this function was previously known as `angular.foreach`.
10067
+ * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
10068
+ * using the `hasOwnProperty` method.
10067
10069
  *
10068
10070
  <pre>
10069
10071
  var values = {name: 'misko', gender: 'male'};
@@ -10071,7 +10073,7 @@ function isArrayLike(obj) {
10071
10073
  angular.forEach(values, function(value, key){
10072
10074
  this.push(key + ': ' + value);
10073
10075
  }, log);
10074
- expect(log).toEqual(['name: misko', 'gender:male']);
10076
+ expect(log).toEqual(['name: misko', 'gender: male']);
10075
10077
  </pre>
10076
10078
  *
10077
10079
  * @param {Object|Array} obj Object to iterate over.
@@ -10084,7 +10086,9 @@ function forEach(obj, iterator, context) {
10084
10086
  if (obj) {
10085
10087
  if (isFunction(obj)){
10086
10088
  for (key in obj) {
10087
- if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
10089
+ // Need to check if hasOwnProperty exists,
10090
+ // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
10091
+ if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
10088
10092
  iterator.call(context, obj[key], key);
10089
10093
  }
10090
10094
  }
@@ -10640,7 +10644,7 @@ function shallowCopy(src, dst) {
10640
10644
  for(var key in src) {
10641
10645
  // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
10642
10646
  // so we don't need to worry about using our custom hasOwnProperty here
10643
- if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
10647
+ if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
10644
10648
  dst[key] = src[key];
10645
10649
  }
10646
10650
  }
@@ -10828,7 +10832,9 @@ function fromJson(json) {
10828
10832
 
10829
10833
 
10830
10834
  function toBoolean(value) {
10831
- if (value && value.length !== 0) {
10835
+ if (typeof value === 'function') {
10836
+ value = true;
10837
+ } else if (value && value.length !== 0) {
10832
10838
  var v = lowercase("" + value);
10833
10839
  value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
10834
10840
  } else {
@@ -10997,6 +11003,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
10997
11003
  <file name="index.html">
10998
11004
  <div ng-controller="ngAppDemoController">
10999
11005
  I can add: {{a}} + {{b}} = {{ a+b }}
11006
+ </div>
11000
11007
  </file>
11001
11008
  <file name="script.js">
11002
11009
  angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
@@ -11621,11 +11628,11 @@ function setupModuleLoader(window) {
11621
11628
  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
11622
11629
  */
11623
11630
  var version = {
11624
- full: '1.2.5', // all of these placeholder strings will be replaced by grunt's
11631
+ full: '1.2.13', // all of these placeholder strings will be replaced by grunt's
11625
11632
  major: 1, // package task
11626
11633
  minor: 2,
11627
- dot: 5,
11628
- codeName: 'singularity-expansion'
11634
+ dot: 13,
11635
+ codeName: 'romantic-transclusion'
11629
11636
  };
11630
11637
 
11631
11638
 
@@ -11787,7 +11794,7 @@ function publishExternalAPI(angular){
11787
11794
  * - [`after()`](http://api.jquery.com/after/)
11788
11795
  * - [`append()`](http://api.jquery.com/append/)
11789
11796
  * - [`attr()`](http://api.jquery.com/attr/)
11790
- * - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
11797
+ * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
11791
11798
  * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
11792
11799
  * - [`clone()`](http://api.jquery.com/clone/)
11793
11800
  * - [`contents()`](http://api.jquery.com/contents/)
@@ -11801,6 +11808,7 @@ function publishExternalAPI(angular){
11801
11808
  * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
11802
11809
  * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
11803
11810
  * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
11811
+ * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
11804
11812
  * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
11805
11813
  * - [`prepend()`](http://api.jquery.com/prepend/)
11806
11814
  * - [`prop()`](http://api.jquery.com/prop/)
@@ -11813,7 +11821,7 @@ function publishExternalAPI(angular){
11813
11821
  * - [`text()`](http://api.jquery.com/text/)
11814
11822
  * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
11815
11823
  * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
11816
- * - [`unbind()`](http://api.jquery.com/off/) - Does not support namespaces
11824
+ * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
11817
11825
  * - [`val()`](http://api.jquery.com/val/)
11818
11826
  * - [`wrap()`](http://api.jquery.com/wrap/)
11819
11827
  *
@@ -11853,6 +11861,14 @@ var jqCache = JQLite.cache = {},
11853
11861
  ? function(element, type, fn) {element.removeEventListener(type, fn, false); }
11854
11862
  : function(element, type, fn) {element.detachEvent('on' + type, fn); });
11855
11863
 
11864
+ /*
11865
+ * !!! This is an undocumented "private" function !!!
11866
+ */
11867
+ var jqData = JQLite._data = function(node) {
11868
+ //jQuery always returns an object on cache miss
11869
+ return this.cache[node[this.expando]] || {};
11870
+ };
11871
+
11856
11872
  function jqNextId() { return ++jqId; }
11857
11873
 
11858
11874
 
@@ -11921,6 +11937,9 @@ function JQLite(element) {
11921
11937
  if (element instanceof JQLite) {
11922
11938
  return element;
11923
11939
  }
11940
+ if (isString(element)) {
11941
+ element = trim(element);
11942
+ }
11924
11943
  if (!(this instanceof JQLite)) {
11925
11944
  if (isString(element) && element.charAt(0) != '<') {
11926
11945
  throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
@@ -12392,7 +12411,10 @@ function createEventHandler(element, events) {
12392
12411
  return event.defaultPrevented || event.returnValue === false;
12393
12412
  };
12394
12413
 
12395
- forEach(events[type || event.type], function(fn) {
12414
+ // Copy event handlers in case event handlers array is modified during execution.
12415
+ var eventHandlersCopy = shallowCopy(events[type || event.type] || []);
12416
+
12417
+ forEach(eventHandlersCopy, function(fn) {
12396
12418
  fn.call(element, event);
12397
12419
  });
12398
12420
 
@@ -12488,6 +12510,19 @@ forEach({
12488
12510
 
12489
12511
  off: jqLiteOff,
12490
12512
 
12513
+ one: function(element, type, fn) {
12514
+ element = jqLite(element);
12515
+
12516
+ //add the listener twice so that when it is called
12517
+ //you can remove the original function and still be
12518
+ //able to call element.off(ev, fn) normally
12519
+ element.on(type, function onFn() {
12520
+ element.off(type, fn);
12521
+ element.off(type, onFn);
12522
+ });
12523
+ element.on(type, fn);
12524
+ },
12525
+
12491
12526
  replaceWith: function(element, replaceNode) {
12492
12527
  var index, parent = element.parentNode;
12493
12528
  jqLiteDealoc(element);
@@ -13050,11 +13085,9 @@ function annotate(fn) {
13050
13085
  * @param {(Object|function())} provider If the provider is:
13051
13086
  *
13052
13087
  * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
13053
- * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be
13054
- * created.
13055
- * - `Constructor`: a new instance of the provider will be created using
13056
- * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as
13057
- * `object`.
13088
+ * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created.
13089
+ * - `Constructor`: a new instance of the provider will be created using
13090
+ * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.
13058
13091
  *
13059
13092
  * @returns {Object} registered provider instance
13060
13093
 
@@ -13170,7 +13203,7 @@ function annotate(fn) {
13170
13203
  * constructor function that will be used to instantiate the service instance.
13171
13204
  *
13172
13205
  * You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service
13173
- * as a type/class. This is common when using {@link http://coffeescript.org CoffeeScript}.
13206
+ * as a type/class.
13174
13207
  *
13175
13208
  * @param {string} name The name of the instance.
13176
13209
  * @param {Function} constructor A class (constructor function) that will be instantiated.
@@ -13178,20 +13211,24 @@ function annotate(fn) {
13178
13211
  *
13179
13212
  * @example
13180
13213
  * Here is an example of registering a service using
13181
- * {@link AUTO.$provide#methods_service $provide.service(class)} that is defined as a CoffeeScript class.
13214
+ * {@link AUTO.$provide#methods_service $provide.service(class)}.
13182
13215
  * <pre>
13183
- * class Ping
13184
- * constructor: (@$http)->
13185
- * send: ()=>
13186
- * @$http.get('/ping')
13187
- *
13188
- * $provide.service('ping', ['$http', Ping])
13216
+ * var Ping = function($http) {
13217
+ * this.$http = $http;
13218
+ * };
13219
+ *
13220
+ * Ping.$inject = ['$http'];
13221
+ *
13222
+ * Ping.prototype.send = function() {
13223
+ * return this.$http.get('/ping');
13224
+ * };
13225
+ * $provide.service('ping', Ping);
13189
13226
  * </pre>
13190
13227
  * You would then inject and use this service like this:
13191
13228
  * <pre>
13192
- * someModule.controller 'Ctrl', ['ping', (ping)->
13193
- * ping.send()
13194
- * ]
13229
+ * someModule.controller('Ctrl', ['ping', function(ping) {
13230
+ * ping.send();
13231
+ * }]);
13195
13232
  * </pre>
13196
13233
  */
13197
13234
 
@@ -13283,7 +13320,7 @@ function annotate(fn) {
13283
13320
  * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
13284
13321
  * calls to {@link ng.$log#error $log.warn()}.
13285
13322
  * <pre>
13286
- * $provider.decorator('$log', ['$delegate', function($delegate) {
13323
+ * $provide.decorator('$log', ['$delegate', function($delegate) {
13287
13324
  * $delegate.warn = $delegate.error;
13288
13325
  * return $delegate;
13289
13326
  * }]);
@@ -13436,6 +13473,11 @@ function createInjector(modulesToLoad) {
13436
13473
  path.unshift(serviceName);
13437
13474
  cache[serviceName] = INSTANTIATING;
13438
13475
  return cache[serviceName] = factory(serviceName);
13476
+ } catch (err) {
13477
+ if (cache[serviceName] === INSTANTIATING) {
13478
+ delete cache[serviceName];
13479
+ }
13480
+ throw err;
13439
13481
  } finally {
13440
13482
  path.shift();
13441
13483
  }
@@ -13656,6 +13698,28 @@ var $AnimateProvider = ['$provide', function($provide) {
13656
13698
  $provide.factory(key, factory);
13657
13699
  };
13658
13700
 
13701
+ /**
13702
+ * @ngdoc function
13703
+ * @name ng.$animateProvider#classNameFilter
13704
+ * @methodOf ng.$animateProvider
13705
+ *
13706
+ * @description
13707
+ * Sets and/or returns the CSS class regular expression that is checked when performing
13708
+ * an animation. Upon bootstrap the classNameFilter value is not set at all and will
13709
+ * therefore enable $animate to attempt to perform an animation on any element.
13710
+ * When setting the classNameFilter value, animations will only be performed on elements
13711
+ * that successfully match the filter expression. This in turn can boost performance
13712
+ * for low-powered devices as well as applications containing a lot of structural operations.
13713
+ * @param {RegExp=} expression The className expression which will be checked against all animations
13714
+ * @return {RegExp} The current CSS className expression value. If null then there is no expression value
13715
+ */
13716
+ this.classNameFilter = function(expression) {
13717
+ if(arguments.length === 1) {
13718
+ this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
13719
+ }
13720
+ return this.$$classNameFilter;
13721
+ };
13722
+
13659
13723
  this.$get = ['$timeout', function($timeout) {
13660
13724
 
13661
13725
  /**
@@ -13795,6 +13859,29 @@ var $AnimateProvider = ['$provide', function($provide) {
13795
13859
  done && $timeout(done, 0, false);
13796
13860
  },
13797
13861
 
13862
+ /**
13863
+ *
13864
+ * @ngdoc function
13865
+ * @name ng.$animate#setClass
13866
+ * @methodOf ng.$animate
13867
+ * @function
13868
+ * @description Adds and/or removes the given CSS classes to and from the element.
13869
+ * Once complete, the done() callback will be fired (if provided).
13870
+ * @param {jQuery/jqLite element} element the element which will it's CSS classes changed
13871
+ * removed from it
13872
+ * @param {string} add the CSS classes which will be added to the element
13873
+ * @param {string} remove the CSS class which will be removed from the element
13874
+ * @param {function=} done the callback function (if provided) that will be fired after the
13875
+ * CSS classes have been set on the element
13876
+ */
13877
+ setClass : function(element, add, remove, done) {
13878
+ forEach(element, function (element) {
13879
+ jqLiteAddClass(element, add);
13880
+ jqLiteRemoveClass(element, remove);
13881
+ });
13882
+ done && $timeout(done, 0, false);
13883
+ },
13884
+
13798
13885
  enabled : noop
13799
13886
  };
13800
13887
  }];
@@ -13948,8 +14035,9 @@ function Browser(window, document, $log, $sniffer) {
13948
14035
  * @param {boolean=} replace Should new url replace current history record ?
13949
14036
  */
13950
14037
  self.url = function(url, replace) {
13951
- // Android Browser BFCache causes location reference to become stale.
14038
+ // Android Browser BFCache causes location, history reference to become stale.
13952
14039
  if (location !== window.location) location = window.location;
14040
+ if (history !== window.history) history = window.history;
13953
14041
 
13954
14042
  // setter
13955
14043
  if (url) {
@@ -14001,7 +14089,7 @@ function Browser(window, document, $log, $sniffer) {
14001
14089
  * @description
14002
14090
  * Register callback function that will be called, when url changes.
14003
14091
  *
14004
- * It's only called when the url is changed by outside of angular:
14092
+ * It's only called when the url is changed from outside of angular:
14005
14093
  * - user types different url into address bar
14006
14094
  * - user clicks on history (forward/back) button
14007
14095
  * - user clicks on a link
@@ -14043,7 +14131,7 @@ function Browser(window, document, $log, $sniffer) {
14043
14131
  /**
14044
14132
  * @name ng.$browser#baseHref
14045
14133
  * @methodOf ng.$browser
14046
- *
14134
+ *
14047
14135
  * @description
14048
14136
  * Returns current <base href>
14049
14137
  * (always relative - without domain)
@@ -14052,7 +14140,7 @@ function Browser(window, document, $log, $sniffer) {
14052
14140
  */
14053
14141
  self.baseHref = function() {
14054
14142
  var href = baseElement.attr('href');
14055
- return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
14143
+ return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
14056
14144
  };
14057
14145
 
14058
14146
  //////////////////////////////////////////////////////////////
@@ -14074,13 +14162,13 @@ function Browser(window, document, $log, $sniffer) {
14074
14162
  * It is not meant to be used directly, use the $cookie service instead.
14075
14163
  *
14076
14164
  * The return values vary depending on the arguments that the method was called with as follows:
14077
- *
14165
+ *
14078
14166
  * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify
14079
14167
  * it
14080
14168
  * - cookies(name, value) -> set name to value, if value is undefined delete the cookie
14081
14169
  * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that
14082
14170
  * way)
14083
- *
14171
+ *
14084
14172
  * @returns {Object} Hash of all cookies (if called without any parameter)
14085
14173
  */
14086
14174
  self.cookies = function(name, value) {
@@ -14458,7 +14546,7 @@ function $TemplateCacheProvider() {
14458
14546
  * @function
14459
14547
  *
14460
14548
  * @description
14461
- * Compiles a piece of HTML string or DOM into a template and produces a template function, which
14549
+ * Compiles an HTML string or DOM into a template and produces a template function, which
14462
14550
  * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
14463
14551
  *
14464
14552
  * The compilation is a process of walking the DOM tree and matching DOM elements to
@@ -14858,13 +14946,17 @@ function $TemplateCacheProvider() {
14858
14946
  <div compile="html"></div>
14859
14947
  </div>
14860
14948
  </doc:source>
14861
- <doc:scenario>
14949
+ <doc:protractor>
14862
14950
  it('should auto compile', function() {
14863
- expect(element('div[compile]').text()).toBe('Hello Angular');
14864
- input('html').enter('{{name}}!');
14865
- expect(element('div[compile]').text()).toBe('Angular!');
14951
+ var textarea = $('textarea');
14952
+ var output = $('div[compile]');
14953
+ // The initial state reads 'Hello Angular'.
14954
+ expect(output.getText()).toBe('Hello Angular');
14955
+ textarea.clear();
14956
+ textarea.sendKeys('{{name}}!');
14957
+ expect(output.getText()).toBe('Angular!');
14866
14958
  });
14867
- </doc:scenario>
14959
+ </doc:protractor>
14868
14960
  </doc:example>
14869
14961
 
14870
14962
  *
@@ -14903,14 +14995,14 @@ function $TemplateCacheProvider() {
14903
14995
  * example would not point to the clone, but rather to the original template that was cloned. In
14904
14996
  * this case, you can access the clone via the cloneAttachFn:
14905
14997
  * <pre>
14906
- * var templateHTML = angular.element('<p>{{total}}</p>'),
14998
+ * var templateElement = angular.element('<p>{{total}}</p>'),
14907
14999
  * scope = ....;
14908
15000
  *
14909
- * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
15001
+ * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
14910
15002
  * //attach the clone to DOM document at the right place
14911
15003
  * });
14912
15004
  *
14913
- * //now we have reference to the cloned DOM via `clone`
15005
+ * //now we have reference to the cloned DOM via `clonedElement`
14914
15006
  * </pre>
14915
15007
  *
14916
15008
  *
@@ -14932,7 +15024,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
14932
15024
  var hasDirectives = {},
14933
15025
  Suffix = 'Directive',
14934
15026
  COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
14935
- CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
15027
+ CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
15028
+ TABLE_CONTENT_REGEXP = /^<\s*(tr|th|td|tbody)(\s+[^>]*)?>/i;
14936
15029
 
14937
15030
  // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
14938
15031
  // The assumption is that future DOM event attribute names will begin with
@@ -15119,8 +15212,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15119
15212
  * @param {string} oldClasses The former CSS className value
15120
15213
  */
15121
15214
  $updateClass : function(newClasses, oldClasses) {
15122
- this.$removeClass(tokenDifference(oldClasses, newClasses));
15123
- this.$addClass(tokenDifference(newClasses, oldClasses));
15215
+ var toAdd = tokenDifference(newClasses, oldClasses);
15216
+ var toRemove = tokenDifference(oldClasses, newClasses);
15217
+
15218
+ if(toAdd.length === 0) {
15219
+ $animate.removeClass(this.$$element, toRemove);
15220
+ } else if(toRemove.length === 0) {
15221
+ $animate.addClass(this.$$element, toAdd);
15222
+ } else {
15223
+ $animate.setClass(this.$$element, toAdd, toRemove);
15224
+ }
15124
15225
  },
15125
15226
 
15126
15227
  /**
@@ -15252,6 +15353,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15252
15353
  var compositeLinkFn =
15253
15354
  compileNodes($compileNodes, transcludeFn, $compileNodes,
15254
15355
  maxPriority, ignoreDirective, previousCompileContext);
15356
+ safeAddClass($compileNodes, 'ng-scope');
15255
15357
  return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){
15256
15358
  assertArg(scope, 'scope');
15257
15359
  // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
@@ -15266,12 +15368,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15266
15368
 
15267
15369
  // Attach scope only to non-text nodes.
15268
15370
  for(var i = 0, ii = $linkNode.length; i<ii; i++) {
15269
- var node = $linkNode[i];
15270
- if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
15371
+ var node = $linkNode[i],
15372
+ nodeType = node.nodeType;
15373
+ if (nodeType === 1 /* element */ || nodeType === 9 /* document */) {
15271
15374
  $linkNode.eq(i).data('$scope', scope);
15272
15375
  }
15273
15376
  }
15274
- safeAddClass($linkNode, 'ng-scope');
15377
+
15275
15378
  if (cloneConnectFn) cloneConnectFn($linkNode, scope);
15276
15379
  if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
15277
15380
  return $linkNode;
@@ -15299,15 +15402,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15299
15402
  * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then
15300
15403
  * the rootElement must be set the jqLite collection of the compile root. This is
15301
15404
  * needed so that the jqLite collection items can be replaced with widgets.
15302
- * @param {number=} max directive priority
15405
+ * @param {number=} maxPriority Max directive priority.
15303
15406
  * @returns {?function} A composite linking function of all of the matched directives or null.
15304
15407
  */
15305
15408
  function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
15306
15409
  previousCompileContext) {
15307
15410
  var linkFns = [],
15308
- nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
15411
+ attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound;
15309
15412
 
15310
- for(var i = 0; i < nodeList.length; i++) {
15413
+ for (var i = 0; i < nodeList.length; i++) {
15311
15414
  attrs = new Attributes();
15312
15415
 
15313
15416
  // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
@@ -15319,16 +15422,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15319
15422
  null, [], [], previousCompileContext)
15320
15423
  : null;
15321
15424
 
15425
+ if (nodeLinkFn && nodeLinkFn.scope) {
15426
+ safeAddClass(jqLite(nodeList[i]), 'ng-scope');
15427
+ }
15428
+
15322
15429
  childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
15323
- !nodeList[i].childNodes ||
15324
- !nodeList[i].childNodes.length)
15430
+ !(childNodes = nodeList[i].childNodes) ||
15431
+ !childNodes.length)
15325
15432
  ? null
15326
- : compileNodes(nodeList[i].childNodes,
15433
+ : compileNodes(childNodes,
15327
15434
  nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
15328
15435
 
15329
- linkFns.push(nodeLinkFn);
15330
- linkFns.push(childLinkFn);
15331
- linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
15436
+ linkFns.push(nodeLinkFn, childLinkFn);
15437
+ linkFnFound = linkFnFound || nodeLinkFn || childLinkFn;
15332
15438
  //use the previous context only for the first element in the virtual group
15333
15439
  previousCompileContext = null;
15334
15440
  }
@@ -15340,9 +15446,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15340
15446
  var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n;
15341
15447
 
15342
15448
  // copy nodeList so that linking doesn't break due to live list updates.
15343
- var stableNodeList = [];
15344
- for (i = 0, ii = nodeList.length; i < ii; i++) {
15345
- stableNodeList.push(nodeList[i]);
15449
+ var nodeListLength = nodeList.length,
15450
+ stableNodeList = new Array(nodeListLength);
15451
+ for (i = 0; i < nodeListLength; i++) {
15452
+ stableNodeList[i] = nodeList[i];
15346
15453
  }
15347
15454
 
15348
15455
  for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
@@ -15355,7 +15462,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15355
15462
  if (nodeLinkFn.scope) {
15356
15463
  childScope = scope.$new();
15357
15464
  $node.data('$scope', childScope);
15358
- safeAddClass($node, 'ng-scope');
15359
15465
  } else {
15360
15466
  childScope = scope;
15361
15467
  }
@@ -15438,9 +15544,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15438
15544
 
15439
15545
  nName = directiveNormalize(name.toLowerCase());
15440
15546
  attrsMap[nName] = name;
15441
- attrs[nName] = value = trim((msie && name == 'href')
15442
- ? decodeURIComponent(node.getAttribute(name, 2))
15443
- : attr.value);
15547
+ attrs[nName] = value = trim(attr.value);
15444
15548
  if (getBooleanAttrName(node, nName)) {
15445
15549
  attrs[nName] = true; // presence means true
15446
15550
  }
@@ -15569,7 +15673,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15569
15673
  templateDirective = previousCompileContext.templateDirective,
15570
15674
  nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
15571
15675
  hasTranscludeDirective = false,
15572
- hasElementTranscludeDirective = false,
15676
+ hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
15573
15677
  $compileNode = templateAttrs.$$element = jqLite(compileNode),
15574
15678
  directive,
15575
15679
  directiveName,
@@ -15623,7 +15727,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15623
15727
  hasTranscludeDirective = true;
15624
15728
 
15625
15729
  // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
15626
- // This option should only be used by directives that know how to how to safely handle element transclusion,
15730
+ // This option should only be used by directives that know how to safely handle element transclusion,
15627
15731
  // where the transcluded nodes are added or replaced after linking.
15628
15732
  if (!directive.$$tlb) {
15629
15733
  assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
@@ -15670,9 +15774,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15670
15774
 
15671
15775
  if (directive.replace) {
15672
15776
  replaceDirective = directive;
15673
- $template = jqLite('<div>' +
15674
- trim(directiveValue) +
15675
- '</div>').contents();
15777
+ $template = directiveTemplateContents(directiveValue);
15676
15778
  compileNode = $template[0];
15677
15779
 
15678
15780
  if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -15743,6 +15845,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15743
15845
 
15744
15846
  nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
15745
15847
  nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
15848
+ previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
15746
15849
 
15747
15850
  // might be normal or delayed nodeLinkFn depending on if templateUrl is present
15748
15851
  return nodeLinkFn;
@@ -16070,6 +16173,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16070
16173
  }
16071
16174
 
16072
16175
 
16176
+ function directiveTemplateContents(template) {
16177
+ var type;
16178
+ template = trim(template);
16179
+ if ((type = TABLE_CONTENT_REGEXP.exec(template))) {
16180
+ type = type[1].toLowerCase();
16181
+ var table = jqLite('<table>' + template + '</table>'),
16182
+ tbody = table.children('tbody'),
16183
+ leaf = /(td|th)/.test(type) && table.find('tr');
16184
+ if (tbody.length && type !== 'tbody') {
16185
+ table = tbody;
16186
+ }
16187
+ if (leaf && leaf.length) {
16188
+ table = leaf;
16189
+ }
16190
+ return table.contents();
16191
+ }
16192
+ return jqLite('<div>' +
16193
+ template +
16194
+ '</div>').contents();
16195
+ }
16196
+
16197
+
16073
16198
  function compileTemplateUrl(directives, $compileNode, tAttrs,
16074
16199
  $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
16075
16200
  var linkQueue = [],
@@ -16094,7 +16219,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16094
16219
  content = denormalizeTemplate(content);
16095
16220
 
16096
16221
  if (origAsyncDirective.replace) {
16097
- $template = jqLite('<div>' + trim(content) + '</div>').contents();
16222
+ $template = directiveTemplateContents(content);
16098
16223
  compileNode = $template[0];
16099
16224
 
16100
16225
  if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -16138,9 +16263,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16138
16263
  linkNode = $compileNode[0];
16139
16264
 
16140
16265
  if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
16141
- // it was cloned therefore we have to clone as well.
16142
- linkNode = jqLiteClone(compileNode);
16266
+ var oldClasses = beforeTemplateLinkNode.className;
16267
+
16268
+ if (!(previousCompileContext.hasElementTranscludeDirective &&
16269
+ origAsyncDirective.replace)) {
16270
+ // it was cloned therefore we have to clone as well.
16271
+ linkNode = jqLiteClone(compileNode);
16272
+ }
16273
+
16143
16274
  replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
16275
+
16276
+ // Copy in CSS classes from original node
16277
+ safeAddClass(jqLite(linkNode), oldClasses);
16144
16278
  }
16145
16279
  if (afterTemplateNodeLinkFn.transclude) {
16146
16280
  childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude);
@@ -16388,7 +16522,7 @@ function directiveNormalize(name) {
16388
16522
  *
16389
16523
  *
16390
16524
  * @param {string} name Normalized element attribute name of the property to modify. The name is
16391
- * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
16525
+ * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
16392
16526
  * property to the original name.
16393
16527
  * @param {string} value Value to set the attribute to. The value can be an interpolated string.
16394
16528
  */
@@ -16526,8 +16660,7 @@ function $ControllerProvider() {
16526
16660
  * @requires $window
16527
16661
  *
16528
16662
  * @description
16529
- * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
16530
- * element.
16663
+ * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object.
16531
16664
  */
16532
16665
  function $DocumentProvider(){
16533
16666
  this.$get = ['$window', function(window){
@@ -16686,9 +16819,9 @@ function $HttpProvider() {
16686
16819
  common: {
16687
16820
  'Accept': 'application/json, text/plain, */*'
16688
16821
  },
16689
- post: CONTENT_TYPE_APPLICATION_JSON,
16690
- put: CONTENT_TYPE_APPLICATION_JSON,
16691
- patch: CONTENT_TYPE_APPLICATION_JSON
16822
+ post: copy(CONTENT_TYPE_APPLICATION_JSON),
16823
+ put: copy(CONTENT_TYPE_APPLICATION_JSON),
16824
+ patch: copy(CONTENT_TYPE_APPLICATION_JSON)
16692
16825
  },
16693
16826
 
16694
16827
  xsrfCookieName: 'XSRF-TOKEN',
@@ -16797,32 +16930,15 @@ function $HttpProvider() {
16797
16930
  * will result in the success callback being called. Note that if the response is a redirect,
16798
16931
  * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
16799
16932
  * called for such responses.
16800
- *
16801
- * # Calling $http from outside AngularJS
16802
- * The `$http` service will not actually send the request until the next `$digest()` is
16803
- * executed. Normally this is not an issue, since almost all the time your call to `$http` will
16804
- * be from within a `$apply()` block.
16805
- * If you are calling `$http` from outside Angular, then you should wrap it in a call to
16806
- * `$apply` to cause a $digest to occur and also to handle errors in the block correctly.
16807
- *
16808
- * ```
16809
- * $scope.$apply(function() {
16810
- * $http(...);
16811
- * });
16812
- * ```
16813
16933
  *
16814
16934
  * # Writing Unit Tests that use $http
16815
- * When unit testing you are mostly responsible for scheduling the `$digest` cycle. If you do
16816
- * not trigger a `$digest` before calling `$httpBackend.flush()` then the request will not have
16817
- * been made and `$httpBackend.expect(...)` expectations will fail. The solution is to run the
16818
- * code that calls the `$http()` method inside a $apply block as explained in the previous
16819
- * section.
16935
+ * When unit testing (using {@link api/ngMock ngMock}), it is necessary to call
16936
+ * {@link api/ngMock.$httpBackend#methods_flush $httpBackend.flush()} to flush each pending
16937
+ * request using trained responses.
16820
16938
  *
16821
16939
  * ```
16822
16940
  * $httpBackend.expectGET(...);
16823
- * $scope.$apply(function() {
16824
- * $http.get(...);
16825
- * });
16941
+ * $http.get(...);
16826
16942
  * $httpBackend.flush();
16827
16943
  * ```
16828
16944
  *
@@ -16866,7 +16982,15 @@ function $HttpProvider() {
16866
16982
  * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }.
16867
16983
  *
16868
16984
  * The defaults can also be set at runtime via the `$http.defaults` object in the same
16869
- * fashion. In addition, you can supply a `headers` property in the config object passed when
16985
+ * fashion. For example:
16986
+ *
16987
+ * ```
16988
+ * module.run(function($http) {
16989
+ * $http.defaults.headers.common.Authentication = 'Basic YmVlcDpib29w'
16990
+ * });
16991
+ * ```
16992
+ *
16993
+ * In addition, you can supply a `headers` property in the config object passed when
16870
16994
  * calling `$http(config)`, which overrides the defaults without changing them globally.
16871
16995
  *
16872
16996
  *
@@ -16890,7 +17014,9 @@ function $HttpProvider() {
16890
17014
  * properties. These properties are by default an array of transform functions, which allows you
16891
17015
  * to `push` or `unshift` a new transformation function into the transformation chain. You can
16892
17016
  * also decide to completely override any default transformations by assigning your
16893
- * transformation functions to these properties directly without the array wrapper.
17017
+ * transformation functions to these properties directly without the array wrapper. These defaults
17018
+ * are again available on the $http factory at run-time, which may be useful if you have run-time
17019
+ * services you wish to be involved in your transformations.
16894
17020
  *
16895
17021
  * Similarly, to locally override the request/response transforms, augment the
16896
17022
  * `transformRequest` and/or `transformResponse` properties of the configuration object passed
@@ -16984,19 +17110,20 @@ function $HttpProvider() {
16984
17110
  * return responseOrNewPromise
16985
17111
  * }
16986
17112
  * return $q.reject(rejection);
16987
- * };
16988
- * }
17113
+ * }
17114
+ * };
16989
17115
  * });
16990
17116
  *
16991
17117
  * $httpProvider.interceptors.push('myHttpInterceptor');
16992
17118
  *
16993
17119
  *
16994
- * // register the interceptor via an anonymous factory
17120
+ * // alternatively, register the interceptor via an anonymous factory
16995
17121
  * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
16996
17122
  * return {
16997
17123
  * 'request': function(config) {
16998
17124
  * // same as above
16999
17125
  * },
17126
+ *
17000
17127
  * 'response': function(response) {
17001
17128
  * // same as above
17002
17129
  * }
@@ -17103,7 +17230,8 @@ function $HttpProvider() {
17103
17230
  * for added security.
17104
17231
  *
17105
17232
  * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
17106
- * properties of either $httpProvider.defaults, or the per-request config object.
17233
+ * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
17234
+ * or the per-request config object.
17107
17235
  *
17108
17236
  *
17109
17237
  * @param {object} config Object describing the request to be made and how it should be
@@ -17167,14 +17295,14 @@ function $HttpProvider() {
17167
17295
  <option>JSONP</option>
17168
17296
  </select>
17169
17297
  <input type="text" ng-model="url" size="80"/>
17170
- <button ng-click="fetch()">fetch</button><br>
17171
- <button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
17172
- <button
17298
+ <button id="fetchbtn" ng-click="fetch()">fetch</button><br>
17299
+ <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
17300
+ <button id="samplejsonpbtn"
17173
17301
  ng-click="updateModel('JSONP',
17174
17302
  'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
17175
17303
  Sample JSONP
17176
17304
  </button>
17177
- <button
17305
+ <button id="invalidjsonpbtn"
17178
17306
  ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
17179
17307
  Invalid JSONP
17180
17308
  </button>
@@ -17211,27 +17339,34 @@ function $HttpProvider() {
17211
17339
  <file name="http-hello.html">
17212
17340
  Hello, $http!
17213
17341
  </file>
17214
- <file name="scenario.js">
17342
+ <file name="protractorTest.js">
17343
+ var status = element(by.binding('status'));
17344
+ var data = element(by.binding('data'));
17345
+ var fetchBtn = element(by.id('fetchbtn'));
17346
+ var sampleGetBtn = element(by.id('samplegetbtn'));
17347
+ var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
17348
+ var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
17349
+
17215
17350
  it('should make an xhr GET request', function() {
17216
- element(':button:contains("Sample GET")').click();
17217
- element(':button:contains("fetch")').click();
17218
- expect(binding('status')).toBe('200');
17219
- expect(binding('data')).toMatch(/Hello, \$http!/);
17351
+ sampleGetBtn.click();
17352
+ fetchBtn.click();
17353
+ expect(status.getText()).toMatch('200');
17354
+ expect(data.getText()).toMatch(/Hello, \$http!/)
17220
17355
  });
17221
17356
 
17222
17357
  it('should make a JSONP request to angularjs.org', function() {
17223
- element(':button:contains("Sample JSONP")').click();
17224
- element(':button:contains("fetch")').click();
17225
- expect(binding('status')).toBe('200');
17226
- expect(binding('data')).toMatch(/Super Hero!/);
17358
+ sampleJsonpBtn.click();
17359
+ fetchBtn.click();
17360
+ expect(status.getText()).toMatch('200');
17361
+ expect(data.getText()).toMatch(/Super Hero!/);
17227
17362
  });
17228
17363
 
17229
17364
  it('should make JSONP request to invalid URL and invoke the error handler',
17230
17365
  function() {
17231
- element(':button:contains("Invalid JSONP")').click();
17232
- element(':button:contains("fetch")').click();
17233
- expect(binding('status')).toBe('0');
17234
- expect(binding('data')).toBe('Request failed');
17366
+ invalidJsonpBtn.click();
17367
+ fetchBtn.click();
17368
+ expect(status.getText()).toMatch('0');
17369
+ expect(data.getText()).toMatch('Request failed');
17235
17370
  });
17236
17371
  </file>
17237
17372
  </example>
@@ -17612,14 +17747,19 @@ function $HttpProvider() {
17612
17747
  }];
17613
17748
  }
17614
17749
 
17615
- var XHR = window.XMLHttpRequest || function() {
17616
- /* global ActiveXObject */
17617
- try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
17618
- try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
17619
- try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
17620
- throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
17621
- };
17750
+ function createXhr(method) {
17751
+ //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest
17752
+ //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest
17753
+ //if it is available
17754
+ if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) ||
17755
+ !window.XMLHttpRequest)) {
17756
+ return new window.ActiveXObject("Microsoft.XMLHTTP");
17757
+ } else if (window.XMLHttpRequest) {
17758
+ return new window.XMLHttpRequest();
17759
+ }
17622
17760
 
17761
+ throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
17762
+ }
17623
17763
 
17624
17764
  /**
17625
17765
  * @ngdoc object
@@ -17640,11 +17780,11 @@ var XHR = window.XMLHttpRequest || function() {
17640
17780
  */
17641
17781
  function $HttpBackendProvider() {
17642
17782
  this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
17643
- return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, $document[0]);
17783
+ return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]);
17644
17784
  }];
17645
17785
  }
17646
17786
 
17647
- function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument) {
17787
+ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
17648
17788
  var ABORTED = -1;
17649
17789
 
17650
17790
  // TODO(vojta): fix the signature
@@ -17666,10 +17806,12 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
17666
17806
  } else {
17667
17807
  completeRequest(callback, status || -2);
17668
17808
  }
17669
- delete callbacks[callbackId];
17809
+ callbacks[callbackId] = angular.noop;
17670
17810
  });
17671
17811
  } else {
17672
- var xhr = new XHR();
17812
+
17813
+ var xhr = createXhr(method);
17814
+
17673
17815
  xhr.open(method, url, true);
17674
17816
  forEach(headers, function(value, key) {
17675
17817
  if (isDefined(value)) {
@@ -17681,17 +17823,25 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
17681
17823
  // response is in the cache. the promise api will ensure that to the app code the api is
17682
17824
  // always async
17683
17825
  xhr.onreadystatechange = function() {
17684
- if (xhr.readyState == 4) {
17826
+ // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by
17827
+ // xhrs that are resolved while the app is in the background (see #5426).
17828
+ // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before
17829
+ // continuing
17830
+ //
17831
+ // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and
17832
+ // Safari respectively.
17833
+ if (xhr && xhr.readyState == 4) {
17685
17834
  var responseHeaders = null,
17686
17835
  response = null;
17687
17836
 
17688
17837
  if(status !== ABORTED) {
17689
17838
  responseHeaders = xhr.getAllResponseHeaders();
17690
- response = xhr.responseType ? xhr.response : xhr.responseText;
17839
+
17840
+ // responseText is the old-school way of retrieving response (supported by IE8 & 9)
17841
+ // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
17842
+ response = ('response' in xhr) ? xhr.response : xhr.responseText;
17691
17843
  }
17692
17844
 
17693
- // responseText is the old-school way of retrieving response (supported by IE8 & 9)
17694
- // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
17695
17845
  completeRequest(callback,
17696
17846
  status || xhr.status,
17697
17847
  response,
@@ -17704,7 +17854,20 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
17704
17854
  }
17705
17855
 
17706
17856
  if (responseType) {
17707
- xhr.responseType = responseType;
17857
+ try {
17858
+ xhr.responseType = responseType;
17859
+ } catch (e) {
17860
+ // WebKit added support for the json responseType value on 09/03/2013
17861
+ // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
17862
+ // known to throw when setting the value "json" as the response type. Other older
17863
+ // browsers implementing the responseType
17864
+ //
17865
+ // The json response type can be ignored if not supported, because JSON payloads are
17866
+ // parsed on the client-side regardless.
17867
+ if (responseType !== 'json') {
17868
+ throw e;
17869
+ }
17870
+ }
17708
17871
  }
17709
17872
 
17710
17873
  xhr.send(post || null);
@@ -17724,14 +17887,14 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument)
17724
17887
  }
17725
17888
 
17726
17889
  function completeRequest(callback, status, response, headersString) {
17727
- var protocol = urlResolve(url).protocol;
17728
-
17729
17890
  // cancel timeout and subsequent timeout promise resolution
17730
17891
  timeoutId && $browserDefer.cancel(timeoutId);
17731
17892
  jsonpDone = xhr = null;
17732
17893
 
17733
- // fix status code for file protocol (it's always 0)
17734
- status = (protocol == 'file' && status === 0) ? (response ? 200 : 404) : status;
17894
+ // fix status code when it is 0 (0 status is undocumented).
17895
+ // Occurs when accessing file resources.
17896
+ // On Android 4.1 stock browser it occurs while retrieving files from application cache.
17897
+ status = (status === 0) ? (response ? 200 : 404) : status;
17735
17898
 
17736
17899
  // normalize IE bug (http://bugs.jquery.com/ticket/1450)
17737
17900
  status = status == 1223 ? 204 : status;
@@ -17803,11 +17966,11 @@ var $interpolateMinErr = minErr('$interpolate');
17803
17966
  //demo.label//
17804
17967
  </div>
17805
17968
  </doc:source>
17806
- <doc:scenario>
17807
- it('should interpolate binding with custom symbols', function() {
17808
- expect(binding('demo.label')).toBe('This binding is brought you by // interpolation symbols.');
17809
- });
17810
- </doc:scenario>
17969
+ <doc:protractor>
17970
+ it('should interpolate binding with custom symbols', function() {
17971
+ expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
17972
+ });
17973
+ </doc:protractor>
17811
17974
  </doc:example>
17812
17975
  */
17813
17976
  function $InterpolateProvider() {
@@ -17999,7 +18162,7 @@ function $InterpolateProvider() {
17999
18162
  * @description
18000
18163
  * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
18001
18164
  *
18002
- * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
18165
+ * Use {@link ng.$interpolateProvider#methods_endSymbol $interpolateProvider#endSymbol} to change
18003
18166
  * the symbol.
18004
18167
  *
18005
18168
  * @returns {string} start symbol.
@@ -18036,6 +18199,14 @@ function $IntervalProvider() {
18036
18199
  * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
18037
18200
  * time.
18038
18201
  *
18202
+ * <div class="alert alert-warning">
18203
+ * **Note**: Intervals created by this service must be explicitly destroyed when you are finished
18204
+ * with them. In particular they are not automatically destroyed when a controller's scope or a
18205
+ * directive's element are destroyed.
18206
+ * You should take this into consideration and make sure to always cancel the interval at the
18207
+ * appropriate moment. See the example below for more details on how and when to do this.
18208
+ * </div>
18209
+ *
18039
18210
  * @param {function()} fn A function that should be called repeatedly.
18040
18211
  * @param {number} delay Number of milliseconds between each function call.
18041
18212
  * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
@@ -18043,6 +18214,95 @@ function $IntervalProvider() {
18043
18214
  * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
18044
18215
  * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block.
18045
18216
  * @returns {promise} A promise which will be notified on each iteration.
18217
+ *
18218
+ * @example
18219
+ <doc:example module="time">
18220
+ <doc:source>
18221
+ <script>
18222
+ function Ctrl2($scope,$interval) {
18223
+ $scope.format = 'M/d/yy h:mm:ss a';
18224
+ $scope.blood_1 = 100;
18225
+ $scope.blood_2 = 120;
18226
+
18227
+ var stop;
18228
+ $scope.fight = function() {
18229
+ // Don't start a new fight if we are already fighting
18230
+ if ( angular.isDefined(stop) ) return;
18231
+
18232
+ stop = $interval(function() {
18233
+ if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
18234
+ $scope.blood_1 = $scope.blood_1 - 3;
18235
+ $scope.blood_2 = $scope.blood_2 - 4;
18236
+ } else {
18237
+ $scope.stopFight();
18238
+ }
18239
+ }, 100);
18240
+ };
18241
+
18242
+ $scope.stopFight = function() {
18243
+ if (angular.isDefined(stop)) {
18244
+ $interval.cancel(stop);
18245
+ stop = undefined;
18246
+ }
18247
+ };
18248
+
18249
+ $scope.resetFight = function() {
18250
+ $scope.blood_1 = 100;
18251
+ $scope.blood_2 = 120;
18252
+ }
18253
+
18254
+ $scope.$on('$destroy', function() {
18255
+ // Make sure that the interval is destroyed too
18256
+ $scope.stopFight();
18257
+ });
18258
+ }
18259
+
18260
+ angular.module('time', [])
18261
+ // Register the 'myCurrentTime' directive factory method.
18262
+ // We inject $interval and dateFilter service since the factory method is DI.
18263
+ .directive('myCurrentTime', function($interval, dateFilter) {
18264
+ // return the directive link function. (compile function not needed)
18265
+ return function(scope, element, attrs) {
18266
+ var format, // date format
18267
+ stopTime; // so that we can cancel the time updates
18268
+
18269
+ // used to update the UI
18270
+ function updateTime() {
18271
+ element.text(dateFilter(new Date(), format));
18272
+ }
18273
+
18274
+ // watch the expression, and update the UI on change.
18275
+ scope.$watch(attrs.myCurrentTime, function(value) {
18276
+ format = value;
18277
+ updateTime();
18278
+ });
18279
+
18280
+ stopTime = $interval(updateTime, 1000);
18281
+
18282
+ // listen on DOM destroy (removal) event, and cancel the next UI update
18283
+ // to prevent updating time ofter the DOM element was removed.
18284
+ element.bind('$destroy', function() {
18285
+ $interval.cancel(stopTime);
18286
+ });
18287
+ }
18288
+ });
18289
+ </script>
18290
+
18291
+ <div>
18292
+ <div ng-controller="Ctrl2">
18293
+ Date format: <input ng-model="format"> <hr/>
18294
+ Current time is: <span my-current-time="format"></span>
18295
+ <hr/>
18296
+ Blood 1 : <font color='red'>{{blood_1}}</font>
18297
+ Blood 2 : <font color='red'>{{blood_2}}</font>
18298
+ <button type="button" data-ng-click="fight()">Fight</button>
18299
+ <button type="button" data-ng-click="stopFight()">StopFight</button>
18300
+ <button type="button" data-ng-click="resetFight()">resetFight</button>
18301
+ </div>
18302
+ </div>
18303
+
18304
+ </doc:source>
18305
+ </doc:example>
18046
18306
  */
18047
18307
  function interval(fn, delay, count, invokeApply) {
18048
18308
  var setInterval = $window.setInterval,
@@ -18051,8 +18311,8 @@ function $IntervalProvider() {
18051
18311
  promise = deferred.promise,
18052
18312
  iteration = 0,
18053
18313
  skipApply = (isDefined(invokeApply) && !invokeApply);
18054
-
18055
- count = isDefined(count) ? count : 0,
18314
+
18315
+ count = isDefined(count) ? count : 0;
18056
18316
 
18057
18317
  promise.then(null, null, fn);
18058
18318
 
@@ -18746,9 +19006,9 @@ function $LocationProvider(){
18746
19006
  * @eventType broadcast on root scope
18747
19007
  * @description
18748
19008
  * Broadcasted before a URL will change. This change can be prevented by calling
18749
- * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
19009
+ * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#methods_$on} for more
18750
19010
  * details about event object. Upon successful change
18751
- * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
19011
+ * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
18752
19012
  *
18753
19013
  * @param {Object} angularEvent Synthetic event object.
18754
19014
  * @param {string} newUrl New URL
@@ -18801,6 +19061,13 @@ function $LocationProvider(){
18801
19061
  }
18802
19062
 
18803
19063
  var absHref = elm.prop('href');
19064
+
19065
+ if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
19066
+ // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
19067
+ // an animation.
19068
+ absHref = urlResolve(absHref.animVal).href;
19069
+ }
19070
+
18804
19071
  var rewrittenUrl = $location.$$rewrite(absHref);
18805
19072
 
18806
19073
  if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) {
@@ -18824,16 +19091,17 @@ function $LocationProvider(){
18824
19091
  // update $location when $browser url changes
18825
19092
  $browser.onUrlChange(function(newUrl) {
18826
19093
  if ($location.absUrl() != newUrl) {
18827
- if ($rootScope.$broadcast('$locationChangeStart', newUrl,
18828
- $location.absUrl()).defaultPrevented) {
18829
- $browser.url($location.absUrl());
18830
- return;
18831
- }
18832
19094
  $rootScope.$evalAsync(function() {
18833
19095
  var oldUrl = $location.absUrl();
18834
19096
 
18835
19097
  $location.$$parse(newUrl);
18836
- afterLocationChange(oldUrl);
19098
+ if ($rootScope.$broadcast('$locationChangeStart', newUrl,
19099
+ oldUrl).defaultPrevented) {
19100
+ $location.$$parse(oldUrl);
19101
+ $browser.url(oldUrl);
19102
+ } else {
19103
+ afterLocationChange(oldUrl);
19104
+ }
18837
19105
  });
18838
19106
  if (!$rootScope.$$phase) $rootScope.$digest();
18839
19107
  }
@@ -18921,7 +19189,7 @@ function $LogProvider(){
18921
19189
  * @name ng.$logProvider#debugEnabled
18922
19190
  * @methodOf ng.$logProvider
18923
19191
  * @description
18924
- * @param {string=} flag enable or disable debug level messages
19192
+ * @param {boolean=} flag enable or disable debug level messages
18925
19193
  * @returns {*} current value if used as getter or itself (chaining) if used as setter
18926
19194
  */
18927
19195
  this.debugEnabled = function(flag) {
@@ -19009,9 +19277,16 @@ function $LogProvider(){
19009
19277
 
19010
19278
  function consoleLog(type) {
19011
19279
  var console = $window.console || {},
19012
- logFn = console[type] || console.log || noop;
19280
+ logFn = console[type] || console.log || noop,
19281
+ hasApply = false;
19282
+
19283
+ // Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
19284
+ // The reason behind this is that console.log has type "object" in IE8...
19285
+ try {
19286
+ hasApply = !! logFn.apply;
19287
+ } catch (e) {}
19013
19288
 
19014
- if (logFn.apply) {
19289
+ if (hasApply) {
19015
19290
  return function() {
19016
19291
  var args = [];
19017
19292
  forEach(arguments, function(arg) {
@@ -19737,7 +20012,7 @@ Parser.prototype = {
19737
20012
  var getter = getterFn(field, this.options, this.text);
19738
20013
 
19739
20014
  return extend(function(scope, locals, self) {
19740
- return getter(self || object(scope, locals), locals);
20015
+ return getter(self || object(scope, locals));
19741
20016
  }, {
19742
20017
  assign: function(scope, value, locals) {
19743
20018
  return setter(object(scope, locals), field, value, parser.text, parser.options);
@@ -19921,19 +20196,23 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
19921
20196
  ? function cspSafeGetter(scope, locals) {
19922
20197
  var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
19923
20198
 
19924
- if (pathVal === null || pathVal === undefined) return pathVal;
20199
+ if (pathVal == null) return pathVal;
19925
20200
  pathVal = pathVal[key0];
19926
20201
 
19927
- if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
20202
+ if (!key1) return pathVal;
20203
+ if (pathVal == null) return undefined;
19928
20204
  pathVal = pathVal[key1];
19929
20205
 
19930
- if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
20206
+ if (!key2) return pathVal;
20207
+ if (pathVal == null) return undefined;
19931
20208
  pathVal = pathVal[key2];
19932
20209
 
19933
- if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
20210
+ if (!key3) return pathVal;
20211
+ if (pathVal == null) return undefined;
19934
20212
  pathVal = pathVal[key3];
19935
20213
 
19936
- if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
20214
+ if (!key4) return pathVal;
20215
+ if (pathVal == null) return undefined;
19937
20216
  pathVal = pathVal[key4];
19938
20217
 
19939
20218
  return pathVal;
@@ -19942,7 +20221,7 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
19942
20221
  var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
19943
20222
  promise;
19944
20223
 
19945
- if (pathVal === null || pathVal === undefined) return pathVal;
20224
+ if (pathVal == null) return pathVal;
19946
20225
 
19947
20226
  pathVal = pathVal[key0];
19948
20227
  if (pathVal && pathVal.then) {
@@ -19954,8 +20233,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
19954
20233
  }
19955
20234
  pathVal = pathVal.$$v;
19956
20235
  }
19957
- if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
19958
20236
 
20237
+ if (!key1) return pathVal;
20238
+ if (pathVal == null) return undefined;
19959
20239
  pathVal = pathVal[key1];
19960
20240
  if (pathVal && pathVal.then) {
19961
20241
  promiseWarning(fullExp);
@@ -19966,8 +20246,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
19966
20246
  }
19967
20247
  pathVal = pathVal.$$v;
19968
20248
  }
19969
- if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
19970
20249
 
20250
+ if (!key2) return pathVal;
20251
+ if (pathVal == null) return undefined;
19971
20252
  pathVal = pathVal[key2];
19972
20253
  if (pathVal && pathVal.then) {
19973
20254
  promiseWarning(fullExp);
@@ -19978,8 +20259,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
19978
20259
  }
19979
20260
  pathVal = pathVal.$$v;
19980
20261
  }
19981
- if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
19982
20262
 
20263
+ if (!key3) return pathVal;
20264
+ if (pathVal == null) return undefined;
19983
20265
  pathVal = pathVal[key3];
19984
20266
  if (pathVal && pathVal.then) {
19985
20267
  promiseWarning(fullExp);
@@ -19990,8 +20272,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
19990
20272
  }
19991
20273
  pathVal = pathVal.$$v;
19992
20274
  }
19993
- if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
19994
20275
 
20276
+ if (!key4) return pathVal;
20277
+ if (pathVal == null) return undefined;
19995
20278
  pathVal = pathVal[key4];
19996
20279
  if (pathVal && pathVal.then) {
19997
20280
  promiseWarning(fullExp);
@@ -20006,6 +20289,26 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
20006
20289
  };
20007
20290
  }
20008
20291
 
20292
+ function simpleGetterFn1(key0, fullExp) {
20293
+ ensureSafeMemberName(key0, fullExp);
20294
+
20295
+ return function simpleGetterFn1(scope, locals) {
20296
+ if (scope == null) return undefined;
20297
+ return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0];
20298
+ };
20299
+ }
20300
+
20301
+ function simpleGetterFn2(key0, key1, fullExp) {
20302
+ ensureSafeMemberName(key0, fullExp);
20303
+ ensureSafeMemberName(key1, fullExp);
20304
+
20305
+ return function simpleGetterFn2(scope, locals) {
20306
+ if (scope == null) return undefined;
20307
+ scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0];
20308
+ return scope == null ? undefined : scope[key1];
20309
+ };
20310
+ }
20311
+
20009
20312
  function getterFn(path, options, fullExp) {
20010
20313
  // Check whether the cache has this getter already.
20011
20314
  // We can use hasOwnProperty directly on the cache because we ensure,
@@ -20018,7 +20321,13 @@ function getterFn(path, options, fullExp) {
20018
20321
  pathKeysLength = pathKeys.length,
20019
20322
  fn;
20020
20323
 
20021
- if (options.csp) {
20324
+ // When we have only 1 or 2 tokens, use optimized special case closures.
20325
+ // http://jsperf.com/angularjs-parse-getter/6
20326
+ if (!options.unwrapPromises && pathKeysLength === 1) {
20327
+ fn = simpleGetterFn1(pathKeys[0], fullExp);
20328
+ } else if (!options.unwrapPromises && pathKeysLength === 2) {
20329
+ fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp);
20330
+ } else if (options.csp) {
20022
20331
  if (pathKeysLength < 6) {
20023
20332
  fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp,
20024
20333
  options);
@@ -20036,11 +20345,10 @@ function getterFn(path, options, fullExp) {
20036
20345
  };
20037
20346
  }
20038
20347
  } else {
20039
- var code = 'var l, fn, p;\n';
20348
+ var code = 'var p;\n';
20040
20349
  forEach(pathKeys, function(key, index) {
20041
20350
  ensureSafeMemberName(key, fullExp);
20042
- code += 'if(s === null || s === undefined) return s;\n' +
20043
- 'l=s;\n' +
20351
+ code += 'if(s == null) return undefined;\n' +
20044
20352
  's='+ (index
20045
20353
  // we simply dereference 's' on any .dot notation
20046
20354
  ? 's'
@@ -20063,10 +20371,10 @@ function getterFn(path, options, fullExp) {
20063
20371
  /* jshint -W054 */
20064
20372
  var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning
20065
20373
  /* jshint +W054 */
20066
- evaledFnGetter.toString = function() { return code; };
20067
- fn = function(scope, locals) {
20374
+ evaledFnGetter.toString = valueFn(code);
20375
+ fn = options.unwrapPromises ? function(scope, locals) {
20068
20376
  return evaledFnGetter(scope, locals, promiseWarning);
20069
- };
20377
+ } : evaledFnGetter;
20070
20378
  }
20071
20379
 
20072
20380
  // Only cache the value if it's not going to mess up the cache object
@@ -20280,9 +20588,9 @@ function $ParseProvider() {
20280
20588
  * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
20281
20589
  *
20282
20590
  * <pre>
20283
- * // for the purpose of this example let's assume that variables `$q` and `scope` are
20284
- * // available in the current lexical scope (they could have been injected or passed in).
20285
- *
20591
+ * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet`
20592
+ * // are available in the current lexical scope (they could have been injected or passed in).
20593
+ *
20286
20594
  * function asyncGreet(name) {
20287
20595
  * var deferred = $q.defer();
20288
20596
  *
@@ -20337,7 +20645,7 @@ function $ParseProvider() {
20337
20645
  * constructed via `$q.reject`, the promise will be rejected instead.
20338
20646
  * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
20339
20647
  * resolving it with a rejection constructed via `$q.reject`.
20340
- * - `notify(value)` - provides updates on the status of the promises execution. This may be called
20648
+ * - `notify(value)` - provides updates on the status of the promise's execution. This may be called
20341
20649
  * multiple times before the promise is either resolved or rejected.
20342
20650
  *
20343
20651
  * **Properties**
@@ -20487,7 +20795,7 @@ function qFactory(nextTick, exceptionHandler) {
20487
20795
 
20488
20796
 
20489
20797
  reject: function(reason) {
20490
- deferred.resolve(reject(reason));
20798
+ deferred.resolve(createInternalRejectedPromise(reason));
20491
20799
  },
20492
20800
 
20493
20801
 
@@ -20644,6 +20952,12 @@ function qFactory(nextTick, exceptionHandler) {
20644
20952
  * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
20645
20953
  */
20646
20954
  var reject = function(reason) {
20955
+ var result = defer();
20956
+ result.reject(reason);
20957
+ return result.promise;
20958
+ };
20959
+
20960
+ var createInternalRejectedPromise = function(reason) {
20647
20961
  return {
20648
20962
  then: function(callback, errback) {
20649
20963
  var result = defer();
@@ -20911,6 +21225,7 @@ function $RootScopeProvider(){
20911
21225
  this.$$asyncQueue = [];
20912
21226
  this.$$postDigestQueue = [];
20913
21227
  this.$$listeners = {};
21228
+ this.$$listenerCount = {};
20914
21229
  this.$$isolateBindings = {};
20915
21230
  }
20916
21231
 
@@ -20963,13 +21278,14 @@ function $RootScopeProvider(){
20963
21278
  } else {
20964
21279
  ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges
20965
21280
  // the name it does not become random set of chars. This will then show up as class
20966
- // name in the debugger.
21281
+ // name in the web inspector.
20967
21282
  ChildScope.prototype = this;
20968
21283
  child = new ChildScope();
20969
21284
  child.$id = nextUid();
20970
21285
  }
20971
21286
  child['this'] = child;
20972
21287
  child.$$listeners = {};
21288
+ child.$$listenerCount = {};
20973
21289
  child.$parent = this;
20974
21290
  child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
20975
21291
  child.$$prevSibling = this.$$childTail;
@@ -21063,7 +21379,7 @@ function $RootScopeProvider(){
21063
21379
  // No digest has been run so the counter will be zero
21064
21380
  expect(scope.foodCounter).toEqual(0);
21065
21381
 
21066
- // Run the digest but since food has not changed cout will still be zero
21382
+ // Run the digest but since food has not changed count will still be zero
21067
21383
  scope.$digest();
21068
21384
  expect(scope.foodCounter).toEqual(0);
21069
21385
 
@@ -21129,6 +21445,7 @@ function $RootScopeProvider(){
21129
21445
 
21130
21446
  return function() {
21131
21447
  arrayRemove(array, watcher);
21448
+ lastDirtyWatch = null;
21132
21449
  };
21133
21450
  },
21134
21451
 
@@ -21407,7 +21724,7 @@ function $RootScopeProvider(){
21407
21724
 
21408
21725
  // `break traverseScopesLoop;` takes us to here
21409
21726
 
21410
- if(dirty && !(ttl--)) {
21727
+ if((dirty || asyncQueue.length) && !(ttl--)) {
21411
21728
  clearPhase();
21412
21729
  throw $rootScopeMinErr('infdig',
21413
21730
  '{0} $digest() iterations reached. Aborting!\n' +
@@ -21474,6 +21791,8 @@ function $RootScopeProvider(){
21474
21791
  this.$$destroyed = true;
21475
21792
  if (this === $rootScope) return;
21476
21793
 
21794
+ forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
21795
+
21477
21796
  if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
21478
21797
  if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
21479
21798
  if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
@@ -21663,8 +21982,18 @@ function $RootScopeProvider(){
21663
21982
  }
21664
21983
  namedListeners.push(listener);
21665
21984
 
21985
+ var current = this;
21986
+ do {
21987
+ if (!current.$$listenerCount[name]) {
21988
+ current.$$listenerCount[name] = 0;
21989
+ }
21990
+ current.$$listenerCount[name]++;
21991
+ } while ((current = current.$parent));
21992
+
21993
+ var self = this;
21666
21994
  return function() {
21667
21995
  namedListeners[indexOf(namedListeners, listener)] = null;
21996
+ decrementListenerCount(self, 1, name);
21668
21997
  };
21669
21998
  },
21670
21999
 
@@ -21689,7 +22018,7 @@ function $RootScopeProvider(){
21689
22018
  * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
21690
22019
  *
21691
22020
  * @param {string} name Event name to emit.
21692
- * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
22021
+ * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
21693
22022
  * @return {Object} Event object (see {@link ng.$rootScope.Scope#methods_$on}).
21694
22023
  */
21695
22024
  $emit: function(name, args) {
@@ -21757,7 +22086,7 @@ function $RootScopeProvider(){
21757
22086
  * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
21758
22087
  *
21759
22088
  * @param {string} name Event name to broadcast.
21760
- * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
22089
+ * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
21761
22090
  * @return {Object} Event object, see {@link ng.$rootScope.Scope#methods_$on}
21762
22091
  */
21763
22092
  $broadcast: function(name, args) {
@@ -21776,8 +22105,7 @@ function $RootScopeProvider(){
21776
22105
  listeners, i, length;
21777
22106
 
21778
22107
  //down while you can, then up and next sibling or up and next sibling until back at root
21779
- do {
21780
- current = next;
22108
+ while ((current = next)) {
21781
22109
  event.currentScope = current;
21782
22110
  listeners = current.$$listeners[name] || [];
21783
22111
  for (i=0, length = listeners.length; i<length; i++) {
@@ -21799,12 +22127,14 @@ function $RootScopeProvider(){
21799
22127
  // Insanity Warning: scope depth-first traversal
21800
22128
  // yes, this code is a bit crazy, but it works and we have tests to prove it!
21801
22129
  // this piece should be kept in sync with the traversal in $digest
21802
- if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
22130
+ // (though it differs due to having the extra check for $$listenerCount)
22131
+ if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
22132
+ (current !== target && current.$$nextSibling)))) {
21803
22133
  while(current !== target && !(next = current.$$nextSibling)) {
21804
22134
  current = current.$parent;
21805
22135
  }
21806
22136
  }
21807
- } while ((current = next));
22137
+ }
21808
22138
 
21809
22139
  return event;
21810
22140
  }
@@ -21833,6 +22163,16 @@ function $RootScopeProvider(){
21833
22163
  return fn;
21834
22164
  }
21835
22165
 
22166
+ function decrementListenerCount(current, count, name) {
22167
+ do {
22168
+ current.$$listenerCount[name] -= count;
22169
+
22170
+ if (current.$$listenerCount[name] === 0) {
22171
+ delete current.$$listenerCount[name];
22172
+ }
22173
+ } while ((current = current.$parent));
22174
+ }
22175
+
21836
22176
  /**
21837
22177
  * function used as an initial value for watchers.
21838
22178
  * because it's unique we can easily tell it apart from other values
@@ -22189,7 +22529,7 @@ function $SceDelegateProvider() {
22189
22529
  *
22190
22530
  * @description
22191
22531
  * Returns an object that is trusted by angular for use in specified strict
22192
- * contextual escaping contexts (such as ng-html-bind-unsafe, ng-include, any src
22532
+ * contextual escaping contexts (such as ng-bind-html, ng-include, any src
22193
22533
  * attribute interpolation, any dom event binding attribute interpolation
22194
22534
  * such as for onclick, etc.) that uses the provided value.
22195
22535
  * See {@link ng.$sce $sce} for enabling strict contextual escaping.
@@ -22235,7 +22575,7 @@ function $SceDelegateProvider() {
22235
22575
  *
22236
22576
  * @param {*} value The result of a prior {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}
22237
22577
  * call or anything else.
22238
- * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs
22578
+ * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#methods_trustAs
22239
22579
  * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns
22240
22580
  * `value` unchanged.
22241
22581
  */
@@ -22416,8 +22756,8 @@ function $SceDelegateProvider() {
22416
22756
  * It's important to remember that SCE only applies to interpolation expressions.
22417
22757
  *
22418
22758
  * If your expressions are constant literals, they're automatically trusted and you don't need to
22419
- * call `$sce.trustAs` on them. (e.g.
22420
- * `<div ng-html-bind-unsafe="'<b>implicitly trusted</b>'"></div>`) just works.
22759
+ * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g.
22760
+ * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works.
22421
22761
  *
22422
22762
  * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
22423
22763
  * through {@link ng.$sce#methods_getTrusted $sce.getTrusted}. SCE doesn't play a role here.
@@ -22477,7 +22817,7 @@ function $SceDelegateProvider() {
22477
22817
  * matched against the **entire** *normalized / absolute URL* of the resource being tested
22478
22818
  * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
22479
22819
  * present on the RegExp (such as multiline, global, ignoreCase) are ignored.
22480
- * - If you are generating your Javascript from some other templating engine (not
22820
+ * - If you are generating your JavaScript from some other templating engine (not
22481
22821
  * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)),
22482
22822
  * remember to escape your regular expression (and be aware that you might need more than
22483
22823
  * one level of escaping depending on your templating engine and the way you interpolated
@@ -22494,7 +22834,7 @@ function $SceDelegateProvider() {
22494
22834
  * ## Show me an example using SCE.
22495
22835
  *
22496
22836
  * @example
22497
- <example module="mySceApp">
22837
+ <example module="mySceApp" deps="angular-sanitize.js">
22498
22838
  <file name="index.html">
22499
22839
  <div ng-controller="myAppController as myCtrl">
22500
22840
  <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br>
@@ -22538,13 +22878,15 @@ function $SceDelegateProvider() {
22538
22878
  ]
22539
22879
  </file>
22540
22880
 
22541
- <file name="scenario.js">
22881
+ <file name="protractorTest.js">
22542
22882
  describe('SCE doc demo', function() {
22543
22883
  it('should sanitize untrusted values', function() {
22544
- expect(element('.htmlComment').html()).toBe('<span>Is <i>anyone</i> reading this?</span>');
22884
+ expect(element(by.css('.htmlComment')).getInnerHtml())
22885
+ .toBe('<span>Is <i>anyone</i> reading this?</span>');
22545
22886
  });
22887
+
22546
22888
  it('should NOT sanitize explicitly trusted values', function() {
22547
- expect(element('#explicitlyTrustedHtml').html()).toBe(
22889
+ expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
22548
22890
  '<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
22549
22891
  'sanitization.&quot;">Hover over this text.</span>');
22550
22892
  });
@@ -22719,8 +23061,8 @@ function $SceProvider() {
22719
23061
  *
22720
23062
  * @description
22721
23063
  * Delegates to {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. As such,
22722
- * returns an objectthat is trusted by angular for use in specified strict contextual
22723
- * escaping contexts (such as ng-html-bind-unsafe, ng-include, any src attribute
23064
+ * returns an object that is trusted by angular for use in specified strict contextual
23065
+ * escaping contexts (such as ng-bind-html, ng-include, any src attribute
22724
23066
  * interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
22725
23067
  * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual
22726
23068
  * escaping.
@@ -23051,7 +23393,7 @@ function $SnifferProvider() {
23051
23393
  // http://code.google.com/p/android/issues/detail?id=17471
23052
23394
  // https://github.com/angular/angular.js/issues/904
23053
23395
 
23054
- // older webit browser (533.9) on Boxee box has exactly the same problem as Android has
23396
+ // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has
23055
23397
  // so let's not use the history API also
23056
23398
  // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined
23057
23399
  // jshint -W018
@@ -23077,6 +23419,7 @@ function $SnifferProvider() {
23077
23419
  vendorPrefix: vendorPrefix,
23078
23420
  transitions : transitions,
23079
23421
  animations : animations,
23422
+ android: android,
23080
23423
  msie : msie,
23081
23424
  msieDocumentMode: documentMode
23082
23425
  };
@@ -23114,113 +23457,26 @@ function $TimeoutProvider() {
23114
23457
  * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
23115
23458
  * promise will be resolved with is the return value of the `fn` function.
23116
23459
  *
23117
- * @example
23118
- <doc:example module="time">
23119
- <doc:source>
23120
- <script>
23121
- function Ctrl2($scope,$timeout) {
23122
- $scope.format = 'M/d/yy h:mm:ss a';
23123
- $scope.blood_1 = 100;
23124
- $scope.blood_2 = 120;
23125
-
23126
- var stop;
23127
- $scope.fight = function() {
23128
- stop = $timeout(function() {
23129
- if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
23130
- $scope.blood_1 = $scope.blood_1 - 3;
23131
- $scope.blood_2 = $scope.blood_2 - 4;
23132
- $scope.fight();
23133
- } else {
23134
- $timeout.cancel(stop);
23135
- }
23136
- }, 100);
23137
- };
23460
+ */
23461
+ function timeout(fn, delay, invokeApply) {
23462
+ var deferred = $q.defer(),
23463
+ promise = deferred.promise,
23464
+ skipApply = (isDefined(invokeApply) && !invokeApply),
23465
+ timeoutId;
23138
23466
 
23139
- $scope.stopFight = function() {
23140
- $timeout.cancel(stop);
23141
- };
23467
+ timeoutId = $browser.defer(function() {
23468
+ try {
23469
+ deferred.resolve(fn());
23470
+ } catch(e) {
23471
+ deferred.reject(e);
23472
+ $exceptionHandler(e);
23473
+ }
23474
+ finally {
23475
+ delete deferreds[promise.$$timeoutId];
23476
+ }
23142
23477
 
23143
- $scope.resetFight = function() {
23144
- $scope.blood_1 = 100;
23145
- $scope.blood_2 = 120;
23146
- }
23147
- }
23148
-
23149
- angular.module('time', [])
23150
- // Register the 'myCurrentTime' directive factory method.
23151
- // We inject $timeout and dateFilter service since the factory method is DI.
23152
- .directive('myCurrentTime', function($timeout, dateFilter) {
23153
- // return the directive link function. (compile function not needed)
23154
- return function(scope, element, attrs) {
23155
- var format, // date format
23156
- timeoutId; // timeoutId, so that we can cancel the time updates
23157
-
23158
- // used to update the UI
23159
- function updateTime() {
23160
- element.text(dateFilter(new Date(), format));
23161
- }
23162
-
23163
- // watch the expression, and update the UI on change.
23164
- scope.$watch(attrs.myCurrentTime, function(value) {
23165
- format = value;
23166
- updateTime();
23167
- });
23168
-
23169
- // schedule update in one second
23170
- function updateLater() {
23171
- // save the timeoutId for canceling
23172
- timeoutId = $timeout(function() {
23173
- updateTime(); // update DOM
23174
- updateLater(); // schedule another update
23175
- }, 1000);
23176
- }
23177
-
23178
- // listen on DOM destroy (removal) event, and cancel the next UI update
23179
- // to prevent updating time ofter the DOM element was removed.
23180
- element.bind('$destroy', function() {
23181
- $timeout.cancel(timeoutId);
23182
- });
23183
-
23184
- updateLater(); // kick off the UI update process.
23185
- }
23186
- });
23187
- </script>
23188
-
23189
- <div>
23190
- <div ng-controller="Ctrl2">
23191
- Date format: <input ng-model="format"> <hr/>
23192
- Current time is: <span my-current-time="format"></span>
23193
- <hr/>
23194
- Blood 1 : <font color='red'>{{blood_1}}</font>
23195
- Blood 2 : <font color='red'>{{blood_2}}</font>
23196
- <button type="button" data-ng-click="fight()">Fight</button>
23197
- <button type="button" data-ng-click="stopFight()">StopFight</button>
23198
- <button type="button" data-ng-click="resetFight()">resetFight</button>
23199
- </div>
23200
- </div>
23201
-
23202
- </doc:source>
23203
- </doc:example>
23204
- */
23205
- function timeout(fn, delay, invokeApply) {
23206
- var deferred = $q.defer(),
23207
- promise = deferred.promise,
23208
- skipApply = (isDefined(invokeApply) && !invokeApply),
23209
- timeoutId;
23210
-
23211
- timeoutId = $browser.defer(function() {
23212
- try {
23213
- deferred.resolve(fn());
23214
- } catch(e) {
23215
- deferred.reject(e);
23216
- $exceptionHandler(e);
23217
- }
23218
- finally {
23219
- delete deferreds[promise.$$timeoutId];
23220
- }
23221
-
23222
- if (!skipApply) $rootScope.$apply();
23223
- }, delay);
23478
+ if (!skipApply) $rootScope.$apply();
23479
+ }, delay);
23224
23480
 
23225
23481
  promise.$$timeoutId = timeoutId;
23226
23482
  deferreds[timeoutId] = deferred;
@@ -23389,13 +23645,13 @@ function urlIsSameOrigin(requestUrl) {
23389
23645
  <button ng-click="doGreeting(greeting)">ALERT</button>
23390
23646
  </div>
23391
23647
  </doc:source>
23392
- <doc:scenario>
23648
+ <doc:protractor>
23393
23649
  it('should display the greeting in the input box', function() {
23394
- input('greeting').enter('Hello, E2E Tests');
23650
+ element(by.model('greeting')).sendKeys('Hello, E2E Tests');
23395
23651
  // If we click the button it will block the test runner
23396
23652
  // element(':button').click();
23397
23653
  });
23398
- </doc:scenario>
23654
+ </doc:protractor>
23399
23655
  </doc:example>
23400
23656
  */
23401
23657
  function $WindowProvider(){
@@ -23548,8 +23804,8 @@ function $FilterProvider($provide) {
23548
23804
  *
23549
23805
  * Can be one of:
23550
23806
  *
23551
- * - `string`: Predicate that results in a substring match using the value of `expression`
23552
- * string. All strings or objects with string properties in `array` that contain this string
23807
+ * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
23808
+ * the contents of the `array`. All strings or objects with string properties in `array` that contain this string
23553
23809
  * will be returned. The predicate can be negated by prefixing the string with `!`.
23554
23810
  *
23555
23811
  * - `Object`: A pattern object can be used to filter specific properties on objects contained
@@ -23559,21 +23815,21 @@ function $FilterProvider($provide) {
23559
23815
  * property of the object. That's equivalent to the simple substring match with a `string`
23560
23816
  * as described above.
23561
23817
  *
23562
- * - `function`: A predicate function can be used to write arbitrary filters. The function is
23818
+ * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is
23563
23819
  * called for each element of `array`. The final result is an array of those elements that
23564
23820
  * the predicate returned true for.
23565
23821
  *
23566
- * @param {function(expected, actual)|true|undefined} comparator Comparator which is used in
23822
+ * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
23567
23823
  * determining if the expected value (from the filter expression) and actual value (from
23568
23824
  * the object in the array) should be considered a match.
23569
23825
  *
23570
23826
  * Can be one of:
23571
23827
  *
23572
- * - `function(expected, actual)`:
23828
+ * - `function(actual, expected)`:
23573
23829
  * The function will be given the object value and the predicate value to compare and
23574
23830
  * should return true if the item should be included in filtered result.
23575
23831
  *
23576
- * - `true`: A shorthand for `function(expected, actual) { return angular.equals(expected, actual)}`.
23832
+ * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`.
23577
23833
  * this is essentially strict comparison of expected and actual.
23578
23834
  *
23579
23835
  * - `false|undefined`: A short hand for a function which will look for a substring match in case
@@ -23604,35 +23860,47 @@ function $FilterProvider($provide) {
23604
23860
  Equality <input type="checkbox" ng-model="strict"><br>
23605
23861
  <table id="searchObjResults">
23606
23862
  <tr><th>Name</th><th>Phone</th></tr>
23607
- <tr ng-repeat="friend in friends | filter:search:strict">
23608
- <td>{{friend.name}}</td>
23609
- <td>{{friend.phone}}</td>
23863
+ <tr ng-repeat="friendObj in friends | filter:search:strict">
23864
+ <td>{{friendObj.name}}</td>
23865
+ <td>{{friendObj.phone}}</td>
23610
23866
  </tr>
23611
23867
  </table>
23612
23868
  </doc:source>
23613
- <doc:scenario>
23614
- it('should search across all fields when filtering with a string', function() {
23615
- input('searchText').enter('m');
23616
- expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
23617
- toEqual(['Mary', 'Mike', 'Adam']);
23869
+ <doc:protractor>
23870
+ var expectFriendNames = function(expectedNames, key) {
23871
+ element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
23872
+ arr.forEach(function(wd, i) {
23873
+ expect(wd.getText()).toMatch(expectedNames[i]);
23874
+ });
23875
+ });
23876
+ };
23618
23877
 
23619
- input('searchText').enter('76');
23620
- expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
23621
- toEqual(['John', 'Julie']);
23878
+ it('should search across all fields when filtering with a string', function() {
23879
+ var searchText = element(by.model('searchText'));
23880
+ searchText.clear();
23881
+ searchText.sendKeys('m');
23882
+ expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
23883
+
23884
+ searchText.clear();
23885
+ searchText.sendKeys('76');
23886
+ expectFriendNames(['John', 'Julie'], 'friend');
23622
23887
  });
23623
23888
 
23624
23889
  it('should search in specific fields when filtering with a predicate object', function() {
23625
- input('search.$').enter('i');
23626
- expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
23627
- toEqual(['Mary', 'Mike', 'Julie', 'Juliette']);
23890
+ var searchAny = element(by.model('search.$'));
23891
+ searchAny.clear();
23892
+ searchAny.sendKeys('i');
23893
+ expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
23628
23894
  });
23629
23895
  it('should use a equal comparison when comparator is true', function() {
23630
- input('search.name').enter('Julie');
23631
- input('strict').check();
23632
- expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
23633
- toEqual(['Julie']);
23896
+ var searchName = element(by.model('search.name'));
23897
+ var strict = element(by.model('strict'));
23898
+ searchName.clear();
23899
+ searchName.sendKeys('Julie');
23900
+ strict.click();
23901
+ expectFriendNames(['Julie'], 'friendObj');
23634
23902
  });
23635
- </doc:scenario>
23903
+ </doc:protractor>
23636
23904
  </doc:example>
23637
23905
  */
23638
23906
  function filterFilter() {
@@ -23658,6 +23926,15 @@ function filterFilter() {
23658
23926
  };
23659
23927
  } else {
23660
23928
  comparator = function(obj, text) {
23929
+ if (obj && text && typeof obj === 'object' && typeof text === 'object') {
23930
+ for (var objKey in obj) {
23931
+ if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
23932
+ comparator(obj[objKey], text[objKey])) {
23933
+ return true;
23934
+ }
23935
+ }
23936
+ return false;
23937
+ }
23661
23938
  text = (''+text).toLowerCase();
23662
23939
  return (''+obj).toLowerCase().indexOf(text) > -1;
23663
23940
  };
@@ -23707,23 +23984,12 @@ function filterFilter() {
23707
23984
  case "object":
23708
23985
  // jshint +W086
23709
23986
  for (var key in expression) {
23710
- if (key == '$') {
23711
- (function() {
23712
- if (!expression[key]) return;
23713
- var path = key;
23714
- predicates.push(function(value) {
23715
- return search(value, expression[path]);
23716
- });
23717
- })();
23718
- } else {
23719
- (function() {
23720
- if (typeof(expression[key]) == 'undefined') { return; }
23721
- var path = key;
23722
- predicates.push(function(value) {
23723
- return search(getter(value,path), expression[path]);
23724
- });
23725
- })();
23726
- }
23987
+ (function(path) {
23988
+ if (typeof expression[path] == 'undefined') return;
23989
+ predicates.push(function(value) {
23990
+ return search(path == '$' ? value : (value && value[path]), expression[path]);
23991
+ });
23992
+ })(key);
23727
23993
  }
23728
23994
  break;
23729
23995
  case 'function':
@@ -23767,21 +24033,27 @@ function filterFilter() {
23767
24033
  </script>
23768
24034
  <div ng-controller="Ctrl">
23769
24035
  <input type="number" ng-model="amount"> <br>
23770
- default currency symbol ($): {{amount | currency}}<br>
23771
- custom currency identifier (USD$): {{amount | currency:"USD$"}}
24036
+ default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
24037
+ custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span>
23772
24038
  </div>
23773
24039
  </doc:source>
23774
- <doc:scenario>
24040
+ <doc:protractor>
23775
24041
  it('should init with 1234.56', function() {
23776
- expect(binding('amount | currency')).toBe('$1,234.56');
23777
- expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
24042
+ expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
24043
+ expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56');
23778
24044
  });
23779
24045
  it('should update', function() {
23780
- input('amount').enter('-1234');
23781
- expect(binding('amount | currency')).toBe('($1,234.00)');
23782
- expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
24046
+ if (browser.params.browser == 'safari') {
24047
+ // Safari does not understand the minus key. See
24048
+ // https://github.com/angular/protractor/issues/481
24049
+ return;
24050
+ }
24051
+ element(by.model('amount')).clear();
24052
+ element(by.model('amount')).sendKeys('-1234');
24053
+ expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)');
24054
+ expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)');
23783
24055
  });
23784
- </doc:scenario>
24056
+ </doc:protractor>
23785
24057
  </doc:example>
23786
24058
  */
23787
24059
  currencyFilter.$inject = ['$locale'];
@@ -23820,25 +24092,26 @@ function currencyFilter($locale) {
23820
24092
  </script>
23821
24093
  <div ng-controller="Ctrl">
23822
24094
  Enter number: <input ng-model='val'><br>
23823
- Default formatting: {{val | number}}<br>
23824
- No fractions: {{val | number:0}}<br>
23825
- Negative number: {{-val | number:4}}
24095
+ Default formatting: <span id='number-default'>{{val | number}}</span><br>
24096
+ No fractions: <span>{{val | number:0}}</span><br>
24097
+ Negative number: <span>{{-val | number:4}}</span>
23826
24098
  </div>
23827
24099
  </doc:source>
23828
- <doc:scenario>
24100
+ <doc:protractor>
23829
24101
  it('should format numbers', function() {
23830
- expect(binding('val | number')).toBe('1,234.568');
23831
- expect(binding('val | number:0')).toBe('1,235');
23832
- expect(binding('-val | number:4')).toBe('-1,234.5679');
24102
+ expect(element(by.id('number-default')).getText()).toBe('1,234.568');
24103
+ expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
24104
+ expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
23833
24105
  });
23834
24106
 
23835
24107
  it('should update', function() {
23836
- input('val').enter('3374.333');
23837
- expect(binding('val | number')).toBe('3,374.333');
23838
- expect(binding('val | number:0')).toBe('3,374');
23839
- expect(binding('-val | number:4')).toBe('-3,374.3330');
23840
- });
23841
- </doc:scenario>
24108
+ element(by.model('val')).clear();
24109
+ element(by.model('val')).sendKeys('3374.333');
24110
+ expect(element(by.id('number-default')).getText()).toBe('3,374.333');
24111
+ expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
24112
+ expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
24113
+ });
24114
+ </doc:protractor>
23842
24115
  </doc:example>
23843
24116
  */
23844
24117
 
@@ -24068,22 +24341,22 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
24068
24341
  <doc:example>
24069
24342
  <doc:source>
24070
24343
  <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
24071
- {{1288323623006 | date:'medium'}}<br>
24344
+ <span>{{1288323623006 | date:'medium'}}</span><br>
24072
24345
  <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
24073
- {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
24346
+ <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
24074
24347
  <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
24075
- {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
24348
+ <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
24076
24349
  </doc:source>
24077
- <doc:scenario>
24350
+ <doc:protractor>
24078
24351
  it('should format date', function() {
24079
- expect(binding("1288323623006 | date:'medium'")).
24352
+ expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
24080
24353
  toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
24081
- expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
24354
+ expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
24082
24355
  toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
24083
- expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
24356
+ expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
24084
24357
  toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
24085
24358
  });
24086
- </doc:scenario>
24359
+ </doc:protractor>
24087
24360
  </doc:example>
24088
24361
  */
24089
24362
  dateFilter.$inject = ['$locale'];
@@ -24182,11 +24455,11 @@ function dateFilter($locale) {
24182
24455
  <doc:source>
24183
24456
  <pre>{{ {'name':'value'} | json }}</pre>
24184
24457
  </doc:source>
24185
- <doc:scenario>
24458
+ <doc:protractor>
24186
24459
  it('should jsonify filtered objects', function() {
24187
- expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
24460
+ expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
24188
24461
  });
24189
- </doc:scenario>
24462
+ </doc:protractor>
24190
24463
  </doc:example>
24191
24464
  *
24192
24465
  */
@@ -24254,28 +24527,37 @@ var uppercaseFilter = valueFn(uppercase);
24254
24527
  <p>Output letters: {{ letters | limitTo:letterLimit }}</p>
24255
24528
  </div>
24256
24529
  </doc:source>
24257
- <doc:scenario>
24530
+ <doc:protractor>
24531
+ var numLimitInput = element(by.model('numLimit'));
24532
+ var letterLimitInput = element(by.model('letterLimit'));
24533
+ var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
24534
+ var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
24535
+
24258
24536
  it('should limit the number array to first three items', function() {
24259
- expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3');
24260
- expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3');
24261
- expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
24262
- expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
24537
+ expect(numLimitInput.getAttribute('value')).toBe('3');
24538
+ expect(letterLimitInput.getAttribute('value')).toBe('3');
24539
+ expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
24540
+ expect(limitedLetters.getText()).toEqual('Output letters: abc');
24263
24541
  });
24264
24542
 
24265
24543
  it('should update the output when -3 is entered', function() {
24266
- input('numLimit').enter(-3);
24267
- input('letterLimit').enter(-3);
24268
- expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
24269
- expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
24544
+ numLimitInput.clear();
24545
+ numLimitInput.sendKeys('-3');
24546
+ letterLimitInput.clear();
24547
+ letterLimitInput.sendKeys('-3');
24548
+ expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
24549
+ expect(limitedLetters.getText()).toEqual('Output letters: ghi');
24270
24550
  });
24271
24551
 
24272
24552
  it('should not exceed the maximum size of input array', function() {
24273
- input('numLimit').enter(100);
24274
- input('letterLimit').enter(100);
24275
- expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
24276
- expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
24553
+ numLimitInput.clear();
24554
+ numLimitInput.sendKeys('100');
24555
+ letterLimitInput.clear();
24556
+ letterLimitInput.sendKeys('100');
24557
+ expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
24558
+ expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
24277
24559
  });
24278
- </doc:scenario>
24560
+ </doc:protractor>
24279
24561
  </doc:example>
24280
24562
  */
24281
24563
  function limitToFilter(){
@@ -24376,29 +24658,6 @@ function limitToFilter(){
24376
24658
  </table>
24377
24659
  </div>
24378
24660
  </doc:source>
24379
- <doc:scenario>
24380
- it('should be reverse ordered by aged', function() {
24381
- expect(binding('predicate')).toBe('-age');
24382
- expect(repeater('table.friend', 'friend in friends').column('friend.age')).
24383
- toEqual(['35', '29', '21', '19', '10']);
24384
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
24385
- toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
24386
- });
24387
-
24388
- it('should reorder the table when user selects different predicate', function() {
24389
- element('.doc-example-live a:contains("Name")').click();
24390
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
24391
- toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
24392
- expect(repeater('table.friend', 'friend in friends').column('friend.age')).
24393
- toEqual(['35', '10', '29', '19', '21']);
24394
-
24395
- element('.doc-example-live a:contains("Phone")').click();
24396
- expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
24397
- toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
24398
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
24399
- toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
24400
- });
24401
- </doc:scenario>
24402
24661
  </doc:example>
24403
24662
  */
24404
24663
  orderByFilter.$inject = ['$parse'];
@@ -24495,11 +24754,14 @@ var htmlAnchorDirective = valueFn({
24495
24754
  element.append(document.createComment('IE fix'));
24496
24755
  }
24497
24756
 
24498
- if (!attr.href && !attr.name) {
24757
+ if (!attr.href && !attr.xlinkHref && !attr.name) {
24499
24758
  return function(scope, element) {
24759
+ // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
24760
+ var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
24761
+ 'xlink:href' : 'href';
24500
24762
  element.on('click', function(event){
24501
24763
  // if we have no href url, then don't navigate anywhere.
24502
- if (!element.attr('href')) {
24764
+ if (!element.attr(href)) {
24503
24765
  event.preventDefault();
24504
24766
  }
24505
24767
  });
@@ -24512,6 +24774,7 @@ var htmlAnchorDirective = valueFn({
24512
24774
  * @ngdoc directive
24513
24775
  * @name ng.directive:ngHref
24514
24776
  * @restrict A
24777
+ * @priority 99
24515
24778
  *
24516
24779
  * @description
24517
24780
  * Using Angular markup like `{{hash}}` in an href attribute will
@@ -24548,46 +24811,55 @@ var htmlAnchorDirective = valueFn({
24548
24811
  <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
24549
24812
  <a id="link-6" ng-href="{{value}}">link</a> (link, change location)
24550
24813
  </doc:source>
24551
- <doc:scenario>
24814
+ <doc:protractor>
24552
24815
  it('should execute ng-click but not reload when href without value', function() {
24553
- element('#link-1').click();
24554
- expect(input('value').val()).toEqual('1');
24555
- expect(element('#link-1').attr('href')).toBe("");
24816
+ element(by.id('link-1')).click();
24817
+ expect(element(by.model('value')).getAttribute('value')).toEqual('1');
24818
+ expect(element(by.id('link-1')).getAttribute('href')).toBe('');
24556
24819
  });
24557
24820
 
24558
24821
  it('should execute ng-click but not reload when href empty string', function() {
24559
- element('#link-2').click();
24560
- expect(input('value').val()).toEqual('2');
24561
- expect(element('#link-2').attr('href')).toBe("");
24822
+ element(by.id('link-2')).click();
24823
+ expect(element(by.model('value')).getAttribute('value')).toEqual('2');
24824
+ expect(element(by.id('link-2')).getAttribute('href')).toBe('');
24562
24825
  });
24563
24826
 
24564
24827
  it('should execute ng-click and change url when ng-href specified', function() {
24565
- expect(element('#link-3').attr('href')).toBe("/123");
24828
+ expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
24829
+
24830
+ element(by.id('link-3')).click();
24566
24831
 
24567
- element('#link-3').click();
24568
- expect(browser().window().path()).toEqual('/123');
24832
+ // At this point, we navigate away from an Angular page, so we need
24833
+ // to use browser.driver to get the base webdriver.
24834
+
24835
+ browser.wait(function() {
24836
+ return browser.driver.getCurrentUrl().then(function(url) {
24837
+ return url.match(/\/123$/);
24838
+ });
24839
+ }, 1000, 'page should navigate to /123');
24569
24840
  });
24570
24841
 
24571
24842
  it('should execute ng-click but not reload when href empty string and name specified', function() {
24572
- element('#link-4').click();
24573
- expect(input('value').val()).toEqual('4');
24574
- expect(element('#link-4').attr('href')).toBe('');
24843
+ element(by.id('link-4')).click();
24844
+ expect(element(by.model('value')).getAttribute('value')).toEqual('4');
24845
+ expect(element(by.id('link-4')).getAttribute('href')).toBe('');
24575
24846
  });
24576
24847
 
24577
24848
  it('should execute ng-click but not reload when no href but name specified', function() {
24578
- element('#link-5').click();
24579
- expect(input('value').val()).toEqual('5');
24580
- expect(element('#link-5').attr('href')).toBe(undefined);
24849
+ element(by.id('link-5')).click();
24850
+ expect(element(by.model('value')).getAttribute('value')).toEqual('5');
24851
+ expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
24581
24852
  });
24582
24853
 
24583
24854
  it('should only change url when only ng-href', function() {
24584
- input('value').enter('6');
24585
- expect(element('#link-6').attr('href')).toBe('6');
24855
+ element(by.model('value')).clear();
24856
+ element(by.model('value')).sendKeys('6');
24857
+ expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
24586
24858
 
24587
- element('#link-6').click();
24588
- expect(browser().location().url()).toEqual('/6');
24859
+ element(by.id('link-6')).click();
24860
+ expect(browser.getCurrentUrl()).toMatch(/\/6$/);
24589
24861
  });
24590
- </doc:scenario>
24862
+ </doc:protractor>
24591
24863
  </doc:example>
24592
24864
  */
24593
24865
 
@@ -24595,6 +24867,7 @@ var htmlAnchorDirective = valueFn({
24595
24867
  * @ngdoc directive
24596
24868
  * @name ng.directive:ngSrc
24597
24869
  * @restrict A
24870
+ * @priority 99
24598
24871
  *
24599
24872
  * @description
24600
24873
  * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
@@ -24620,6 +24893,7 @@ var htmlAnchorDirective = valueFn({
24620
24893
  * @ngdoc directive
24621
24894
  * @name ng.directive:ngSrcset
24622
24895
  * @restrict A
24896
+ * @priority 99
24623
24897
  *
24624
24898
  * @description
24625
24899
  * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't
@@ -24645,6 +24919,7 @@ var htmlAnchorDirective = valueFn({
24645
24919
  * @ngdoc directive
24646
24920
  * @name ng.directive:ngDisabled
24647
24921
  * @restrict A
24922
+ * @priority 100
24648
24923
  *
24649
24924
  * @description
24650
24925
  *
@@ -24669,13 +24944,13 @@ var htmlAnchorDirective = valueFn({
24669
24944
  Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
24670
24945
  <button ng-model="button" ng-disabled="checked">Button</button>
24671
24946
  </doc:source>
24672
- <doc:scenario>
24947
+ <doc:protractor>
24673
24948
  it('should toggle button', function() {
24674
- expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
24675
- input('checked').check();
24676
- expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
24949
+ expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeFalsy();
24950
+ element(by.model('checked')).click();
24951
+ expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeTruthy();
24677
24952
  });
24678
- </doc:scenario>
24953
+ </doc:protractor>
24679
24954
  </doc:example>
24680
24955
  *
24681
24956
  * @element INPUT
@@ -24688,6 +24963,7 @@ var htmlAnchorDirective = valueFn({
24688
24963
  * @ngdoc directive
24689
24964
  * @name ng.directive:ngChecked
24690
24965
  * @restrict A
24966
+ * @priority 100
24691
24967
  *
24692
24968
  * @description
24693
24969
  * The HTML specification does not require browsers to preserve the values of boolean attributes
@@ -24703,13 +24979,13 @@ var htmlAnchorDirective = valueFn({
24703
24979
  Check me to check both: <input type="checkbox" ng-model="master"><br/>
24704
24980
  <input id="checkSlave" type="checkbox" ng-checked="master">
24705
24981
  </doc:source>
24706
- <doc:scenario>
24982
+ <doc:protractor>
24707
24983
  it('should check both checkBoxes', function() {
24708
- expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
24709
- input('master').check();
24710
- expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
24984
+ expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
24985
+ element(by.model('master')).click();
24986
+ expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
24711
24987
  });
24712
- </doc:scenario>
24988
+ </doc:protractor>
24713
24989
  </doc:example>
24714
24990
  *
24715
24991
  * @element INPUT
@@ -24722,6 +24998,7 @@ var htmlAnchorDirective = valueFn({
24722
24998
  * @ngdoc directive
24723
24999
  * @name ng.directive:ngReadonly
24724
25000
  * @restrict A
25001
+ * @priority 100
24725
25002
  *
24726
25003
  * @description
24727
25004
  * The HTML specification does not require browsers to preserve the values of boolean attributes
@@ -24731,20 +25008,19 @@ var htmlAnchorDirective = valueFn({
24731
25008
  * The `ngReadonly` directive solves this problem for the `readonly` attribute.
24732
25009
  * This complementary directive is not removed by the browser and so provides
24733
25010
  * a permanent reliable place to store the binding information.
24734
-
24735
25011
  * @example
24736
25012
  <doc:example>
24737
25013
  <doc:source>
24738
25014
  Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
24739
25015
  <input type="text" ng-readonly="checked" value="I'm Angular"/>
24740
25016
  </doc:source>
24741
- <doc:scenario>
25017
+ <doc:protractor>
24742
25018
  it('should toggle readonly attr', function() {
24743
- expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
24744
- input('checked').check();
24745
- expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
25019
+ expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeFalsy();
25020
+ element(by.model('checked')).click();
25021
+ expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeTruthy();
24746
25022
  });
24747
- </doc:scenario>
25023
+ </doc:protractor>
24748
25024
  </doc:example>
24749
25025
  *
24750
25026
  * @element INPUT
@@ -24757,6 +25033,7 @@ var htmlAnchorDirective = valueFn({
24757
25033
  * @ngdoc directive
24758
25034
  * @name ng.directive:ngSelected
24759
25035
  * @restrict A
25036
+ * @priority 100
24760
25037
  *
24761
25038
  * @description
24762
25039
  * The HTML specification does not require browsers to preserve the values of boolean attributes
@@ -24766,6 +25043,7 @@ var htmlAnchorDirective = valueFn({
24766
25043
  * The `ngSelected` directive solves this problem for the `selected` atttribute.
24767
25044
  * This complementary directive is not removed by the browser and so provides
24768
25045
  * a permanent reliable place to store the binding information.
25046
+ *
24769
25047
  * @example
24770
25048
  <doc:example>
24771
25049
  <doc:source>
@@ -24775,13 +25053,13 @@ var htmlAnchorDirective = valueFn({
24775
25053
  <option id="greet" ng-selected="selected">Greetings!</option>
24776
25054
  </select>
24777
25055
  </doc:source>
24778
- <doc:scenario>
25056
+ <doc:protractor>
24779
25057
  it('should select Greetings!', function() {
24780
- expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
24781
- input('selected').check();
24782
- expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
25058
+ expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
25059
+ element(by.model('selected')).click();
25060
+ expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
24783
25061
  });
24784
- </doc:scenario>
25062
+ </doc:protractor>
24785
25063
  </doc:example>
24786
25064
  *
24787
25065
  * @element OPTION
@@ -24793,6 +25071,7 @@ var htmlAnchorDirective = valueFn({
24793
25071
  * @ngdoc directive
24794
25072
  * @name ng.directive:ngOpen
24795
25073
  * @restrict A
25074
+ * @priority 100
24796
25075
  *
24797
25076
  * @description
24798
25077
  * The HTML specification does not require browsers to preserve the values of boolean attributes
@@ -24802,8 +25081,6 @@ var htmlAnchorDirective = valueFn({
24802
25081
  * The `ngOpen` directive solves this problem for the `open` attribute.
24803
25082
  * This complementary directive is not removed by the browser and so provides
24804
25083
  * a permanent reliable place to store the binding information.
24805
-
24806
- *
24807
25084
  * @example
24808
25085
  <doc:example>
24809
25086
  <doc:source>
@@ -24812,13 +25089,13 @@ var htmlAnchorDirective = valueFn({
24812
25089
  <summary>Show/Hide me</summary>
24813
25090
  </details>
24814
25091
  </doc:source>
24815
- <doc:scenario>
25092
+ <doc:protractor>
24816
25093
  it('should toggle open', function() {
24817
- expect(element('#details').prop('open')).toBeFalsy();
24818
- input('open').check();
24819
- expect(element('#details').prop('open')).toBeTruthy();
25094
+ expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
25095
+ element(by.model('open')).click();
25096
+ expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
24820
25097
  });
24821
- </doc:scenario>
25098
+ </doc:protractor>
24822
25099
  </doc:example>
24823
25100
  *
24824
25101
  * @element DETAILS
@@ -24838,12 +25115,10 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) {
24838
25115
  ngAttributeAliasDirectives[normalized] = function() {
24839
25116
  return {
24840
25117
  priority: 100,
24841
- compile: function() {
24842
- return function(scope, element, attr) {
24843
- scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
24844
- attr.$set(attrName, !!value);
24845
- });
24846
- };
25118
+ link: function(scope, element, attr) {
25119
+ scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
25120
+ attr.$set(attrName, !!value);
25121
+ });
24847
25122
  }
24848
25123
  };
24849
25124
  };
@@ -25123,10 +25398,10 @@ function FormController(element, attrs) {
25123
25398
  *
25124
25399
  *
25125
25400
  * # CSS classes
25126
- * - `ng-valid` Is set if the form is valid.
25127
- * - `ng-invalid` Is set if the form is invalid.
25128
- * - `ng-pristine` Is set if the form is pristine.
25129
- * - `ng-dirty` Is set if the form is dirty.
25401
+ * - `ng-valid` is set if the form is valid.
25402
+ * - `ng-invalid` is set if the form is invalid.
25403
+ * - `ng-pristine` is set if the form is pristine.
25404
+ * - `ng-dirty` is set if the form is dirty.
25130
25405
  *
25131
25406
  *
25132
25407
  * # Submitting a form and preventing the default action
@@ -25179,18 +25454,27 @@ function FormController(element, attrs) {
25179
25454
  <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
25180
25455
  </form>
25181
25456
  </doc:source>
25182
- <doc:scenario>
25457
+ <doc:protractor>
25183
25458
  it('should initialize to model', function() {
25184
- expect(binding('userType')).toEqual('guest');
25185
- expect(binding('myForm.input.$valid')).toEqual('true');
25459
+ var userType = element(by.binding('userType'));
25460
+ var valid = element(by.binding('myForm.input.$valid'));
25461
+
25462
+ expect(userType.getText()).toContain('guest');
25463
+ expect(valid.getText()).toContain('true');
25186
25464
  });
25187
25465
 
25188
25466
  it('should be invalid if empty', function() {
25189
- input('userType').enter('');
25190
- expect(binding('userType')).toEqual('');
25191
- expect(binding('myForm.input.$valid')).toEqual('false');
25467
+ var userType = element(by.binding('userType'));
25468
+ var valid = element(by.binding('myForm.input.$valid'));
25469
+ var userInput = element(by.model('userType'));
25470
+
25471
+ userInput.clear();
25472
+ userInput.sendKeys('');
25473
+
25474
+ expect(userType.getText()).toEqual('userType =');
25475
+ expect(valid.getText()).toContain('false');
25192
25476
  });
25193
- </doc:scenario>
25477
+ </doc:protractor>
25194
25478
  </doc:example>
25195
25479
  */
25196
25480
  var formDirectiveFactory = function(isNgForm) {
@@ -25262,7 +25546,7 @@ var ngFormDirective = formDirectiveFactory(true);
25262
25546
  */
25263
25547
 
25264
25548
  var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
25265
- var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;
25549
+ var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
25266
25550
  var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
25267
25551
 
25268
25552
  var inputType = {
@@ -25315,29 +25599,31 @@ var inputType = {
25315
25599
  <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25316
25600
  </form>
25317
25601
  </doc:source>
25318
- <doc:scenario>
25602
+ <doc:protractor>
25603
+ var text = element(by.binding('text'));
25604
+ var valid = element(by.binding('myForm.input.$valid'));
25605
+ var input = element(by.model('text'));
25606
+
25319
25607
  it('should initialize to model', function() {
25320
- expect(binding('text')).toEqual('guest');
25321
- expect(binding('myForm.input.$valid')).toEqual('true');
25608
+ expect(text.getText()).toContain('guest');
25609
+ expect(valid.getText()).toContain('true');
25322
25610
  });
25323
25611
 
25324
25612
  it('should be invalid if empty', function() {
25325
- input('text').enter('');
25326
- expect(binding('text')).toEqual('');
25327
- expect(binding('myForm.input.$valid')).toEqual('false');
25613
+ input.clear();
25614
+ input.sendKeys('');
25615
+
25616
+ expect(text.getText()).toEqual('text =');
25617
+ expect(valid.getText()).toContain('false');
25328
25618
  });
25329
25619
 
25330
25620
  it('should be invalid if multi word', function() {
25331
- input('text').enter('hello world');
25332
- expect(binding('myForm.input.$valid')).toEqual('false');
25333
- });
25621
+ input.clear();
25622
+ input.sendKeys('hello world');
25334
25623
 
25335
- it('should not be trimmed', function() {
25336
- input('text').enter('untrimmed ');
25337
- expect(binding('text')).toEqual('untrimmed ');
25338
- expect(binding('myForm.input.$valid')).toEqual('true');
25624
+ expect(valid.getText()).toContain('false');
25339
25625
  });
25340
- </doc:scenario>
25626
+ </doc:protractor>
25341
25627
  </doc:example>
25342
25628
  */
25343
25629
  'text': textInputType,
@@ -25391,24 +25677,30 @@ var inputType = {
25391
25677
  <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
25392
25678
  </form>
25393
25679
  </doc:source>
25394
- <doc:scenario>
25680
+ <doc:protractor>
25681
+ var value = element(by.binding('value'));
25682
+ var valid = element(by.binding('myForm.input.$valid'));
25683
+ var input = element(by.model('value'));
25684
+
25395
25685
  it('should initialize to model', function() {
25396
- expect(binding('value')).toEqual('12');
25397
- expect(binding('myForm.input.$valid')).toEqual('true');
25686
+ expect(value.getText()).toContain('12');
25687
+ expect(valid.getText()).toContain('true');
25398
25688
  });
25399
25689
 
25400
25690
  it('should be invalid if empty', function() {
25401
- input('value').enter('');
25402
- expect(binding('value')).toEqual('');
25403
- expect(binding('myForm.input.$valid')).toEqual('false');
25691
+ input.clear();
25692
+ input.sendKeys('');
25693
+ expect(value.getText()).toEqual('value =');
25694
+ expect(valid.getText()).toContain('false');
25404
25695
  });
25405
25696
 
25406
25697
  it('should be invalid if over max', function() {
25407
- input('value').enter('123');
25408
- expect(binding('value')).toEqual('');
25409
- expect(binding('myForm.input.$valid')).toEqual('false');
25698
+ input.clear();
25699
+ input.sendKeys('123');
25700
+ expect(value.getText()).toEqual('value =');
25701
+ expect(valid.getText()).toContain('false');
25410
25702
  });
25411
- </doc:scenario>
25703
+ </doc:protractor>
25412
25704
  </doc:example>
25413
25705
  */
25414
25706
  'number': numberInputType,
@@ -25460,23 +25752,31 @@ var inputType = {
25460
25752
  <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
25461
25753
  </form>
25462
25754
  </doc:source>
25463
- <doc:scenario>
25755
+ <doc:protractor>
25756
+ var text = element(by.binding('text'));
25757
+ var valid = element(by.binding('myForm.input.$valid'));
25758
+ var input = element(by.model('text'));
25759
+
25464
25760
  it('should initialize to model', function() {
25465
- expect(binding('text')).toEqual('http://google.com');
25466
- expect(binding('myForm.input.$valid')).toEqual('true');
25761
+ expect(text.getText()).toContain('http://google.com');
25762
+ expect(valid.getText()).toContain('true');
25467
25763
  });
25468
25764
 
25469
25765
  it('should be invalid if empty', function() {
25470
- input('text').enter('');
25471
- expect(binding('text')).toEqual('');
25472
- expect(binding('myForm.input.$valid')).toEqual('false');
25766
+ input.clear();
25767
+ input.sendKeys('');
25768
+
25769
+ expect(text.getText()).toEqual('text =');
25770
+ expect(valid.getText()).toContain('false');
25473
25771
  });
25474
25772
 
25475
25773
  it('should be invalid if not url', function() {
25476
- input('text').enter('xxx');
25477
- expect(binding('myForm.input.$valid')).toEqual('false');
25774
+ input.clear();
25775
+ input.sendKeys('box');
25776
+
25777
+ expect(valid.getText()).toContain('false');
25478
25778
  });
25479
- </doc:scenario>
25779
+ </doc:protractor>
25480
25780
  </doc:example>
25481
25781
  */
25482
25782
  'url': urlInputType,
@@ -25528,23 +25828,30 @@ var inputType = {
25528
25828
  <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
25529
25829
  </form>
25530
25830
  </doc:source>
25531
- <doc:scenario>
25831
+ <doc:protractor>
25832
+ var text = element(by.binding('text'));
25833
+ var valid = element(by.binding('myForm.input.$valid'));
25834
+ var input = element(by.model('text'));
25835
+
25532
25836
  it('should initialize to model', function() {
25533
- expect(binding('text')).toEqual('me@example.com');
25534
- expect(binding('myForm.input.$valid')).toEqual('true');
25837
+ expect(text.getText()).toContain('me@example.com');
25838
+ expect(valid.getText()).toContain('true');
25535
25839
  });
25536
25840
 
25537
25841
  it('should be invalid if empty', function() {
25538
- input('text').enter('');
25539
- expect(binding('text')).toEqual('');
25540
- expect(binding('myForm.input.$valid')).toEqual('false');
25842
+ input.clear();
25843
+ input.sendKeys('');
25844
+ expect(text.getText()).toEqual('text =');
25845
+ expect(valid.getText()).toContain('false');
25541
25846
  });
25542
25847
 
25543
25848
  it('should be invalid if not email', function() {
25544
- input('text').enter('xxx');
25545
- expect(binding('myForm.input.$valid')).toEqual('false');
25849
+ input.clear();
25850
+ input.sendKeys('xxx');
25851
+
25852
+ expect(valid.getText()).toContain('false');
25546
25853
  });
25547
- </doc:scenario>
25854
+ </doc:protractor>
25548
25855
  </doc:example>
25549
25856
  */
25550
25857
  'email': emailInputType,
@@ -25562,6 +25869,8 @@ var inputType = {
25562
25869
  * @param {string=} name Property name of the form under which the control is published.
25563
25870
  * @param {string=} ngChange Angular expression to be executed when input changes due to user
25564
25871
  * interaction with the input element.
25872
+ * @param {string} ngValue Angular expression which sets the value to which the expression should
25873
+ * be set when selected.
25565
25874
  *
25566
25875
  * @example
25567
25876
  <doc:example>
@@ -25569,23 +25878,31 @@ var inputType = {
25569
25878
  <script>
25570
25879
  function Ctrl($scope) {
25571
25880
  $scope.color = 'blue';
25881
+ $scope.specialValue = {
25882
+ "id": "12345",
25883
+ "value": "green"
25884
+ };
25572
25885
  }
25573
25886
  </script>
25574
25887
  <form name="myForm" ng-controller="Ctrl">
25575
25888
  <input type="radio" ng-model="color" value="red"> Red <br/>
25576
- <input type="radio" ng-model="color" value="green"> Green <br/>
25889
+ <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/>
25577
25890
  <input type="radio" ng-model="color" value="blue"> Blue <br/>
25578
- <tt>color = {{color}}</tt><br/>
25891
+ <tt>color = {{color | json}}</tt><br/>
25579
25892
  </form>
25893
+ Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
25580
25894
  </doc:source>
25581
- <doc:scenario>
25895
+ <doc:protractor>
25582
25896
  it('should change state', function() {
25583
- expect(binding('color')).toEqual('blue');
25897
+ var color = element(by.binding('color'));
25584
25898
 
25585
- input('color').select('red');
25586
- expect(binding('color')).toEqual('red');
25899
+ expect(color.getText()).toContain('blue');
25900
+
25901
+ element.all(by.model('color')).get(0).click();
25902
+
25903
+ expect(color.getText()).toContain('red');
25587
25904
  });
25588
- </doc:scenario>
25905
+ </doc:protractor>
25589
25906
  </doc:example>
25590
25907
  */
25591
25908
  'radio': radioInputType,
@@ -25622,17 +25939,21 @@ var inputType = {
25622
25939
  <tt>value2 = {{value2}}</tt><br/>
25623
25940
  </form>
25624
25941
  </doc:source>
25625
- <doc:scenario>
25942
+ <doc:protractor>
25626
25943
  it('should change state', function() {
25627
- expect(binding('value1')).toEqual('true');
25628
- expect(binding('value2')).toEqual('YES');
25944
+ var value1 = element(by.binding('value1'));
25945
+ var value2 = element(by.binding('value2'));
25629
25946
 
25630
- input('value1').check();
25631
- input('value2').check();
25632
- expect(binding('value1')).toEqual('false');
25633
- expect(binding('value2')).toEqual('NO');
25947
+ expect(value1.getText()).toContain('true');
25948
+ expect(value2.getText()).toContain('YES');
25949
+
25950
+ element(by.model('value1')).click();
25951
+ element(by.model('value2')).click();
25952
+
25953
+ expect(value1.getText()).toContain('false');
25954
+ expect(value2.getText()).toContain('NO');
25634
25955
  });
25635
- </doc:scenario>
25956
+ </doc:protractor>
25636
25957
  </doc:example>
25637
25958
  */
25638
25959
  'checkbox': checkboxInputType,
@@ -25640,23 +25961,33 @@ var inputType = {
25640
25961
  'hidden': noop,
25641
25962
  'button': noop,
25642
25963
  'submit': noop,
25643
- 'reset': noop
25964
+ 'reset': noop,
25965
+ 'file': noop
25644
25966
  };
25645
25967
 
25968
+ // A helper function to call $setValidity and return the value / undefined,
25969
+ // a pattern that is repeated a lot in the input validation logic.
25970
+ function validate(ctrl, validatorName, validity, value){
25971
+ ctrl.$setValidity(validatorName, validity);
25972
+ return validity ? value : undefined;
25973
+ }
25646
25974
 
25647
25975
  function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25648
25976
  // In composition mode, users are still inputing intermediate text buffer,
25649
25977
  // hold the listener until composition is done.
25650
25978
  // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
25651
- var composing = false;
25979
+ if (!$sniffer.android) {
25980
+ var composing = false;
25652
25981
 
25653
- element.on('compositionstart', function() {
25654
- composing = true;
25655
- });
25982
+ element.on('compositionstart', function(data) {
25983
+ composing = true;
25984
+ });
25656
25985
 
25657
- element.on('compositionend', function() {
25658
- composing = false;
25659
- });
25986
+ element.on('compositionend', function() {
25987
+ composing = false;
25988
+ listener();
25989
+ });
25990
+ }
25660
25991
 
25661
25992
  var listener = function() {
25662
25993
  if (composing) return;
@@ -25670,9 +26001,13 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25670
26001
  }
25671
26002
 
25672
26003
  if (ctrl.$viewValue !== value) {
25673
- scope.$apply(function() {
26004
+ if (scope.$$phase) {
25674
26005
  ctrl.$setViewValue(value);
25675
- });
26006
+ } else {
26007
+ scope.$apply(function() {
26008
+ ctrl.$setViewValue(value);
26009
+ });
26010
+ }
25676
26011
  }
25677
26012
  };
25678
26013
 
@@ -25721,22 +26056,15 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25721
26056
  patternValidator,
25722
26057
  match;
25723
26058
 
25724
- var validate = function(regexp, value) {
25725
- if (ctrl.$isEmpty(value) || regexp.test(value)) {
25726
- ctrl.$setValidity('pattern', true);
25727
- return value;
25728
- } else {
25729
- ctrl.$setValidity('pattern', false);
25730
- return undefined;
25731
- }
25732
- };
25733
-
25734
26059
  if (pattern) {
26060
+ var validateRegex = function(regexp, value) {
26061
+ return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value);
26062
+ };
25735
26063
  match = pattern.match(/^\/(.*)\/([gim]*)$/);
25736
26064
  if (match) {
25737
26065
  pattern = new RegExp(match[1], match[2]);
25738
26066
  patternValidator = function(value) {
25739
- return validate(pattern, value);
26067
+ return validateRegex(pattern, value);
25740
26068
  };
25741
26069
  } else {
25742
26070
  patternValidator = function(value) {
@@ -25747,7 +26075,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25747
26075
  'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern,
25748
26076
  patternObj, startingTag(element));
25749
26077
  }
25750
- return validate(patternObj, value);
26078
+ return validateRegex(patternObj, value);
25751
26079
  };
25752
26080
  }
25753
26081
 
@@ -25759,13 +26087,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25759
26087
  if (attr.ngMinlength) {
25760
26088
  var minlength = int(attr.ngMinlength);
25761
26089
  var minLengthValidator = function(value) {
25762
- if (!ctrl.$isEmpty(value) && value.length < minlength) {
25763
- ctrl.$setValidity('minlength', false);
25764
- return undefined;
25765
- } else {
25766
- ctrl.$setValidity('minlength', true);
25767
- return value;
25768
- }
26090
+ return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value);
25769
26091
  };
25770
26092
 
25771
26093
  ctrl.$parsers.push(minLengthValidator);
@@ -25776,13 +26098,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25776
26098
  if (attr.ngMaxlength) {
25777
26099
  var maxlength = int(attr.ngMaxlength);
25778
26100
  var maxLengthValidator = function(value) {
25779
- if (!ctrl.$isEmpty(value) && value.length > maxlength) {
25780
- ctrl.$setValidity('maxlength', false);
25781
- return undefined;
25782
- } else {
25783
- ctrl.$setValidity('maxlength', true);
25784
- return value;
25785
- }
26101
+ return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value);
25786
26102
  };
25787
26103
 
25788
26104
  ctrl.$parsers.push(maxLengthValidator);
@@ -25811,13 +26127,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25811
26127
  if (attr.min) {
25812
26128
  var minValidator = function(value) {
25813
26129
  var min = parseFloat(attr.min);
25814
- if (!ctrl.$isEmpty(value) && value < min) {
25815
- ctrl.$setValidity('min', false);
25816
- return undefined;
25817
- } else {
25818
- ctrl.$setValidity('min', true);
25819
- return value;
25820
- }
26130
+ return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value);
25821
26131
  };
25822
26132
 
25823
26133
  ctrl.$parsers.push(minValidator);
@@ -25827,13 +26137,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25827
26137
  if (attr.max) {
25828
26138
  var maxValidator = function(value) {
25829
26139
  var max = parseFloat(attr.max);
25830
- if (!ctrl.$isEmpty(value) && value > max) {
25831
- ctrl.$setValidity('max', false);
25832
- return undefined;
25833
- } else {
25834
- ctrl.$setValidity('max', true);
25835
- return value;
25836
- }
26140
+ return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value);
25837
26141
  };
25838
26142
 
25839
26143
  ctrl.$parsers.push(maxValidator);
@@ -25841,14 +26145,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25841
26145
  }
25842
26146
 
25843
26147
  ctrl.$formatters.push(function(value) {
25844
-
25845
- if (ctrl.$isEmpty(value) || isNumber(value)) {
25846
- ctrl.$setValidity('number', true);
25847
- return value;
25848
- } else {
25849
- ctrl.$setValidity('number', false);
25850
- return undefined;
25851
- }
26148
+ return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value);
25852
26149
  });
25853
26150
  }
25854
26151
 
@@ -25856,13 +26153,7 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25856
26153
  textInputType(scope, element, attr, ctrl, $sniffer, $browser);
25857
26154
 
25858
26155
  var urlValidator = function(value) {
25859
- if (ctrl.$isEmpty(value) || URL_REGEXP.test(value)) {
25860
- ctrl.$setValidity('url', true);
25861
- return value;
25862
- } else {
25863
- ctrl.$setValidity('url', false);
25864
- return undefined;
25865
- }
26156
+ return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value);
25866
26157
  };
25867
26158
 
25868
26159
  ctrl.$formatters.push(urlValidator);
@@ -25873,13 +26164,7 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
25873
26164
  textInputType(scope, element, attr, ctrl, $sniffer, $browser);
25874
26165
 
25875
26166
  var emailValidator = function(value) {
25876
- if (ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value)) {
25877
- ctrl.$setValidity('email', true);
25878
- return value;
25879
- } else {
25880
- ctrl.$setValidity('email', false);
25881
- return undefined;
25882
- }
26167
+ return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value);
25883
26168
  };
25884
26169
 
25885
26170
  ctrl.$formatters.push(emailValidator);
@@ -26023,44 +26308,59 @@ function checkboxInputType(scope, element, attr, ctrl) {
26023
26308
  <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
26024
26309
  </div>
26025
26310
  </doc:source>
26026
- <doc:scenario>
26311
+ <doc:protractor>
26312
+ var user = element(by.binding('{{user}}'));
26313
+ var userNameValid = element(by.binding('myForm.userName.$valid'));
26314
+ var lastNameValid = element(by.binding('myForm.lastName.$valid'));
26315
+ var lastNameError = element(by.binding('myForm.lastName.$error'));
26316
+ var formValid = element(by.binding('myForm.$valid'));
26317
+ var userNameInput = element(by.model('user.name'));
26318
+ var userLastInput = element(by.model('user.last'));
26319
+
26027
26320
  it('should initialize to model', function() {
26028
- expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
26029
- expect(binding('myForm.userName.$valid')).toEqual('true');
26030
- expect(binding('myForm.$valid')).toEqual('true');
26321
+ expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
26322
+ expect(userNameValid.getText()).toContain('true');
26323
+ expect(formValid.getText()).toContain('true');
26031
26324
  });
26032
26325
 
26033
26326
  it('should be invalid if empty when required', function() {
26034
- input('user.name').enter('');
26035
- expect(binding('user')).toEqual('{"last":"visitor"}');
26036
- expect(binding('myForm.userName.$valid')).toEqual('false');
26037
- expect(binding('myForm.$valid')).toEqual('false');
26327
+ userNameInput.clear();
26328
+ userNameInput.sendKeys('');
26329
+
26330
+ expect(user.getText()).toContain('{"last":"visitor"}');
26331
+ expect(userNameValid.getText()).toContain('false');
26332
+ expect(formValid.getText()).toContain('false');
26038
26333
  });
26039
26334
 
26040
26335
  it('should be valid if empty when min length is set', function() {
26041
- input('user.last').enter('');
26042
- expect(binding('user')).toEqual('{"name":"guest","last":""}');
26043
- expect(binding('myForm.lastName.$valid')).toEqual('true');
26044
- expect(binding('myForm.$valid')).toEqual('true');
26336
+ userLastInput.clear();
26337
+ userLastInput.sendKeys('');
26338
+
26339
+ expect(user.getText()).toContain('{"name":"guest","last":""}');
26340
+ expect(lastNameValid.getText()).toContain('true');
26341
+ expect(formValid.getText()).toContain('true');
26045
26342
  });
26046
26343
 
26047
26344
  it('should be invalid if less than required min length', function() {
26048
- input('user.last').enter('xx');
26049
- expect(binding('user')).toEqual('{"name":"guest"}');
26050
- expect(binding('myForm.lastName.$valid')).toEqual('false');
26051
- expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
26052
- expect(binding('myForm.$valid')).toEqual('false');
26345
+ userLastInput.clear();
26346
+ userLastInput.sendKeys('xx');
26347
+
26348
+ expect(user.getText()).toContain('{"name":"guest"}');
26349
+ expect(lastNameValid.getText()).toContain('false');
26350
+ expect(lastNameError.getText()).toContain('minlength');
26351
+ expect(formValid.getText()).toContain('false');
26053
26352
  });
26054
26353
 
26055
26354
  it('should be invalid if longer than max length', function() {
26056
- input('user.last').enter('some ridiculously long name');
26057
- expect(binding('user'))
26058
- .toEqual('{"name":"guest"}');
26059
- expect(binding('myForm.lastName.$valid')).toEqual('false');
26060
- expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
26061
- expect(binding('myForm.$valid')).toEqual('false');
26355
+ userLastInput.clear();
26356
+ userLastInput.sendKeys('some ridiculously long name');
26357
+
26358
+ expect(user.getText()).toContain('{"name":"guest"}');
26359
+ expect(lastNameValid.getText()).toContain('false');
26360
+ expect(lastNameError.getText()).toContain('maxlength');
26361
+ expect(formValid.getText()).toContain('false');
26062
26362
  });
26063
- </doc:scenario>
26363
+ </doc:protractor>
26064
26364
  </doc:example>
26065
26365
  */
26066
26366
  var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
@@ -26192,14 +26492,23 @@ var VALID_CLASS = 'ng-valid',
26192
26492
  <textarea ng-model="userContent"></textarea>
26193
26493
  </form>
26194
26494
  </file>
26195
- <file name="scenario.js">
26495
+ <file name="protractorTest.js">
26196
26496
  it('should data-bind and become invalid', function() {
26197
- var contentEditable = element('[contenteditable]');
26497
+ if (browser.params.browser = 'safari') {
26498
+ // SafariDriver can't handle contenteditable.
26499
+ return;
26500
+ };
26501
+ var contentEditable = element(by.css('.doc-example-live [contenteditable]'));
26502
+
26503
+ expect(contentEditable.getText()).toEqual('Change me!');
26504
+
26505
+ // Firefox driver doesn't trigger the proper events on 'clear', so do this hack
26506
+ contentEditable.click();
26507
+ contentEditable.sendKeys(protractor.Key.chord(protractor.Key.COMMAND, "a"));
26508
+ contentEditable.sendKeys(protractor.Key.BACK_SPACE);
26198
26509
 
26199
- expect(contentEditable.text()).toEqual('Change me!');
26200
- input('userContent').enter('');
26201
- expect(contentEditable.text()).toEqual('');
26202
- expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
26510
+ expect(contentEditable.getText()).toEqual('');
26511
+ expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
26203
26512
  });
26204
26513
  </file>
26205
26514
  * </example>
@@ -26252,6 +26561,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
26252
26561
  * You can override this for input directives whose concept of being empty is different to the
26253
26562
  * default. The `checkboxInputType` directive does this because in its case a value of `false`
26254
26563
  * implies empty.
26564
+ *
26565
+ * @param {*} value Reference to check.
26566
+ * @returns {boolean} True if `value` is empty.
26255
26567
  */
26256
26568
  this.$isEmpty = function(value) {
26257
26569
  return isUndefined(value) || value === '' || value === null || value !== value;
@@ -26479,7 +26791,10 @@ var ngModelDirective = function() {
26479
26791
  * @name ng.directive:ngChange
26480
26792
  *
26481
26793
  * @description
26482
- * Evaluate given expression when user changes the input.
26794
+ * Evaluate the given expression when the user changes the input.
26795
+ * The expression is evaluated immediately, unlike the JavaScript onchange event
26796
+ * which only triggers at the end of a change (usually, when the user leaves the
26797
+ * form element or presses the return key).
26483
26798
  * The expression is not evaluated when the value change is coming from the model.
26484
26799
  *
26485
26800
  * Note, this directive requires `ngModel` to be present.
@@ -26503,24 +26818,30 @@ var ngModelDirective = function() {
26503
26818
  * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
26504
26819
  * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
26505
26820
  * <label for="ng-change-example2">Confirmed</label><br />
26506
- * debug = {{confirmed}}<br />
26507
- * counter = {{counter}}
26821
+ * <tt>debug = {{confirmed}}</tt><br/>
26822
+ * <tt>counter = {{counter}}</tt><br/>
26508
26823
  * </div>
26509
26824
  * </doc:source>
26510
- * <doc:scenario>
26825
+ * <doc:protractor>
26826
+ * var counter = element(by.binding('counter'));
26827
+ * var debug = element(by.binding('confirmed'));
26828
+ *
26511
26829
  * it('should evaluate the expression if changing from view', function() {
26512
- * expect(binding('counter')).toEqual('0');
26513
- * element('#ng-change-example1').click();
26514
- * expect(binding('counter')).toEqual('1');
26515
- * expect(binding('confirmed')).toEqual('true');
26830
+ * expect(counter.getText()).toContain('0');
26831
+ *
26832
+ * element(by.id('ng-change-example1')).click();
26833
+ *
26834
+ * expect(counter.getText()).toContain('1');
26835
+ * expect(debug.getText()).toContain('true');
26516
26836
  * });
26517
26837
  *
26518
26838
  * it('should not evaluate the expression if changing from model', function() {
26519
- * element('#ng-change-example2').click();
26520
- * expect(binding('counter')).toEqual('0');
26521
- * expect(binding('confirmed')).toEqual('true');
26839
+ * element(by.id('ng-change-example2')).click();
26840
+
26841
+ * expect(counter.getText()).toContain('0');
26842
+ * expect(debug.getText()).toContain('true');
26522
26843
  * });
26523
- * </doc:scenario>
26844
+ * </doc:protractor>
26524
26845
  * </doc:example>
26525
26846
  */
26526
26847
  var ngChangeDirective = valueFn({
@@ -26593,20 +26914,26 @@ var requiredDirective = function() {
26593
26914
  <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
26594
26915
  </form>
26595
26916
  </doc:source>
26596
- <doc:scenario>
26917
+ <doc:protractor>
26918
+ var listInput = element(by.model('names'));
26919
+ var names = element(by.binding('{{names}}'));
26920
+ var valid = element(by.binding('myForm.namesInput.$valid'));
26921
+ var error = element(by.css('span.error'));
26922
+
26597
26923
  it('should initialize to model', function() {
26598
- expect(binding('names')).toEqual('["igor","misko","vojta"]');
26599
- expect(binding('myForm.namesInput.$valid')).toEqual('true');
26600
- expect(element('span.error').css('display')).toBe('none');
26924
+ expect(names.getText()).toContain('["igor","misko","vojta"]');
26925
+ expect(valid.getText()).toContain('true');
26926
+ expect(error.getCssValue('display')).toBe('none');
26601
26927
  });
26602
26928
 
26603
26929
  it('should be invalid if empty', function() {
26604
- input('names').enter('');
26605
- expect(binding('names')).toEqual('');
26606
- expect(binding('myForm.namesInput.$valid')).toEqual('false');
26607
- expect(element('span.error').css('display')).not().toBe('none');
26608
- });
26609
- </doc:scenario>
26930
+ listInput.clear();
26931
+ listInput.sendKeys('');
26932
+
26933
+ expect(names.getText()).toContain('');
26934
+ expect(valid.getText()).toContain('false');
26935
+ expect(error.getCssValue('display')).not.toBe('none'); });
26936
+ </doc:protractor>
26610
26937
  </doc:example>
26611
26938
  */
26612
26939
  var ngListDirective = function() {
@@ -26688,15 +27015,17 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
26688
27015
  <div>You chose {{my.favorite}}</div>
26689
27016
  </form>
26690
27017
  </doc:source>
26691
- <doc:scenario>
27018
+ <doc:protractor>
27019
+ var favorite = element(by.binding('my.favorite'));
27020
+
26692
27021
  it('should initialize to model', function() {
26693
- expect(binding('my.favorite')).toEqual('unicorns');
27022
+ expect(favorite.getText()).toContain('unicorns');
26694
27023
  });
26695
27024
  it('should bind the values to the inputs', function() {
26696
- input('my.favorite').select('pizza');
26697
- expect(binding('my.favorite')).toEqual('pizza');
27025
+ element.all(by.model('my.favorite')).get(0).click();
27026
+ expect(favorite.getText()).toContain('pizza');
26698
27027
  });
26699
- </doc:scenario>
27028
+ </doc:protractor>
26700
27029
  </doc:example>
26701
27030
  */
26702
27031
  var ngValueDirective = function() {
@@ -26756,13 +27085,17 @@ var ngValueDirective = function() {
26756
27085
  Hello <span ng-bind="name"></span>!
26757
27086
  </div>
26758
27087
  </doc:source>
26759
- <doc:scenario>
27088
+ <doc:protractor>
26760
27089
  it('should check ng-bind', function() {
26761
- expect(using('.doc-example-live').binding('name')).toBe('Whirled');
26762
- using('.doc-example-live').input('name').enter('world');
26763
- expect(using('.doc-example-live').binding('name')).toBe('world');
27090
+ var exampleContainer = $('.doc-example-live');
27091
+ var nameInput = element(by.model('name'));
27092
+
27093
+ expect(exampleContainer.findElement(by.binding('name')).getText()).toBe('Whirled');
27094
+ nameInput.clear();
27095
+ nameInput.sendKeys('world');
27096
+ expect(exampleContainer.findElement(by.binding('name')).getText()).toBe('world');
26764
27097
  });
26765
- </doc:scenario>
27098
+ </doc:protractor>
26766
27099
  </doc:example>
26767
27100
  */
26768
27101
  var ngBindDirective = ngDirective(function(scope, element, attr) {
@@ -26808,20 +27141,22 @@ var ngBindDirective = ngDirective(function(scope, element, attr) {
26808
27141
  <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
26809
27142
  </div>
26810
27143
  </doc:source>
26811
- <doc:scenario>
27144
+ <doc:protractor>
26812
27145
  it('should check ng-bind', function() {
26813
- expect(using('.doc-example-live').binding('salutation')).
26814
- toBe('Hello');
26815
- expect(using('.doc-example-live').binding('name')).
26816
- toBe('World');
26817
- using('.doc-example-live').input('salutation').enter('Greetings');
26818
- using('.doc-example-live').input('name').enter('user');
26819
- expect(using('.doc-example-live').binding('salutation')).
26820
- toBe('Greetings');
26821
- expect(using('.doc-example-live').binding('name')).
26822
- toBe('user');
27146
+ var salutationElem = element(by.binding('salutation'));
27147
+ var salutationInput = element(by.model('salutation'));
27148
+ var nameInput = element(by.model('name'));
27149
+
27150
+ expect(salutationElem.getText()).toBe('Hello World!');
27151
+
27152
+ salutationInput.clear();
27153
+ salutationInput.sendKeys('Greetings');
27154
+ nameInput.clear();
27155
+ nameInput.sendKeys('user');
27156
+
27157
+ expect(salutationElem.getText()).toBe('Greetings user!');
26823
27158
  });
26824
- </doc:scenario>
27159
+ </doc:protractor>
26825
27160
  </doc:example>
26826
27161
  */
26827
27162
  var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
@@ -26874,12 +27209,10 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
26874
27209
  }]);
26875
27210
  </file>
26876
27211
 
26877
- <file name="scenario.js">
27212
+ <file name="protractorTest.js">
26878
27213
  it('should check ng-bind-html', function() {
26879
- expect(using('.doc-example-live').binding('myHTML')).
26880
- toBe(
26881
- 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'
26882
- );
27214
+ expect(element(by.binding('myHTML')).getText()).toBe(
27215
+ 'I am an HTMLstring with links! and other stuff');
26883
27216
  });
26884
27217
  </file>
26885
27218
  </example>
@@ -27011,31 +27344,34 @@ function classDirective(name, selector) {
27011
27344
  color: red;
27012
27345
  }
27013
27346
  </file>
27014
- <file name="scenario.js">
27347
+ <file name="protractorTest.js">
27348
+ var ps = element.all(by.css('.doc-example-live p'));
27349
+
27015
27350
  it('should let you toggle the class', function() {
27016
27351
 
27017
- expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/bold/);
27018
- expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/red/);
27352
+ expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
27353
+ expect(ps.first().getAttribute('class')).not.toMatch(/red/);
27019
27354
 
27020
- input('important').check();
27021
- expect(element('.doc-example-live p:first').prop('className')).toMatch(/bold/);
27355
+ element(by.model('important')).click();
27356
+ expect(ps.first().getAttribute('class')).toMatch(/bold/);
27022
27357
 
27023
- input('error').check();
27024
- expect(element('.doc-example-live p:first').prop('className')).toMatch(/red/);
27358
+ element(by.model('error')).click();
27359
+ expect(ps.first().getAttribute('class')).toMatch(/red/);
27025
27360
  });
27026
27361
 
27027
27362
  it('should let you toggle string example', function() {
27028
- expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('');
27029
- input('style').enter('red');
27030
- expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('red');
27363
+ expect(ps.get(1).getAttribute('class')).toBe('');
27364
+ element(by.model('style')).clear();
27365
+ element(by.model('style')).sendKeys('red');
27366
+ expect(ps.get(1).getAttribute('class')).toBe('red');
27031
27367
  });
27032
27368
 
27033
27369
  it('array example should have 3 classes', function() {
27034
- expect(element('.doc-example-live p:last').prop('className')).toBe('');
27035
- input('style1').enter('bold');
27036
- input('style2').enter('strike');
27037
- input('style3').enter('red');
27038
- expect(element('.doc-example-live p:last').prop('className')).toBe('bold strike red');
27370
+ expect(ps.last().getAttribute('class')).toBe('');
27371
+ element(by.model('style1')).sendKeys('bold');
27372
+ element(by.model('style2')).sendKeys('strike');
27373
+ element(by.model('style3')).sendKeys('red');
27374
+ expect(ps.last().getAttribute('class')).toBe('bold strike red');
27039
27375
  });
27040
27376
  </file>
27041
27377
  </example>
@@ -27046,8 +27382,8 @@ function classDirective(name, selector) {
27046
27382
 
27047
27383
  <example animations="true">
27048
27384
  <file name="index.html">
27049
- <input type="button" value="set" ng-click="myVar='my-class'">
27050
- <input type="button" value="clear" ng-click="myVar=''">
27385
+ <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
27386
+ <input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
27051
27387
  <br>
27052
27388
  <span class="base-class" ng-class="myVar">Sample Text</span>
27053
27389
  </file>
@@ -27062,19 +27398,19 @@ function classDirective(name, selector) {
27062
27398
  font-size:3em;
27063
27399
  }
27064
27400
  </file>
27065
- <file name="scenario.js">
27401
+ <file name="protractorTest.js">
27066
27402
  it('should check ng-class', function() {
27067
- expect(element('.doc-example-live span').prop('className')).not().
27403
+ expect(element(by.css('.base-class')).getAttribute('class')).not.
27068
27404
  toMatch(/my-class/);
27069
27405
 
27070
- using('.doc-example-live').element(':button:first').click();
27406
+ element(by.id('setbtn')).click();
27071
27407
 
27072
- expect(element('.doc-example-live span').prop('className')).
27408
+ expect(element(by.css('.base-class')).getAttribute('class')).
27073
27409
  toMatch(/my-class/);
27074
27410
 
27075
- using('.doc-example-live').element(':button:last').click();
27411
+ element(by.id('clearbtn')).click();
27076
27412
 
27077
- expect(element('.doc-example-live span').prop('className')).not().
27413
+ expect(element(by.css('.base-class')).getAttribute('class')).not.
27078
27414
  toMatch(/my-class/);
27079
27415
  });
27080
27416
  </file>
@@ -27126,11 +27462,11 @@ var ngClassDirective = classDirective('', true);
27126
27462
  color: blue;
27127
27463
  }
27128
27464
  </file>
27129
- <file name="scenario.js">
27465
+ <file name="protractorTest.js">
27130
27466
  it('should check ng-class-odd and ng-class-even', function() {
27131
- expect(element('.doc-example-live li:first span').prop('className')).
27467
+ expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
27132
27468
  toMatch(/odd/);
27133
- expect(element('.doc-example-live li:last span').prop('className')).
27469
+ expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
27134
27470
  toMatch(/even/);
27135
27471
  });
27136
27472
  </file>
@@ -27174,11 +27510,11 @@ var ngClassOddDirective = classDirective('Odd', 0);
27174
27510
  color: blue;
27175
27511
  }
27176
27512
  </file>
27177
- <file name="scenario.js">
27513
+ <file name="protractorTest.js">
27178
27514
  it('should check ng-class-odd and ng-class-even', function() {
27179
- expect(element('.doc-example-live li:first span').prop('className')).
27515
+ expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
27180
27516
  toMatch(/odd/);
27181
- expect(element('.doc-example-live li:last span').prop('className')).
27517
+ expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
27182
27518
  toMatch(/even/);
27183
27519
  });
27184
27520
  </file>
@@ -27221,7 +27557,7 @@ var ngClassEvenDirective = classDirective('Even', 1);
27221
27557
  *
27222
27558
  * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
27223
27559
  * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
27224
- * class `ngCloak` in addition to the `ngCloak` directive as shown in the example below.
27560
+ * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below.
27225
27561
  *
27226
27562
  * @element ANY
27227
27563
  *
@@ -27231,14 +27567,14 @@ var ngClassEvenDirective = classDirective('Even', 1);
27231
27567
  <div id="template1" ng-cloak>{{ 'hello' }}</div>
27232
27568
  <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
27233
27569
  </doc:source>
27234
- <doc:scenario>
27570
+ <doc:protractor>
27235
27571
  it('should remove the template directive and css class', function() {
27236
- expect(element('.doc-example-live #template1').attr('ng-cloak')).
27237
- not().toBeDefined();
27238
- expect(element('.doc-example-live #template2').attr('ng-cloak')).
27239
- not().toBeDefined();
27572
+ expect($('.doc-example-live #template1').getAttribute('ng-cloak')).
27573
+ toBeNull();
27574
+ expect($('.doc-example-live #template2').getAttribute('ng-cloak')).
27575
+ toBeNull();
27240
27576
  });
27241
- </doc:scenario>
27577
+ </doc:protractor>
27242
27578
  </doc:example>
27243
27579
  *
27244
27580
  */
@@ -27331,22 +27667,36 @@ var ngCloakDirective = ngDirective({
27331
27667
  </ul>
27332
27668
  </div>
27333
27669
  </doc:source>
27334
- <doc:scenario>
27670
+ <doc:protractor>
27335
27671
  it('should check controller as', function() {
27336
- expect(element('#ctrl-as-exmpl>:input').val()).toBe('John Smith');
27337
- expect(element('#ctrl-as-exmpl li:nth-child(1) input').val())
27338
- .toBe('408 555 1212');
27339
- expect(element('#ctrl-as-exmpl li:nth-child(2) input').val())
27340
- .toBe('john.smith@example.org');
27341
-
27342
- element('#ctrl-as-exmpl li:first a:contains("clear")').click();
27343
- expect(element('#ctrl-as-exmpl li:first input').val()).toBe('');
27344
-
27345
- element('#ctrl-as-exmpl li:last a:contains("add")').click();
27346
- expect(element('#ctrl-as-exmpl li:nth-child(3) input').val())
27347
- .toBe('yourname@example.org');
27672
+ var container = element(by.id('ctrl-as-exmpl'));
27673
+
27674
+ expect(container.findElement(by.model('settings.name'))
27675
+ .getAttribute('value')).toBe('John Smith');
27676
+
27677
+ var firstRepeat =
27678
+ container.findElement(by.repeater('contact in settings.contacts').row(0));
27679
+ var secondRepeat =
27680
+ container.findElement(by.repeater('contact in settings.contacts').row(1));
27681
+
27682
+ expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
27683
+ .toBe('408 555 1212');
27684
+ expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
27685
+ .toBe('john.smith@example.org');
27686
+
27687
+ firstRepeat.findElement(by.linkText('clear')).click()
27688
+
27689
+ expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
27690
+ .toBe('');
27691
+
27692
+ container.findElement(by.linkText('add')).click();
27693
+
27694
+ expect(container.findElement(by.repeater('contact in settings.contacts').row(2))
27695
+ .findElement(by.model('contact.value'))
27696
+ .getAttribute('value'))
27697
+ .toBe('yourname@example.org');
27348
27698
  });
27349
- </doc:scenario>
27699
+ </doc:protractor>
27350
27700
  </doc:example>
27351
27701
  <doc:example>
27352
27702
  <doc:source>
@@ -27394,22 +27744,36 @@ var ngCloakDirective = ngDirective({
27394
27744
  </ul>
27395
27745
  </div>
27396
27746
  </doc:source>
27397
- <doc:scenario>
27747
+ <doc:protractor>
27398
27748
  it('should check controller', function() {
27399
- expect(element('#ctrl-exmpl>:input').val()).toBe('John Smith');
27400
- expect(element('#ctrl-exmpl li:nth-child(1) input').val())
27401
- .toBe('408 555 1212');
27402
- expect(element('#ctrl-exmpl li:nth-child(2) input').val())
27403
- .toBe('john.smith@example.org');
27404
-
27405
- element('#ctrl-exmpl li:first a:contains("clear")').click();
27406
- expect(element('#ctrl-exmpl li:first input').val()).toBe('');
27407
-
27408
- element('#ctrl-exmpl li:last a:contains("add")').click();
27409
- expect(element('#ctrl-exmpl li:nth-child(3) input').val())
27410
- .toBe('yourname@example.org');
27749
+ var container = element(by.id('ctrl-exmpl'));
27750
+
27751
+ expect(container.findElement(by.model('name'))
27752
+ .getAttribute('value')).toBe('John Smith');
27753
+
27754
+ var firstRepeat =
27755
+ container.findElement(by.repeater('contact in contacts').row(0));
27756
+ var secondRepeat =
27757
+ container.findElement(by.repeater('contact in contacts').row(1));
27758
+
27759
+ expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
27760
+ .toBe('408 555 1212');
27761
+ expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
27762
+ .toBe('john.smith@example.org');
27763
+
27764
+ firstRepeat.findElement(by.linkText('clear')).click()
27765
+
27766
+ expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
27767
+ .toBe('');
27768
+
27769
+ container.findElement(by.linkText('add')).click();
27770
+
27771
+ expect(container.findElement(by.repeater('contact in contacts').row(2))
27772
+ .findElement(by.model('contact.value'))
27773
+ .getAttribute('value'))
27774
+ .toBe('yourname@example.org');
27411
27775
  });
27412
- </doc:scenario>
27776
+ </doc:protractor>
27413
27777
  </doc:example>
27414
27778
 
27415
27779
  */
@@ -27472,6 +27836,7 @@ var ngControllerDirective = [function() {
27472
27836
  * an element is clicked.
27473
27837
  *
27474
27838
  * @element ANY
27839
+ * @priority 0
27475
27840
  * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
27476
27841
  * click. (Event object is available as `$event`)
27477
27842
  *
@@ -27483,13 +27848,13 @@ var ngControllerDirective = [function() {
27483
27848
  </button>
27484
27849
  count: {{count}}
27485
27850
  </doc:source>
27486
- <doc:scenario>
27851
+ <doc:protractor>
27487
27852
  it('should check ng-click', function() {
27488
- expect(binding('count')).toBe('0');
27489
- element('.doc-example-live :button').click();
27490
- expect(binding('count')).toBe('1');
27853
+ expect(element(by.binding('count')).getText()).toMatch('0');
27854
+ element(by.css('.doc-example-live button')).click();
27855
+ expect(element(by.binding('count')).getText()).toMatch('1');
27491
27856
  });
27492
- </doc:scenario>
27857
+ </doc:protractor>
27493
27858
  </doc:example>
27494
27859
  */
27495
27860
  /*
@@ -27528,11 +27893,19 @@ forEach(
27528
27893
  * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event.
27529
27894
  *
27530
27895
  * @element ANY
27896
+ * @priority 0
27531
27897
  * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
27532
27898
  * a dblclick. (The Event object is available as `$event`)
27533
27899
  *
27534
27900
  * @example
27535
- * See {@link ng.directive:ngClick ngClick}
27901
+ <doc:example>
27902
+ <doc:source>
27903
+ <button ng-dblclick="count = count + 1" ng-init="count=0">
27904
+ Increment (on double click)
27905
+ </button>
27906
+ count: {{count}}
27907
+ </doc:source>
27908
+ </doc:example>
27536
27909
  */
27537
27910
 
27538
27911
 
@@ -27544,11 +27917,19 @@ forEach(
27544
27917
  * The ngMousedown directive allows you to specify custom behavior on mousedown event.
27545
27918
  *
27546
27919
  * @element ANY
27920
+ * @priority 0
27547
27921
  * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
27548
27922
  * mousedown. (Event object is available as `$event`)
27549
27923
  *
27550
27924
  * @example
27551
- * See {@link ng.directive:ngClick ngClick}
27925
+ <doc:example>
27926
+ <doc:source>
27927
+ <button ng-mousedown="count = count + 1" ng-init="count=0">
27928
+ Increment (on mouse down)
27929
+ </button>
27930
+ count: {{count}}
27931
+ </doc:source>
27932
+ </doc:example>
27552
27933
  */
27553
27934
 
27554
27935
 
@@ -27560,11 +27941,19 @@ forEach(
27560
27941
  * Specify custom behavior on mouseup event.
27561
27942
  *
27562
27943
  * @element ANY
27944
+ * @priority 0
27563
27945
  * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
27564
27946
  * mouseup. (Event object is available as `$event`)
27565
27947
  *
27566
27948
  * @example
27567
- * See {@link ng.directive:ngClick ngClick}
27949
+ <doc:example>
27950
+ <doc:source>
27951
+ <button ng-mouseup="count = count + 1" ng-init="count=0">
27952
+ Increment (on mouse up)
27953
+ </button>
27954
+ count: {{count}}
27955
+ </doc:source>
27956
+ </doc:example>
27568
27957
  */
27569
27958
 
27570
27959
  /**
@@ -27575,11 +27964,19 @@ forEach(
27575
27964
  * Specify custom behavior on mouseover event.
27576
27965
  *
27577
27966
  * @element ANY
27967
+ * @priority 0
27578
27968
  * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
27579
27969
  * mouseover. (Event object is available as `$event`)
27580
27970
  *
27581
27971
  * @example
27582
- * See {@link ng.directive:ngClick ngClick}
27972
+ <doc:example>
27973
+ <doc:source>
27974
+ <button ng-mouseover="count = count + 1" ng-init="count=0">
27975
+ Increment (when mouse is over)
27976
+ </button>
27977
+ count: {{count}}
27978
+ </doc:source>
27979
+ </doc:example>
27583
27980
  */
27584
27981
 
27585
27982
 
@@ -27591,11 +27988,19 @@ forEach(
27591
27988
  * Specify custom behavior on mouseenter event.
27592
27989
  *
27593
27990
  * @element ANY
27991
+ * @priority 0
27594
27992
  * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
27595
27993
  * mouseenter. (Event object is available as `$event`)
27596
27994
  *
27597
27995
  * @example
27598
- * See {@link ng.directive:ngClick ngClick}
27996
+ <doc:example>
27997
+ <doc:source>
27998
+ <button ng-mouseenter="count = count + 1" ng-init="count=0">
27999
+ Increment (when mouse enters)
28000
+ </button>
28001
+ count: {{count}}
28002
+ </doc:source>
28003
+ </doc:example>
27599
28004
  */
27600
28005
 
27601
28006
 
@@ -27607,11 +28012,19 @@ forEach(
27607
28012
  * Specify custom behavior on mouseleave event.
27608
28013
  *
27609
28014
  * @element ANY
28015
+ * @priority 0
27610
28016
  * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
27611
28017
  * mouseleave. (Event object is available as `$event`)
27612
28018
  *
27613
28019
  * @example
27614
- * See {@link ng.directive:ngClick ngClick}
28020
+ <doc:example>
28021
+ <doc:source>
28022
+ <button ng-mouseleave="count = count + 1" ng-init="count=0">
28023
+ Increment (when mouse leaves)
28024
+ </button>
28025
+ count: {{count}}
28026
+ </doc:source>
28027
+ </doc:example>
27615
28028
  */
27616
28029
 
27617
28030
 
@@ -27623,11 +28036,19 @@ forEach(
27623
28036
  * Specify custom behavior on mousemove event.
27624
28037
  *
27625
28038
  * @element ANY
28039
+ * @priority 0
27626
28040
  * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
27627
28041
  * mousemove. (Event object is available as `$event`)
27628
28042
  *
27629
28043
  * @example
27630
- * See {@link ng.directive:ngClick ngClick}
28044
+ <doc:example>
28045
+ <doc:source>
28046
+ <button ng-mousemove="count = count + 1" ng-init="count=0">
28047
+ Increment (when mouse moves)
28048
+ </button>
28049
+ count: {{count}}
28050
+ </doc:source>
28051
+ </doc:example>
27631
28052
  */
27632
28053
 
27633
28054
 
@@ -27639,11 +28060,17 @@ forEach(
27639
28060
  * Specify custom behavior on keydown event.
27640
28061
  *
27641
28062
  * @element ANY
28063
+ * @priority 0
27642
28064
  * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
27643
28065
  * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
27644
28066
  *
27645
28067
  * @example
27646
- * See {@link ng.directive:ngClick ngClick}
28068
+ <doc:example>
28069
+ <doc:source>
28070
+ <input ng-keydown="count = count + 1" ng-init="count=0">
28071
+ key down count: {{count}}
28072
+ </doc:source>
28073
+ </doc:example>
27647
28074
  */
27648
28075
 
27649
28076
 
@@ -27655,11 +28082,17 @@ forEach(
27655
28082
  * Specify custom behavior on keyup event.
27656
28083
  *
27657
28084
  * @element ANY
28085
+ * @priority 0
27658
28086
  * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
27659
28087
  * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
27660
28088
  *
27661
28089
  * @example
27662
- * See {@link ng.directive:ngClick ngClick}
28090
+ <doc:example>
28091
+ <doc:source>
28092
+ <input ng-keyup="count = count + 1" ng-init="count=0">
28093
+ key up count: {{count}}
28094
+ </doc:source>
28095
+ </doc:example>
27663
28096
  */
27664
28097
 
27665
28098
 
@@ -27675,7 +28108,12 @@ forEach(
27675
28108
  * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
27676
28109
  *
27677
28110
  * @example
27678
- * See {@link ng.directive:ngClick ngClick}
28111
+ <doc:example>
28112
+ <doc:source>
28113
+ <input ng-keypress="count = count + 1" ng-init="count=0">
28114
+ key press count: {{count}}
28115
+ </doc:source>
28116
+ </doc:example>
27679
28117
  */
27680
28118
 
27681
28119
 
@@ -27687,10 +28125,11 @@ forEach(
27687
28125
  * Enables binding angular expressions to onsubmit events.
27688
28126
  *
27689
28127
  * Additionally it prevents the default action (which for form means sending the request to the
27690
- * server and reloading the current page) **but only if the form does not contain an `action`
27691
- * attribute**.
28128
+ * server and reloading the current page), but only if the form does not contain `action`,
28129
+ * `data-action`, or `x-action` attributes.
27692
28130
  *
27693
28131
  * @element form
28132
+ * @priority 0
27694
28133
  * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`)
27695
28134
  *
27696
28135
  * @example
@@ -27715,20 +28154,20 @@ forEach(
27715
28154
  <pre>list={{list}}</pre>
27716
28155
  </form>
27717
28156
  </doc:source>
27718
- <doc:scenario>
28157
+ <doc:protractor>
27719
28158
  it('should check ng-submit', function() {
27720
- expect(binding('list')).toBe('[]');
27721
- element('.doc-example-live #submit').click();
27722
- expect(binding('list')).toBe('["hello"]');
27723
- expect(input('text').val()).toBe('');
28159
+ expect(element(by.binding('list')).getText()).toBe('list=[]');
28160
+ element(by.css('.doc-example-live #submit')).click();
28161
+ expect(element(by.binding('list')).getText()).toContain('hello');
28162
+ expect(element(by.input('text')).getAttribute('value')).toBe('');
27724
28163
  });
27725
28164
  it('should ignore empty strings', function() {
27726
- expect(binding('list')).toBe('[]');
27727
- element('.doc-example-live #submit').click();
27728
- element('.doc-example-live #submit').click();
27729
- expect(binding('list')).toBe('["hello"]');
27730
- });
27731
- </doc:scenario>
28165
+ expect(element(by.binding('list')).getText()).toBe('list=[]');
28166
+ element(by.css('.doc-example-live #submit')).click();
28167
+ element(by.css('.doc-example-live #submit')).click();
28168
+ expect(element(by.binding('list')).getText()).toContain('hello');
28169
+ });
28170
+ </doc:protractor>
27732
28171
  </doc:example>
27733
28172
  */
27734
28173
 
@@ -27740,6 +28179,7 @@ forEach(
27740
28179
  * Specify custom behavior on focus event.
27741
28180
  *
27742
28181
  * @element window, input, select, textarea, a
28182
+ * @priority 0
27743
28183
  * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon
27744
28184
  * focus. (Event object is available as `$event`)
27745
28185
  *
@@ -27755,6 +28195,7 @@ forEach(
27755
28195
  * Specify custom behavior on blur event.
27756
28196
  *
27757
28197
  * @element window, input, select, textarea, a
28198
+ * @priority 0
27758
28199
  * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon
27759
28200
  * blur. (Event object is available as `$event`)
27760
28201
  *
@@ -27770,11 +28211,17 @@ forEach(
27770
28211
  * Specify custom behavior on copy event.
27771
28212
  *
27772
28213
  * @element window, input, select, textarea, a
28214
+ * @priority 0
27773
28215
  * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon
27774
28216
  * copy. (Event object is available as `$event`)
27775
28217
  *
27776
28218
  * @example
27777
- * See {@link ng.directive:ngClick ngClick}
28219
+ <doc:example>
28220
+ <doc:source>
28221
+ <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value">
28222
+ copied: {{copied}}
28223
+ </doc:source>
28224
+ </doc:example>
27778
28225
  */
27779
28226
 
27780
28227
  /**
@@ -27785,11 +28232,17 @@ forEach(
27785
28232
  * Specify custom behavior on cut event.
27786
28233
  *
27787
28234
  * @element window, input, select, textarea, a
28235
+ * @priority 0
27788
28236
  * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon
27789
28237
  * cut. (Event object is available as `$event`)
27790
28238
  *
27791
28239
  * @example
27792
- * See {@link ng.directive:ngClick ngClick}
28240
+ <doc:example>
28241
+ <doc:source>
28242
+ <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value">
28243
+ cut: {{cut}}
28244
+ </doc:source>
28245
+ </doc:example>
27793
28246
  */
27794
28247
 
27795
28248
  /**
@@ -27800,11 +28253,17 @@ forEach(
27800
28253
  * Specify custom behavior on paste event.
27801
28254
  *
27802
28255
  * @element window, input, select, textarea, a
28256
+ * @priority 0
27803
28257
  * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon
27804
28258
  * paste. (Event object is available as `$event`)
27805
28259
  *
27806
28260
  * @example
27807
- * See {@link ng.directive:ngClick ngClick}
28261
+ <doc:example>
28262
+ <doc:source>
28263
+ <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'>
28264
+ pasted: {{paste}}
28265
+ </doc:source>
28266
+ </doc:example>
27808
28267
  */
27809
28268
 
27810
28269
  /**
@@ -27866,9 +28325,6 @@ forEach(
27866
28325
  padding:10px;
27867
28326
  }
27868
28327
 
27869
- /&#42;
27870
- The transition styles can also be placed on the CSS base class above
27871
- &#42;/
27872
28328
  .animate-if.ng-enter, .animate-if.ng-leave {
27873
28329
  -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
27874
28330
  transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
@@ -28038,19 +28494,33 @@ var ngIfDirective = ['$animate', function($animate) {
28038
28494
  top:50px;
28039
28495
  }
28040
28496
  </file>
28041
- <file name="scenario.js">
28497
+ <file name="protractorTest.js">
28498
+ var templateSelect = element(by.model('template'));
28499
+ var includeElem = element(by.css('.doc-example-live [ng-include]'));
28500
+
28042
28501
  it('should load template1.html', function() {
28043
- expect(element('.doc-example-live [ng-include]').text()).
28044
- toMatch(/Content of template1.html/);
28502
+ expect(includeElem.getText()).toMatch(/Content of template1.html/);
28045
28503
  });
28504
+
28046
28505
  it('should load template2.html', function() {
28047
- select('template').option('1');
28048
- expect(element('.doc-example-live [ng-include]').text()).
28049
- toMatch(/Content of template2.html/);
28506
+ if (browser.params.browser == 'firefox') {
28507
+ // Firefox can't handle using selects
28508
+ // See https://github.com/angular/protractor/issues/480
28509
+ return;
28510
+ }
28511
+ templateSelect.click();
28512
+ templateSelect.element.all(by.css('option')).get(2).click();
28513
+ expect(includeElem.getText()).toMatch(/Content of template2.html/);
28050
28514
  });
28515
+
28051
28516
  it('should change to blank', function() {
28052
- select('template').option('');
28053
- expect(element('.doc-example-live [ng-include]')).toBe(undefined);
28517
+ if (browser.params.browser == 'firefox') {
28518
+ // Firefox can't handle using selects
28519
+ return;
28520
+ }
28521
+ templateSelect.click();
28522
+ templateSelect.element.all(by.css('option')).get(0).click();
28523
+ expect(includeElem.isPresent()).toBe(false);
28054
28524
  });
28055
28525
  </file>
28056
28526
  </example>
@@ -28176,11 +28646,18 @@ var ngIncludeFillContentDirective = ['$compile',
28176
28646
  * current scope.
28177
28647
  *
28178
28648
  * <div class="alert alert-error">
28179
- * The only appropriate use of `ngInit` for aliasing special properties of
28649
+ * The only appropriate use of `ngInit` is for aliasing special properties of
28180
28650
  * {@link api/ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you
28181
28651
  * should use {@link guide/controller controllers} rather than `ngInit`
28182
28652
  * to initialize values on a scope.
28183
28653
  * </div>
28654
+ * <div class="alert alert-warning">
28655
+ * **Note**: If you have assignment in `ngInit` along with {@link api/ng.$filter `$filter`}, make
28656
+ * sure you have parenthesis for correct precedence:
28657
+ * <pre class="prettyprint">
28658
+ * <div ng-init="test1 = (data | orderBy:'name')"></div>
28659
+ * </pre>
28660
+ * </div>
28184
28661
  *
28185
28662
  * @priority 450
28186
28663
  *
@@ -28203,15 +28680,15 @@ var ngIncludeFillContentDirective = ['$compile',
28203
28680
  </div>
28204
28681
  </div>
28205
28682
  </doc:source>
28206
- <doc:scenario>
28683
+ <doc:protractor>
28207
28684
  it('should alias index positions', function() {
28208
- expect(element('.example-init').text())
28209
- .toBe('list[ 0 ][ 0 ] = a;' +
28210
- 'list[ 0 ][ 1 ] = b;' +
28211
- 'list[ 1 ][ 0 ] = c;' +
28212
- 'list[ 1 ][ 1 ] = d;');
28685
+ var elements = element.all(by.css('.example-init'));
28686
+ expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
28687
+ expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
28688
+ expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
28689
+ expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
28213
28690
  });
28214
- </doc:scenario>
28691
+ </doc:protractor>
28215
28692
  </doc:example>
28216
28693
  */
28217
28694
  var ngInitDirective = ngDirective({
@@ -28249,13 +28726,12 @@ var ngInitDirective = ngDirective({
28249
28726
  <div>Normal: {{1 + 2}}</div>
28250
28727
  <div ng-non-bindable>Ignored: {{1 + 2}}</div>
28251
28728
  </doc:source>
28252
- <doc:scenario>
28729
+ <doc:protractor>
28253
28730
  it('should check ng-non-bindable', function() {
28254
- expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
28255
- expect(using('.doc-example-live').element('div:last').text()).
28256
- toMatch(/1 \+ 2/);
28731
+ expect(element(by.binding('1 + 2')).getText()).toContain('3');
28732
+ expect(element.all(by.css('.doc-example-live div')).last().getText()).toMatch(/1 \+ 2/);
28257
28733
  });
28258
- </doc:scenario>
28734
+ </doc:protractor>
28259
28735
  </doc:example>
28260
28736
  */
28261
28737
  var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
@@ -28383,49 +28859,53 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
28383
28859
  </ng-pluralize>
28384
28860
  </div>
28385
28861
  </doc:source>
28386
- <doc:scenario>
28862
+ <doc:protractor>
28387
28863
  it('should show correct pluralized string', function() {
28388
- expect(element('.doc-example-live ng-pluralize:first').text()).
28389
- toBe('1 person is viewing.');
28390
- expect(element('.doc-example-live ng-pluralize:last').text()).
28391
- toBe('Igor is viewing.');
28392
-
28393
- using('.doc-example-live').input('personCount').enter('0');
28394
- expect(element('.doc-example-live ng-pluralize:first').text()).
28395
- toBe('Nobody is viewing.');
28396
- expect(element('.doc-example-live ng-pluralize:last').text()).
28397
- toBe('Nobody is viewing.');
28398
-
28399
- using('.doc-example-live').input('personCount').enter('2');
28400
- expect(element('.doc-example-live ng-pluralize:first').text()).
28401
- toBe('2 people are viewing.');
28402
- expect(element('.doc-example-live ng-pluralize:last').text()).
28403
- toBe('Igor and Misko are viewing.');
28404
-
28405
- using('.doc-example-live').input('personCount').enter('3');
28406
- expect(element('.doc-example-live ng-pluralize:first').text()).
28407
- toBe('3 people are viewing.');
28408
- expect(element('.doc-example-live ng-pluralize:last').text()).
28409
- toBe('Igor, Misko and one other person are viewing.');
28410
-
28411
- using('.doc-example-live').input('personCount').enter('4');
28412
- expect(element('.doc-example-live ng-pluralize:first').text()).
28413
- toBe('4 people are viewing.');
28414
- expect(element('.doc-example-live ng-pluralize:last').text()).
28415
- toBe('Igor, Misko and 2 other people are viewing.');
28416
- });
28864
+ var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
28865
+ var withOffset = element.all(by.css('ng-pluralize')).get(1);
28866
+ var countInput = element(by.model('personCount'));
28867
+
28868
+ expect(withoutOffset.getText()).toEqual('1 person is viewing.');
28869
+ expect(withOffset.getText()).toEqual('Igor is viewing.');
28870
+
28871
+ countInput.clear();
28872
+ countInput.sendKeys('0');
28873
+
28874
+ expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
28875
+ expect(withOffset.getText()).toEqual('Nobody is viewing.');
28876
+
28877
+ countInput.clear();
28878
+ countInput.sendKeys('2');
28417
28879
 
28418
- it('should show data-binded names', function() {
28419
- using('.doc-example-live').input('personCount').enter('4');
28420
- expect(element('.doc-example-live ng-pluralize:last').text()).
28421
- toBe('Igor, Misko and 2 other people are viewing.');
28880
+ expect(withoutOffset.getText()).toEqual('2 people are viewing.');
28881
+ expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
28422
28882
 
28423
- using('.doc-example-live').input('person1').enter('Di');
28424
- using('.doc-example-live').input('person2').enter('Vojta');
28425
- expect(element('.doc-example-live ng-pluralize:last').text()).
28426
- toBe('Di, Vojta and 2 other people are viewing.');
28883
+ countInput.clear();
28884
+ countInput.sendKeys('3');
28885
+
28886
+ expect(withoutOffset.getText()).toEqual('3 people are viewing.');
28887
+ expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
28888
+
28889
+ countInput.clear();
28890
+ countInput.sendKeys('4');
28891
+
28892
+ expect(withoutOffset.getText()).toEqual('4 people are viewing.');
28893
+ expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
28427
28894
  });
28428
- </doc:scenario>
28895
+ it('should show data-bound names', function() {
28896
+ var withOffset = element.all(by.css('ng-pluralize')).get(1);
28897
+ var personCount = element(by.model('personCount'));
28898
+ var person1 = element(by.model('person1'));
28899
+ var person2 = element(by.model('person2'));
28900
+ personCount.clear();
28901
+ personCount.sendKeys('4');
28902
+ person1.clear();
28903
+ person1.sendKeys('Di');
28904
+ person2.clear();
28905
+ person2.sendKeys('Vojta');
28906
+ expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
28907
+ });
28908
+ </doc:protractor>
28429
28909
  </doc:example>
28430
28910
  */
28431
28911
  var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
@@ -28492,6 +28972,8 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
28492
28972
  * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
28493
28973
  * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
28494
28974
  *
28975
+ * Creating aliases for these properties is possible with {@link api/ng.directive:ngInit `ngInit`}.
28976
+ * This may be useful when, for instance, nesting ngRepeats.
28495
28977
  *
28496
28978
  * # Special repeat start and end points
28497
28979
  * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
@@ -28642,25 +29124,27 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
28642
29124
  max-height:40px;
28643
29125
  }
28644
29126
  </file>
28645
- <file name="scenario.js">
28646
- it('should render initial data set', function() {
28647
- var r = using('.doc-example-live').repeater('ul li');
28648
- expect(r.count()).toBe(10);
28649
- expect(r.row(0)).toEqual(["1","John","25"]);
28650
- expect(r.row(1)).toEqual(["2","Jessie","30"]);
28651
- expect(r.row(9)).toEqual(["10","Samantha","60"]);
28652
- expect(binding('friends.length')).toBe("10");
28653
- });
29127
+ <file name="protractorTest.js">
29128
+ var friends = element(by.css('.doc-example-live'))
29129
+ .element.all(by.repeater('friend in friends'));
29130
+
29131
+ it('should render initial data set', function() {
29132
+ expect(friends.count()).toBe(10);
29133
+ expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
29134
+ expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
29135
+ expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
29136
+ expect(element(by.binding('friends.length')).getText())
29137
+ .toMatch("I have 10 friends. They are:");
29138
+ });
28654
29139
 
28655
29140
  it('should update repeater when filter predicate changes', function() {
28656
- var r = using('.doc-example-live').repeater('ul li');
28657
- expect(r.count()).toBe(10);
29141
+ expect(friends.count()).toBe(10);
28658
29142
 
28659
- input('q').enter('ma');
29143
+ element(by.css('.doc-example-live')).element(by.model('q')).sendKeys('ma');
28660
29144
 
28661
- expect(r.count()).toBe(2);
28662
- expect(r.row(0)).toEqual(["1","Mary","28"]);
28663
- expect(r.row(1)).toEqual(["2","Samantha","60"]);
29145
+ expect(friends.count()).toBe(2);
29146
+ expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
29147
+ expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
28664
29148
  });
28665
29149
  </file>
28666
29150
  </example>
@@ -28675,7 +29159,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
28675
29159
  $$tlb: true,
28676
29160
  link: function($scope, $element, $attr, ctrl, $transclude){
28677
29161
  var expression = $attr.ngRepeat;
28678
- var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
29162
+ var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/),
28679
29163
  trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn,
28680
29164
  lhs, rhs, valueIdentifier, keyIdentifier,
28681
29165
  hashFnLocals = {$id: hashKey};
@@ -28687,7 +29171,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
28687
29171
 
28688
29172
  lhs = match[1];
28689
29173
  rhs = match[2];
28690
- trackByExp = match[4];
29174
+ trackByExp = match[3];
28691
29175
 
28692
29176
  if (trackByExp) {
28693
29177
  trackByExpGetter = $parse(trackByExp);
@@ -28914,6 +29398,11 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
28914
29398
  *
28915
29399
  * Just remember to include the important flag so the CSS override will function.
28916
29400
  *
29401
+ * <div class="alert alert-warning">
29402
+ * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br />
29403
+ * "f" / "0" / "false" / "no" / "n" / "[]"
29404
+ * </div>
29405
+ *
28917
29406
  * ## A note about animations with ngShow
28918
29407
  *
28919
29408
  * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
@@ -28989,16 +29478,19 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
28989
29478
  background:white;
28990
29479
  }
28991
29480
  </file>
28992
- <file name="scenario.js">
28993
- it('should check ng-show / ng-hide', function() {
28994
- expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
28995
- expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
29481
+ <file name="protractorTest.js">
29482
+ var thumbsUp = element(by.css('.doc-example-live span.icon-thumbs-up'));
29483
+ var thumbsDown = element(by.css('.doc-example-live span.icon-thumbs-down'));
28996
29484
 
28997
- input('checked').check();
29485
+ it('should check ng-show / ng-hide', function() {
29486
+ expect(thumbsUp.isDisplayed()).toBeFalsy();
29487
+ expect(thumbsDown.isDisplayed()).toBeTruthy();
28998
29488
 
28999
- expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
29000
- expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
29001
- });
29489
+ element(by.model('checked')).click();
29490
+
29491
+ expect(thumbsUp.isDisplayed()).toBeTruthy();
29492
+ expect(thumbsDown.isDisplayed()).toBeFalsy();
29493
+ });
29002
29494
  </file>
29003
29495
  </example>
29004
29496
  */
@@ -29062,6 +29554,11 @@ var ngShowDirective = ['$animate', function($animate) {
29062
29554
  * </pre>
29063
29555
  *
29064
29556
  * Just remember to include the important flag so the CSS override will function.
29557
+ *
29558
+ * <div class="alert alert-warning">
29559
+ * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br />
29560
+ * "f" / "0" / "false" / "no" / "n" / "[]"
29561
+ * </div>
29065
29562
  *
29066
29563
  * ## A note about animations with ngHide
29067
29564
  *
@@ -29138,16 +29635,19 @@ var ngShowDirective = ['$animate', function($animate) {
29138
29635
  background:white;
29139
29636
  }
29140
29637
  </file>
29141
- <file name="scenario.js">
29142
- it('should check ng-show / ng-hide', function() {
29143
- expect(element('.doc-example-live .check-element:first:hidden').count()).toEqual(1);
29144
- expect(element('.doc-example-live .check-element:last:visible').count()).toEqual(1);
29638
+ <file name="protractorTest.js">
29639
+ var thumbsUp = element(by.css('.doc-example-live span.icon-thumbs-up'));
29640
+ var thumbsDown = element(by.css('.doc-example-live span.icon-thumbs-down'));
29145
29641
 
29146
- input('checked').check();
29642
+ it('should check ng-show / ng-hide', function() {
29643
+ expect(thumbsUp.isDisplayed()).toBeFalsy();
29644
+ expect(thumbsDown.isDisplayed()).toBeTruthy();
29147
29645
 
29148
- expect(element('.doc-example-live .check-element:first:visible').count()).toEqual(1);
29149
- expect(element('.doc-example-live .check-element:last:hidden').count()).toEqual(1);
29150
- });
29646
+ element(by.model('checked')).click();
29647
+
29648
+ expect(thumbsUp.isDisplayed()).toBeTruthy();
29649
+ expect(thumbsDown.isDisplayed()).toBeFalsy();
29650
+ });
29151
29651
  </file>
29152
29652
  </example>
29153
29653
  */
@@ -29186,13 +29686,15 @@ var ngHideDirective = ['$animate', function($animate) {
29186
29686
  color: black;
29187
29687
  }
29188
29688
  </file>
29189
- <file name="scenario.js">
29689
+ <file name="protractorTest.js">
29690
+ var colorSpan = element(by.css('.doc-example-live span'));
29691
+
29190
29692
  it('should check ng-style', function() {
29191
- expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
29192
- element('.doc-example-live :button[value=set]').click();
29193
- expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
29194
- element('.doc-example-live :button[value=clear]').click();
29195
- expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
29693
+ expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
29694
+ element(by.css('.doc-example-live input[value=set]')).click();
29695
+ expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
29696
+ element(by.css('.doc-example-live input[value=clear]')).click();
29697
+ expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
29196
29698
  });
29197
29699
  </file>
29198
29700
  </example>
@@ -29217,7 +29719,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
29217
29719
  * as specified in the template.
29218
29720
  *
29219
29721
  * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
29220
- * from the template cache), `ngSwitch` simply choses one of the nested elements and makes it visible based on which element
29722
+ * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
29221
29723
  * matches the value obtained from the evaluated expression. In other words, you define a container element
29222
29724
  * (where you place the directive), place an expression on the **`on="..."` attribute**
29223
29725
  * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
@@ -29313,17 +29815,20 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
29313
29815
  top:0;
29314
29816
  }
29315
29817
  </file>
29316
- <file name="scenario.js">
29818
+ <file name="protractorTest.js">
29819
+ var switchElem = element(by.css('.doc-example-live [ng-switch]'));
29820
+ var select = element(by.model('selection'));
29821
+
29317
29822
  it('should start in settings', function() {
29318
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
29823
+ expect(switchElem.getText()).toMatch(/Settings Div/);
29319
29824
  });
29320
29825
  it('should change to home', function() {
29321
- select('selection').option('home');
29322
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
29826
+ select.element.all(by.css('option')).get(1).click();
29827
+ expect(switchElem.getText()).toMatch(/Home Span/);
29323
29828
  });
29324
29829
  it('should select default', function() {
29325
- select('selection').option('other');
29326
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
29830
+ select.element.all(by.css('option')).get(2).click();
29831
+ expect(switchElem.getText()).toMatch(/default/);
29327
29832
  });
29328
29833
  </file>
29329
29834
  </example>
@@ -29374,11 +29879,9 @@ var ngSwitchWhenDirective = ngDirective({
29374
29879
  transclude: 'element',
29375
29880
  priority: 800,
29376
29881
  require: '^ngSwitch',
29377
- compile: function(element, attrs) {
29378
- return function(scope, element, attr, ctrl, $transclude) {
29379
- ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
29380
- ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
29381
- };
29882
+ link: function(scope, element, attrs, ctrl, $transclude) {
29883
+ ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
29884
+ ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
29382
29885
  }
29383
29886
  });
29384
29887
 
@@ -29432,35 +29935,32 @@ var ngSwitchDefaultDirective = ngDirective({
29432
29935
  <pane title="{{title}}">{{text}}</pane>
29433
29936
  </div>
29434
29937
  </doc:source>
29435
- <doc:scenario>
29938
+ <doc:protractor>
29436
29939
  it('should have transcluded', function() {
29437
- input('title').enter('TITLE');
29438
- input('text').enter('TEXT');
29439
- expect(binding('title')).toEqual('TITLE');
29440
- expect(binding('text')).toEqual('TEXT');
29940
+ var titleElement = element(by.model('title'));
29941
+ titleElement.clear();
29942
+ titleElement.sendKeys('TITLE');
29943
+ var textElement = element(by.model('text'));
29944
+ textElement.clear();
29945
+ textElement.sendKeys('TEXT');
29946
+ expect(element(by.binding('title')).getText()).toEqual('TITLE');
29947
+ expect(element(by.binding('text')).getText()).toEqual('TEXT');
29441
29948
  });
29442
- </doc:scenario>
29949
+ </doc:protractor>
29443
29950
  </doc:example>
29444
29951
  *
29445
29952
  */
29446
29953
  var ngTranscludeDirective = ngDirective({
29447
- controller: ['$element', '$transclude', function($element, $transclude) {
29954
+ link: function($scope, $element, $attrs, controller, $transclude) {
29448
29955
  if (!$transclude) {
29449
29956
  throw minErr('ngTransclude')('orphan',
29450
- 'Illegal use of ngTransclude directive in the template! ' +
29451
- 'No parent directive that requires a transclusion found. ' +
29452
- 'Element: {0}',
29453
- startingTag($element));
29957
+ 'Illegal use of ngTransclude directive in the template! ' +
29958
+ 'No parent directive that requires a transclusion found. ' +
29959
+ 'Element: {0}',
29960
+ startingTag($element));
29454
29961
  }
29455
-
29456
- // remember the transclusion fn but call it during linking so that we don't process transclusion before directives on
29457
- // the parent element even when the transclusion replaces the current element. (we can't use priority here because
29458
- // that applies only to compile fns and not controllers
29459
- this.$transclude = $transclude;
29460
- }],
29461
-
29462
- link: function($scope, $element, $attrs, controller) {
29463
- controller.$transclude(function(clone) {
29962
+
29963
+ $transclude(function(clone) {
29464
29964
  $element.empty();
29465
29965
  $element.append(clone);
29466
29966
  });
@@ -29473,10 +29973,14 @@ var ngTranscludeDirective = ngDirective({
29473
29973
  * @restrict E
29474
29974
  *
29475
29975
  * @description
29476
- * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
29477
- * template can be used by `ngInclude`, `ngView` or directive templates.
29976
+ * Load the content of a `<script>` element into {@link api/ng.$templateCache `$templateCache`}, so that the
29977
+ * template can be used by {@link api/ng.directive:ngInclude `ngInclude`},
29978
+ * {@link api/ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the
29979
+ * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be
29980
+ * assigned through the element's `id`, which can then be used as a directive's `templateUrl`.
29478
29981
  *
29479
- * @param {'text/ng-template'} type must be set to `'text/ng-template'`
29982
+ * @param {'text/ng-template'} type Must be set to `'text/ng-template'`.
29983
+ * @param {string} id Cache name of the template.
29480
29984
  *
29481
29985
  * @example
29482
29986
  <doc:example>
@@ -29488,12 +29992,12 @@ var ngTranscludeDirective = ngDirective({
29488
29992
  <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
29489
29993
  <div id="tpl-content" ng-include src="currentTpl"></div>
29490
29994
  </doc:source>
29491
- <doc:scenario>
29995
+ <doc:protractor>
29492
29996
  it('should load template defined inside script tag', function() {
29493
- element('#tpl-link').click();
29494
- expect(element('#tpl-content').text()).toMatch(/Content of the template/);
29997
+ element(by.css('#tpl-link')).click();
29998
+ expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
29495
29999
  });
29496
- </doc:scenario>
30000
+ </doc:protractor>
29497
30001
  </doc:example>
29498
30002
  */
29499
30003
  var scriptDirective = ['$templateCache', function($templateCache) {
@@ -29531,14 +30035,21 @@ var ngOptionsMinErr = minErr('ngOptions');
29531
30035
  * represented by the selected option will be bound to the model identified by the `ngModel`
29532
30036
  * directive.
29533
30037
  *
30038
+ * <div class="alert alert-warning">
30039
+ * **Note:** `ngModel` compares by reference, not value. This is important when binding to an
30040
+ * array of objects. See an example {@link http://jsfiddle.net/qWzTb/ in this jsfiddle}.
30041
+ * </div>
30042
+ *
29534
30043
  * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
29535
30044
  * be nested into the `<select>` element. This element will then represent the `null` or "not selected"
29536
30045
  * option. See example below for demonstration.
29537
30046
  *
29538
- * Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead
30047
+ * <div class="alert alert-warning">
30048
+ * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead
29539
30049
  * of {@link ng.directive:ngRepeat ngRepeat} when you want the
29540
30050
  * `select` model to be bound to a non-string value. This is because an option element can only
29541
30051
  * be bound to string values at present.
30052
+ * </div>
29542
30053
  *
29543
30054
  * @param {string} ngModel Assignable angular expression to data-bind to.
29544
30055
  * @param {string=} name Property name of the form under which the control is published.
@@ -29625,23 +30136,25 @@ var ngOptionsMinErr = minErr('ngOptions');
29625
30136
  </div>
29626
30137
  </div>
29627
30138
  </doc:source>
29628
- <doc:scenario>
30139
+ <doc:protractor>
29629
30140
  it('should check ng-options', function() {
29630
- expect(binding('{selected_color:color}')).toMatch('red');
29631
- select('color').option('0');
29632
- expect(binding('{selected_color:color}')).toMatch('black');
29633
- using('.nullable').select('color').option('');
29634
- expect(binding('{selected_color:color}')).toMatch('null');
30141
+ expect(element(by.binding('{selected_color:color}')).getText()).toMatch('red');
30142
+ element.all(by.select('color')).first().click();
30143
+ element.all(by.css('select[ng-model="color"] option')).first().click();
30144
+ expect(element(by.binding('{selected_color:color}')).getText()).toMatch('black');
30145
+ element(by.css('.nullable select[ng-model="color"]')).click();
30146
+ element.all(by.css('.nullable select[ng-model="color"] option')).first().click();
30147
+ expect(element(by.binding('{selected_color:color}')).getText()).toMatch('null');
29635
30148
  });
29636
- </doc:scenario>
30149
+ </doc:protractor>
29637
30150
  </doc:example>
29638
30151
  */
29639
30152
 
29640
30153
  var ngOptionsDirective = valueFn({ terminal: true });
29641
30154
  // jshint maxlen: false
29642
30155
  var selectDirective = ['$compile', '$parse', function($compile, $parse) {
29643
- //0000111110000000000022220000000000000000000000333300000000000000444444444444444000000000555555555555555000000066666666666666600000000000000007777000000000000000000088888
29644
- var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/,
30156
+ //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
30157
+ var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
29645
30158
  nullModelCtrl = {$setViewValue: noop};
29646
30159
  // jshint maxlen: 100
29647
30160
 
@@ -29733,18 +30246,10 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
29733
30246
  selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
29734
30247
 
29735
30248
  // required validator
29736
- if (multiple && (attr.required || attr.ngRequired)) {
29737
- var requiredValidator = function(value) {
29738
- ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
29739
- return value;
30249
+ if (multiple) {
30250
+ ngModelCtrl.$isEmpty = function(value) {
30251
+ return !value || value.length === 0;
29740
30252
  };
29741
-
29742
- ngModelCtrl.$parsers.push(requiredValidator);
29743
- ngModelCtrl.$formatters.unshift(requiredValidator);
29744
-
29745
- attr.$observe('required', function() {
29746
- requiredValidator(ngModelCtrl.$viewValue);
29747
- });
29748
30253
  }
29749
30254
 
29750
30255
  if (optionsExp) setupAsOptions(scope, element, ngModelCtrl);
@@ -29950,7 +30455,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
29950
30455
 
29951
30456
  // We now build up the list of options we need (we merge later)
29952
30457
  for (index = 0; length = keys.length, index < length; index++) {
29953
-
30458
+
29954
30459
  key = index;
29955
30460
  if (keyName) {
29956
30461
  key = keys[index];
@@ -30384,7 +30889,8 @@ function callerFile(offset) {
30384
30889
  * To work around this we instead use our own handler that fires a real event.
30385
30890
  */
30386
30891
  (function(fn){
30387
- var parentTrigger = fn.trigger;
30892
+ // We need a handle to the original trigger function for input tests.
30893
+ var parentTrigger = fn._originalTrigger = fn.trigger;
30388
30894
  fn.trigger = function(type) {
30389
30895
  if (/(click|change|keydown|blur|input|mousedown|mouseup)/.test(type)) {
30390
30896
  var processDefaults = [];
@@ -32370,5 +32876,5 @@ if (config.autotest) {
32370
32876
  })(window, document);
32371
32877
 
32372
32878
 
32373
- !angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";\n\n[ng\\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],\n.ng-cloak, .x-ng-cloak,\n.ng-hide {\n display: none !important;\n}\n\nng\\:form {\n display: block;\n}\n\n/* The styles below ensure that the CSS transition will ALWAYS\n * animate and close. A nasty bug occurs with CSS transitions where\n * when the active class isn\'t set, or if the active class doesn\'t\n * contain any styles to transition to, then, if ngAnimate is used,\n * it will appear as if the webpage is broken due to the forever hanging\n * animations. The border-spacing (!ie) and zoom (ie) CSS properties are\n * used below since they trigger a transition without making the browser\n * animate anything and they\'re both highly underused CSS properties */\n.ng-animate-start { border-spacing:1px 1px; -ms-zoom:1.0001; }\n.ng-animate-active { border-spacing:0px 0px; -ms-zoom:1; }\n</style>');
32879
+ !angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";\n\n[ng\\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],\n.ng-cloak, .x-ng-cloak,\n.ng-hide {\n display: none !important;\n}\n\nng\\:form {\n display: block;\n}\n\n.ng-animate-block-transitions {\n transition:0s all!important;\n -webkit-transition:0s all!important;\n}\n</style>');
32374
32880
  !angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";\n/* CSS Document */\n\n/** Structure */\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n font-size: 14px;\n}\n\n#system-error {\n font-size: 1.5em;\n text-align: center;\n}\n\n#json, #xml {\n display: none;\n}\n\n#header {\n position: fixed;\n width: 100%;\n}\n\n#specs {\n padding-top: 50px;\n}\n\n#header .angular {\n font-family: Courier New, monospace;\n font-weight: bold;\n}\n\n#header h1 {\n font-weight: normal;\n float: left;\n font-size: 30px;\n line-height: 30px;\n margin: 0;\n padding: 10px 10px;\n height: 30px;\n}\n\n#application h2,\n#specs h2 {\n margin: 0;\n padding: 0.5em;\n font-size: 1.1em;\n}\n\n#status-legend {\n margin-top: 10px;\n margin-right: 10px;\n}\n\n#header,\n#application,\n.test-info,\n.test-actions li {\n overflow: hidden;\n}\n\n#application {\n margin: 10px;\n}\n\n#application iframe {\n width: 100%;\n height: 758px;\n}\n\n#application .popout {\n float: right;\n}\n\n#application iframe {\n border: none;\n}\n\n.tests li,\n.test-actions li,\n.test-it li,\n.test-it ol,\n.status-display {\n list-style-type: none;\n}\n\n.tests,\n.test-it ol,\n.status-display {\n margin: 0;\n padding: 0;\n}\n\n.test-info {\n margin-left: 1em;\n margin-top: 0.5em;\n border-radius: 8px 0 0 8px;\n -webkit-border-radius: 8px 0 0 8px;\n -moz-border-radius: 8px 0 0 8px;\n cursor: pointer;\n}\n\n.test-info:hover .test-name {\n text-decoration: underline;\n}\n\n.test-info .closed:before {\n content: \'\\25b8\\00A0\';\n}\n\n.test-info .open:before {\n content: \'\\25be\\00A0\';\n font-weight: bold;\n}\n\n.test-it ol {\n margin-left: 2.5em;\n}\n\n.status-display,\n.status-display li {\n float: right;\n}\n\n.status-display li {\n padding: 5px 10px;\n}\n\n.timer-result,\n.test-title {\n display: inline-block;\n margin: 0;\n padding: 4px;\n}\n\n.test-actions .test-title,\n.test-actions .test-result {\n display: table-cell;\n padding-left: 0.5em;\n padding-right: 0.5em;\n}\n\n.test-actions {\n display: table;\n}\n\n.test-actions li {\n display: table-row;\n}\n\n.timer-result {\n width: 4em;\n padding: 0 10px;\n text-align: right;\n font-family: monospace;\n}\n\n.test-it pre,\n.test-actions pre {\n clear: left;\n color: black;\n margin-left: 6em;\n}\n\n.test-describe {\n padding-bottom: 0.5em;\n}\n\n.test-describe .test-describe {\n margin: 5px 5px 10px 2em;\n}\n\n.test-actions .status-pending .test-title:before {\n content: \'\\00bb\\00A0\';\n}\n\n.scrollpane {\n max-height: 20em;\n overflow: auto;\n}\n\n/** Colors */\n\n#header {\n background-color: #F2C200;\n}\n\n#specs h2 {\n border-top: 2px solid #BABAD1;\n}\n\n#specs h2,\n#application h2 {\n background-color: #efefef;\n}\n\n#application {\n border: 1px solid #BABAD1;\n}\n\n.test-describe .test-describe {\n border-left: 1px solid #BABAD1;\n border-right: 1px solid #BABAD1;\n border-bottom: 1px solid #BABAD1;\n}\n\n.status-display {\n border: 1px solid #777;\n}\n\n.status-display .status-pending,\n.status-pending .test-info {\n background-color: #F9EEBC;\n}\n\n.status-display .status-success,\n.status-success .test-info {\n background-color: #B1D7A1;\n}\n\n.status-display .status-failure,\n.status-failure .test-info {\n background-color: #FF8286;\n}\n\n.status-display .status-error,\n.status-error .test-info {\n background-color: black;\n color: white;\n}\n\n.test-actions .status-success .test-title {\n color: #30B30A;\n}\n\n.test-actions .status-failure .test-title {\n color: #DF0000;\n}\n\n.test-actions .status-error .test-title {\n color: black;\n}\n\n.test-actions .timer-result {\n color: #888;\n}\n</style>');