angular-rails-engine 1.2.5.0 → 1.2.13.0

Sign up to get free protection for your applications and to get access to all the features.
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>');