From 06516d7c27a32614b1d45070c409e2d0112c9a11 Mon Sep 17 00:00:00 2001 From: michaelb958 Date: Sun, 2 Apr 2017 16:58:31 +1000 Subject: [PATCH 001/631] docs(guide/i18n): fix links to CLDR The old link target is dead, deceased, pushing up daisies. I quote: > The cldr-tmp repository is no longer available. > For access to CLDR sources and data, please see the [CLDR pages](link to new one). Closes #15879 --- docs/content/guide/i18n.ngdoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/guide/i18n.ngdoc b/docs/content/guide/i18n.ngdoc index ebe28c2b57fc..c090f70d1a95 100644 --- a/docs/content/guide/i18n.ngdoc +++ b/docs/content/guide/i18n.ngdoc @@ -281,18 +281,18 @@ categories as you need. #### Selection Keywords The selection keywords can be either exact matches or language dependent [plural -categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). +categories](http://cldr.unicode.org/index/cldr-spec/plural-rules). Exact matches are written as the equal sign followed by the exact value. `=0`, `=1`, `=2` and `=123` are all examples of exact matches. Note that there should be no space between the equal sign and the numeric value. Plural category matches are single words corresponding to the [plural -categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) of -the CLDR plural category spec. These categories vary by locale. The "en" (English) locale, for -example, defines just "one" and "other" while the "ga" (Irish) locale defines "one", "two", "few", -"many" and "other". Typically, you would just write the categories for your language. During -translation, the translators will add or remove more categories depending on the target locale. +categories](http://cldr.unicode.org/index/cldr-spec/plural-rules) of the CLDR plural category spec. +These categories vary by locale. The "en" (English) locale, for example, defines just "one" and +"other" while the "ga" (Irish) locale defines "one", "two", "few", "many" and "other". Typically, +you would just write the categories for your language. During translation, the translators will add +or remove more categories depending on the target locale. Exact matches always win over keyword matches. Therefore, if you define both `=0` and `zero`, when the value of the expression is zero, the `=0` message is the one that will be selected. (The From 136a42abc162a64ac57ff45417553b58a031990b Mon Sep 17 00:00:00 2001 From: Atul Shimpi Date: Mon, 3 Apr 2017 13:49:35 +0530 Subject: [PATCH 002/631] docs(README): improve vocabulary and orthography Closes #15876 Closes #15875 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c6a8dd5c369c..ea372ec0a658 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ synchronizes data from your UI (view) with your JavaScript objects (model) throu binding. To help you structure your application better and make it easy to test, AngularJS teaches the browser how to do dependency injection and inversion of control. -It also helps with server-side communication, taming async callbacks with promises and deferreds, -and it makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a +It also helps with server-side communication, taming async callbacks with promises and deferred objects, +and it makes client-side navigation and deep linking with hashbang urls or HTML5 pushState a piece of cake. Best of all? It makes development fun! * Web site: https://angularjs.org From 0fbb1187b8d9f04576c146221aaba95e983bdac7 Mon Sep 17 00:00:00 2001 From: Atef Ben Ali Date: Wed, 5 Apr 2017 14:13:28 +0100 Subject: [PATCH 003/631] docs(guide/directive): delete redundant 'the' Closes #15891 --- docs/content/guide/directive.ngdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/guide/directive.ngdoc b/docs/content/guide/directive.ngdoc index f47cf368120d..a40e3ee85803 100644 --- a/docs/content/guide/directive.ngdoc +++ b/docs/content/guide/directive.ngdoc @@ -123,7 +123,7 @@ The other forms shown above are accepted for legacy reasons but we advise you to `$compile` can match directives based on element names (E), attributes (A), class names (C), and comments (M). -The built-in the AngularJS directives show in their documentation page which type of matching they support. +The built-in AngularJS directives show in their documentation page which type of matching they support. The following demonstrates the various ways a directive (`myDir` in this case) that matches all 4 types can be referenced from within a template. From 6ee7c29ca74e416bd53ca6174e2003a001e1356c Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Fri, 7 Apr 2017 15:01:49 +0200 Subject: [PATCH 004/631] docs(filter/filter): remove duplicate 'the' Closes #15893 --- src/ng/filter/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ng/filter/filter.js b/src/ng/filter/filter.js index a11a0ce4d25d..d0848209feed 100644 --- a/src/ng/filter/filter.js +++ b/src/ng/filter/filter.js @@ -46,7 +46,7 @@ * * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in * determining if values retrieved using `expression` (when it is not a function) should be - * considered a match based on the the expected value (from the filter expression) and actual + * considered a match based on the expected value (from the filter expression) and actual * value (from the object in the array). * * Can be one of: From e812b9fa9ec7086ab8d64a32d86f6e991f84bc55 Mon Sep 17 00:00:00 2001 From: Eli Sadoff Date: Fri, 7 Apr 2017 15:23:19 +0000 Subject: [PATCH 005/631] docs(filter/uppercase): add an example I saw that the uppercase filter had no example so I decided to add a minimal example to explain how the uppercase filter works. Thank you very much to @narretz for helping me through this process. Closes #15885 --- src/ng/filter/filters.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/ng/filter/filters.js b/src/ng/filter/filters.js index 3f83895f74c2..5a79a7799929 100644 --- a/src/ng/filter/filters.js +++ b/src/ng/filter/filters.js @@ -698,6 +698,9 @@ function jsonFilter() { * @kind function * @description * Converts string to lowercase. + * + * See the {@link ng.uppercase uppercase filter documentation} for a functionally identical example. + * * @see angular.lowercase */ var lowercaseFilter = valueFn(lowercase); @@ -709,6 +712,22 @@ var lowercaseFilter = valueFn(lowercase); * @kind function * @description * Converts string to uppercase. - * @see angular.uppercase + * @example + + + +
+ +

{{title}}

+ +

{{title | uppercase}}

+
+
+
*/ var uppercaseFilter = valueFn(uppercase); From d0622d06499ae514dd618f593e103d9e8857b217 Mon Sep 17 00:00:00 2001 From: Vigneshkumar Chinnachamy Date: Sat, 8 Apr 2017 17:40:21 +0530 Subject: [PATCH 006/631] docs(guide/Developer Guide): Update twitter handle Replaced the old Angular twitter handle with the new one. Closes #15903 --- docs/content/guide/index.ngdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/guide/index.ngdoc b/docs/content/guide/index.ngdoc index b3cc1e5385d3..fa5be9875d4e 100644 --- a/docs/content/guide/index.ngdoc +++ b/docs/content/guide/index.ngdoc @@ -75,7 +75,7 @@ Official announcements, news and releases are posted to our blog, G+ and Twitter * [AngularJS Blog](http://blog.angularjs.org/) * [Google+](https://plus.google.com/u/0/+AngularJS) -* [Twitter](https://twitter.com/angularjs) +* [Twitter](https://twitter.com/angular) * [AngularJS on YouTube](http://youtube.com/angularjs) ## Contributing to AngularJS From e23782b8c23fc766efb29a87a25bc054af3159fd Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Wed, 12 Apr 2017 11:21:10 +0200 Subject: [PATCH 007/631] docs($http): correct and clarify default transforms - baddata error described incorrect http behavior, and workarounds - httpProvider defaults were missing transformResponse / transformRequest - http was not clear about JSON detection strategy Closes #15897 Closes #15906 --- docs/content/error/$http/baddata.ngdoc | 12 +++---- src/ng/http.js | 44 +++++++++++++++++++------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/docs/content/error/$http/baddata.ngdoc b/docs/content/error/$http/baddata.ngdoc index 9349b76639c5..512a73046145 100644 --- a/docs/content/error/$http/baddata.ngdoc +++ b/docs/content/error/$http/baddata.ngdoc @@ -3,12 +3,12 @@ @fullName Bad JSON Data @description -The default @{link ng.$http#default-transformations `transformResponse`} will try to parse the -response as JSON if the `Content-Type` header is `application/json` or the response looks like a +The default {@link ng.$http#default-transformations `transformResponse`} will try to parse the +response as JSON if the `Content-Type` header is `application/json`, or the response looks like a valid JSON-stringified object or array. This error occurs when that data is not a valid JSON object. -The error message should provide additional context such as the actual response. - -To resolve this error, make sure you pass valid JSON data to `transformResponse` or use an -appropriate `Content-Type` header for non-JSON data. +To resolve this error, make sure you pass valid JSON data to `transformResponse`. If the response +data looks like JSON, but has a different `Content-Type` header, you must +{@link ng.$http#overriding-the-default-transformations-per-request implement your own response +transformer on a per request basis}, or {@link ng.$http#default-transformations modify the default `$http` responseTransform}. diff --git a/src/ng/http.js b/src/ng/http.js index fe67455e3ca9..6c9785e5ac06 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -266,12 +266,6 @@ function $HttpProvider() { * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses * by default. See {@link $http#caching $http Caching} for more information. * - * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. - * Defaults value is `'XSRF-TOKEN'`. - * - * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the - * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. - * * - **`defaults.headers`** - {Object} - Default headers for all $http requests. * Refer to {@link ng.$http#setting-http-headers $http} for documentation on * setting default headers. @@ -280,15 +274,38 @@ function $HttpProvider() { * - **`defaults.headers.put`** * - **`defaults.headers.patch`** * + * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the + * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the + * {@link $jsonpCallbacks} service. Defaults to `'callback'`. * * - **`defaults.paramSerializer`** - `{string|function(Object):string}` - A function * used to the prepare string representation of request parameters (specified as an object). * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. * - * - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the - * callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the - * {@link $jsonpCallbacks} service. Defaults to `'callback'`. + * - **`defaults.transformRequest`** - + * `{Array|function(data, headersGetter)}` - + * An array of functions (or a single function) which are applied to the request data. + * By default, this is an array with one request transformation function: + * + * - If the `data` property of the request configuration object contains an object, serialize it + * into JSON format. + * + * - **`defaults.transformResponse`** - + * `{Array|function(data, headersGetter, status)}` - + * An array of functions (or a single function) which are applied to the response data. By default, + * this is an array which applies one response transformation function that does two things: + * + * - If XSRF prefix is detected, strip it + * (see {@link ng.$http#security-considerations Security Considerations in the $http docs}). + * - If the `Content-Type` is `application/json` or the response looks like JSON, + * deserialize it using a JSON parser. + * + * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. + * Defaults value is `'XSRF-TOKEN'`. + * + * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the + * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. * **/ var defaults = this.defaults = { @@ -552,15 +569,18 @@ function $HttpProvider() { * * AngularJS provides the following default transformations: * - * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): + * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`) is + * an array with one function that does the following: * * - If the `data` property of the request configuration object contains an object, serialize it * into JSON format. * - * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): + * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`) is + * an array with one function that does the following: * * - If XSRF prefix is detected, strip it (see Security Considerations section below). - * - If JSON response is detected, deserialize it using a JSON parser. + * - If the `Content-Type` is `application/json` or the response looks like JSON, + * deserialize it using a JSON parser. * * * ### Overriding the Default Transformations Per Request From ad0bb83819458563eac9729b448894164cc2da53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82=C4=99biowski?= Date: Wed, 12 Apr 2017 13:20:43 +0200 Subject: [PATCH 008/631] chore(browserstack): Update browserstacktunnel-wrapper, fix options Only the latest version of the package works correctly (the backend for it at BrowserStack is not versioned) and the options have changed in the new version of the package. Also, iOS 8.0 is no longer available on BrowserStack, only 8.3 is. Instead, this commit changes it to 9.3 as we shouldn't be testing on 8 anymore anyway. Closes #15892 --- karma-shared.conf.js | 4 +-- lib/browserstack/start_tunnel.js | 2 +- package.json | 2 +- yarn.lock | 55 ++++++++++++-------------------- 4 files changed, 24 insertions(+), 39 deletions(-) diff --git a/karma-shared.conf.js b/karma-shared.conf.js index 44acad709f48..46b98b2c4724 100644 --- a/karma-shared.conf.js +++ b/karma-shared.conf.js @@ -122,9 +122,9 @@ module.exports = function(config, specificOptions) { }, 'BS_iOS': { base: 'BrowserStack', - device: 'iPhone 6', + device: 'iPhone 6S', os: 'ios', - os_version: '8.0' + os_version: '9.3' } } }); diff --git a/lib/browserstack/start_tunnel.js b/lib/browserstack/start_tunnel.js index 59519a320438..a97235af8487 100644 --- a/lib/browserstack/start_tunnel.js +++ b/lib/browserstack/start_tunnel.js @@ -25,7 +25,7 @@ PORTS.forEach(function(port) { var tunnel = new BrowserStackTunnel({ key: ACCESS_KEY, - tunnelIdentifier: TUNNEL_IDENTIFIER, + localIdentifier: TUNNEL_IDENTIFIER, hosts: hosts }); diff --git a/package.json b/package.json index 89c48fca8f3e..64a2bcb12efb 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "benchmark": "1.x.x", "bootstrap": "3.1.1", "bower": "~1.3.9", - "browserstacktunnel-wrapper": "^1.4.2", + "browserstacktunnel-wrapper": "2.0.0", "canonical-path": "0.0.2", "changez": "^2.1.1", "changez-angular": "^2.1.2", diff --git a/yarn.lock b/yarn.lock index 9ef42ec2a9fb..60ae25c1d951 100644 --- a/yarn.lock +++ b/yarn.lock @@ -754,7 +754,13 @@ browserstack@1.5.0: dependencies: https-proxy-agent "1.0.0" -browserstacktunnel-wrapper@^1.4.2, browserstacktunnel-wrapper@~1.4.2: +browserstacktunnel-wrapper@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.0.tgz#4d6ebf6a667451ad4ee9325fddcf3546607b4d92" + dependencies: + unzip "~0.1.9" + +browserstacktunnel-wrapper@~1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-1.4.2.tgz#6598fb7d784b6ff348e3df7c104b0d9c27ea5275" dependencies: @@ -1191,15 +1197,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.4.6, concat-stream@^1.4.7: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" - dependencies: - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -concat-stream@~1.4.1, concat-stream@~1.4.5: +concat-stream@^1.4.6, concat-stream@^1.4.7, concat-stream@~1.4.1, concat-stream@~1.4.5: version "1.4.10" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.4.10.tgz#acc3bbf5602cb8cc980c6ac840fa7d8603e3ef36" dependencies: @@ -1701,11 +1699,7 @@ domain-browser@~1.1.0: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" -domelementtype@1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" - -domelementtype@~1.1.1: +domelementtype@1, domelementtype@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" @@ -2244,7 +2238,7 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -findup-sync@0.4.2: +findup-sync@0.4.2, findup-sync@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.2.tgz#a8117d0f73124f5a4546839579fe52d7129fb5e5" dependencies: @@ -2253,15 +2247,6 @@ findup-sync@0.4.2: micromatch "^2.3.7" resolve-dir "^0.1.0" -findup-sync@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" - dependencies: - detect-file "^0.1.0" - is-glob "^2.0.1" - micromatch "^2.3.7" - resolve-dir "^0.1.0" - findup-sync@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16" @@ -3110,7 +3095,7 @@ inherits@1: version "1.0.2" resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -5018,18 +5003,18 @@ pump@^0.3.5: end-of-stream "~1.0.0" once "~1.2.0" -punycode@1.3.2: +punycode@1.3.2, punycode@>=0.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" -punycode@>=0.2.0, punycode@~1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.2.4.tgz#54008ac972aec74175def9cba6df7fa9d3918740" - punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" +punycode@~1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.2.4.tgz#54008ac972aec74175def9cba6df7fa9d3918740" + q-io@^1.10.9: version "1.13.2" resolved "https://registry.yarnpkg.com/q-io/-/q-io-1.13.2.tgz#eea130d481ddb5e1aa1bc5a66855f7391d06f003" @@ -5163,7 +5148,7 @@ read@~1.0.4: dependencies: mute-stream "~0.0.4" -readable-stream@1.1, "readable-stream@>=1.1.13-1 <1.2.0-0", readable-stream@^1.0.27-1, readable-stream@^1.0.33-1, readable-stream@^1.1.13, readable-stream@^1.1.13-1, readable-stream@~1.1.8, readable-stream@~1.1.9: +readable-stream@1.1, "readable-stream@>=1.1.13-1 <1.2.0-0", readable-stream@^1.0.27-1, readable-stream@^1.1.13, readable-stream@^1.1.13-1, readable-stream@~1.1.8, readable-stream@~1.1.9: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" dependencies: @@ -5172,7 +5157,7 @@ readable-stream@1.1, "readable-stream@>=1.1.13-1 <1.2.0-0", readable-stream@^1.0 isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.0, readable-stream@~1.0.17, readable-stream@~1.0.2, readable-stream@~1.0.26, readable-stream@~1.0.31: +"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@^1.0.33-1, readable-stream@~1.0.0, readable-stream@~1.0.17, readable-stream@~1.0.2, readable-stream@~1.0.26, readable-stream@~1.0.31: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: @@ -5181,7 +5166,7 @@ readable-stream@1.1, "readable-stream@>=1.1.13-1 <1.2.0-0", readable-stream@^1.0 isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2: +readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" dependencies: @@ -6312,7 +6297,7 @@ type-is@~1.6.14: media-typer "0.3.0" mime-types "~2.1.13" -typedarray@^0.0.6, typedarray@~0.0.5: +typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" From 8eb925d7c58a64dd2a63e68e706a9dda309fcfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski?= Date: Wed, 12 Apr 2017 13:57:00 +0200 Subject: [PATCH 009/631] chore(browserstack): Update OS X, make iOS 8-10 available to test --- karma-shared.conf.js | 20 ++++++++++++++++---- scripts/travis/build.sh | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/karma-shared.conf.js b/karma-shared.conf.js index 46b98b2c4724..88138cc8e606 100644 --- a/karma-shared.conf.js +++ b/karma-shared.conf.js @@ -85,19 +85,19 @@ module.exports = function(config, specificOptions) { base: 'BrowserStack', browser: 'chrome', os: 'OS X', - os_version: 'Yosemite' + os_version: 'Sierra' }, 'BS_Safari': { base: 'BrowserStack', browser: 'safari', os: 'OS X', - os_version: 'Yosemite' + os_version: 'Sierra' }, 'BS_Firefox': { base: 'BrowserStack', browser: 'firefox', os: 'Windows', - os_version: '8' + os_version: '10' }, 'BS_IE_9': { base: 'BrowserStack', @@ -120,11 +120,23 @@ module.exports = function(config, specificOptions) { os: 'Windows', os_version: '8.1' }, - 'BS_iOS': { + 'BS_iOS_8': { + base: 'BrowserStack', + device: 'iPhone 6', + os: 'ios', + os_version: '8.3' + }, + 'BS_iOS_9': { base: 'BrowserStack', device: 'iPhone 6S', os: 'ios', os_version: '9.3' + }, + 'BS_iOS_10': { + base: 'BrowserStack', + device: 'iPhone 7', + os: 'ios', + os_version: '10.0' } } }); diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index 70dd4182f645..bc2b6635a1db 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -9,7 +9,7 @@ if [ "$JOB" == "ci-checks" ]; then grunt ci-checks elif [ "$JOB" == "unit" ]; then if [ "$BROWSER_PROVIDER" == "browserstack" ]; then - BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_iOS" + BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_iOS_8,BS_iOS_9" else BROWSERS="SL_Chrome,SL_Firefox,SL_Safari_8,SL_Safari_9,SL_IE_9,SL_IE_10,SL_IE_11,SL_iOS" fi From 37a2c20bb85f6a2ffd3c6338116c67ced6e5c022 Mon Sep 17 00:00:00 2001 From: TheHalcyonSavant Date: Mon, 17 Apr 2017 13:45:48 +0200 Subject: [PATCH 010/631] docs(guide/migration): remove duplicate entry for commit 13c252 Closes #15919 --- docs/content/guide/migration.ngdoc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/content/guide/migration.ngdoc b/docs/content/guide/migration.ngdoc index 4a416357f0b0..46ca03ec9d02 100644 --- a/docs/content/guide/migration.ngdoc +++ b/docs/content/guide/migration.ngdoc @@ -484,14 +484,6 @@ lifecycle hook), you may need to manually call `$onInit()` from your constructor }) ``` -
- -**Due to [13c252](https://github.com/angular/angular.js/commit/13c2522baf7c8f616b2efcaab4bffd54c8736591)**, -on **IE11 only**, consecutive text nodes will always get merged. Previously, they would not get -merged if they had no parent. The new behavior, which fixes an IE11 bug affecting interpolation -under certain circumstances, might in some edge-cases have unexpected side effects that you should -be aware of. Please, check the commit message for more details. -
**Due to [04cad4](https://github.com/angular/angular.js/commit/04cad41d26ebaf44b5ee0c29a152d61f235f3efa)**, From 496138f12a03028daa37bb23f63cb61826c857f6 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Sat, 9 Apr 2016 14:25:15 +0200 Subject: [PATCH 011/631] chore: test on Microsoft Edge Closes #13687 Closes #14401 --- karma-shared.conf.js | 13 +++++++++++++ scripts/travis/build.sh | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/karma-shared.conf.js b/karma-shared.conf.js index 88138cc8e606..1fcf53d4a4d6 100644 --- a/karma-shared.conf.js +++ b/karma-shared.conf.js @@ -74,6 +74,12 @@ module.exports = function(config, specificOptions) { platform: 'Windows 8.1', version: '11' }, + 'SL_EDGE': { + base: 'SauceLabs', + browserName: 'microsoftedge', + platform: 'Windows 10', + version: '14' + }, 'SL_iOS': { base: 'SauceLabs', browserName: 'iphone', @@ -120,6 +126,13 @@ module.exports = function(config, specificOptions) { os: 'Windows', os_version: '8.1' }, + 'BS_EDGE': { + base: 'BrowserStack', + browser: 'edge', + browser_version: '14', + os: 'Windows', + os_version: '10' + }, 'BS_iOS_8': { base: 'BrowserStack', device: 'iPhone 6', diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index bc2b6635a1db..c193039bc02d 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -9,9 +9,9 @@ if [ "$JOB" == "ci-checks" ]; then grunt ci-checks elif [ "$JOB" == "unit" ]; then if [ "$BROWSER_PROVIDER" == "browserstack" ]; then - BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_iOS_8,BS_iOS_9" + BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_EDGE,BS_iOS_8,BS_iOS_9" else - BROWSERS="SL_Chrome,SL_Firefox,SL_Safari_8,SL_Safari_9,SL_IE_9,SL_IE_10,SL_IE_11,SL_iOS" + BROWSERS="SL_Chrome,SL_Firefox,SL_Safari_8,SL_Safari_9,SL_IE_9,SL_IE_10,SL_IE_11,SL_EDGE,SL_iOS" fi grunt test:promises-aplus From 7efed006327df7e6f0784b54e856f0a4e87331e5 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Thu, 14 Apr 2016 00:07:10 +0200 Subject: [PATCH 012/631] test(input): exclude tests that are only failing on Edge --- test/ng/animateRunnerSpec.js | 2 +- test/ng/directive/inputSpec.js | 96 ++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 41 deletions(-) diff --git a/test/ng/animateRunnerSpec.js b/test/ng/animateRunnerSpec.js index cd3ddea7f33d..601d3fba3427 100644 --- a/test/ng/animateRunnerSpec.js +++ b/test/ng/animateRunnerSpec.js @@ -329,7 +329,7 @@ describe('$$AnimateRunner', function() { expect(status).toBe(true); })); - it('should break the chian when a function evaluates to false', + it('should break the chain when a function evaluates to false', inject(function($$rAF, $$AnimateRunner) { var runner1 = new $$AnimateRunner(); diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 7014e00624d2..b29fba489fe5 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -5,6 +5,9 @@ describe('input', function() { var helper = {}, $compile, $rootScope, $browser, $sniffer, $timeout, $q; + // UA sniffing to exclude Edge from some date input tests + var isEdge = /\bEdge\//.test(window.navigator.userAgent); + generateInputCompilerHelper(helper); beforeEach(inject(function(_$compile_, _$rootScope_, _$browser_, _$sniffer_, _$timeout_, _$q_) { @@ -688,18 +691,20 @@ describe('input', function() { expect($rootScope.form.alias.$error.month).toBeTruthy(); }); - it('should allow four or more digits in year', function() { - var inputElm = helper.compileInput(''); - helper.changeInputValueTo('10123-03'); - expect(+$rootScope.value).toBe(Date.UTC(10123, 2, 1, 0, 0, 0)); + if (!isEdge) { + it('should allow four or more digits in year', function() { + var inputElm = helper.compileInput(''); - $rootScope.$apply(function() { - $rootScope.value = new Date(Date.UTC(20456, 3, 1, 0, 0, 0)); - }); - expect(inputElm.val()).toBe('20456-04'); - }); + helper.changeInputValueTo('10123-03'); + expect(+$rootScope.value).toBe(Date.UTC(10123, 2, 1, 0, 0, 0)); + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(20456, 3, 1, 0, 0, 0)); + }); + expect(inputElm.val()).toBe('20456-04'); + }); + } it('should only change the month of a bound date', function() { var inputElm = helper.compileInput(''); @@ -899,17 +904,19 @@ describe('input', function() { expect(inputElm).toBeValid(); }); - it('should allow four or more digits in year', function() { - var inputElm = helper.compileInput(''); + if (!isEdge) { + it('should allow four or more digits in year', function() { + var inputElm = helper.compileInput(''); - helper.changeInputValueTo('10123-W03'); - expect(+$rootScope.value).toBe(Date.UTC(10123, 0, 21)); + helper.changeInputValueTo('10123-W03'); + expect(+$rootScope.value).toBe(Date.UTC(10123, 0, 21)); - $rootScope.$apply(function() { - $rootScope.value = new Date(Date.UTC(20456, 0, 28)); + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(20456, 0, 28)); + }); + expect(inputElm.val()).toBe('20456-W04'); }); - expect(inputElm.val()).toBe('20456-W04'); - }); + } it('should use UTC if specified in the options', function() { var inputElm = helper.compileInput(''); @@ -1195,18 +1202,22 @@ describe('input', function() { expect(+$rootScope.value).toBe(+new Date(2000, 0, 1, 1, 2, 0)); }); - it('should allow four or more digits in year', function() { - var inputElm = helper.compileInput(''); - helper.changeInputValueTo('10123-01-01T01:02'); - expect(+$rootScope.value).toBe(+new Date(10123, 0, 1, 1, 2, 0)); + if (!isEdge) { + it('should allow four or more digits in year', function() { + var inputElm = helper.compileInput(''); + + helper.changeInputValueTo('10123-01-01T01:02'); + expect(+$rootScope.value).toBe(+new Date(10123, 0, 1, 1, 2, 0)); + + $rootScope.$apply(function() { + $rootScope.value = new Date(20456, 1, 1, 1, 2, 0); + }); + expect(inputElm.val()).toBe('20456-02-01T01:02:00.000'); + } + ); + } - $rootScope.$apply(function() { - $rootScope.value = new Date(20456, 1, 1, 1, 2, 0); - }); - expect(inputElm.val()).toBe('20456-02-01T01:02:00.000'); - } - ); it('should label parse errors as `datetimelocal`', function() { var inputElm = helper.compileInput('', { @@ -1800,19 +1811,20 @@ describe('input', function() { } ); - it('should allow four or more digits in year', function() { - var inputElm = helper.compileInput(''); - - helper.changeInputValueTo('10123-01-01'); - expect(+$rootScope.value).toBe(Date.UTC(10123, 0, 1, 0, 0, 0)); + if (!isEdge) { + it('should allow four or more digits in year', function() { + var inputElm = helper.compileInput(''); - $rootScope.$apply(function() { - $rootScope.value = new Date(Date.UTC(20456, 1, 1, 0, 0, 0)); - }); - expect(inputElm.val()).toBe('20456-02-01'); - } - ); + helper.changeInputValueTo('10123-01-01'); + expect(+$rootScope.value).toBe(Date.UTC(10123, 0, 1, 0, 0, 0)); + $rootScope.$apply(function() { + $rootScope.value = new Date(Date.UTC(20456, 1, 1, 0, 0, 0)); + }); + expect(inputElm.val()).toBe('20456-02-01'); + } + ); + } it('should label parse errors as `date`', function() { var inputElm = helper.compileInput('', { @@ -4224,10 +4236,14 @@ describe('input', function() { }); expect(inputElm[0].value).toBe(''); - // Support: IE 9-11 + // Support: IE 9-11, Edge // In IE it is not possible to remove the `value` attribute from an input element. - if (!msie) { + if (!msie && !isEdge) { expect(inputElm[0].getAttribute('value')).toBeNull(); + } else { + // Support: IE 9-11, Edge + // This will fail if the Edge bug gets fixed + expect(inputElm[0].getAttribute('value')).toBe('something'); } }); From 69c3faf40589b4ac9e7fa75f3b4fb83349aba60d Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Thu, 13 Apr 2017 12:09:41 +0200 Subject: [PATCH 013/631] test(core): expect that Edge cannot auto-bootstrap in extensions --- test/AngularSpec.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 5e9ef2167429..4b6610527c7c 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -1755,8 +1755,7 @@ describe('angular', function() { }; } - it('should bootstrap from an extension into an extension document for same-origin documents only', function() { - + describe('from extensions into extension documents', function() { // Extension URLs are browser-specific, so we must choose a scheme that is supported by the browser to make // sure that the URL is properly parsed. var protocol; @@ -1773,10 +1772,29 @@ describe('angular', function() { protocol = 'browserext:'; // Upcoming standard scheme. } - expect(allowAutoBootstrap(createFakeDoc({src: protocol + '//something'}, protocol))).toBe(true); - expect(allowAutoBootstrap(createFakeDoc({src: protocol + '//something-else'}, protocol))).toBe(false); + + if (protocol === 'ms-browser-extension:') { + // Support: Edge 13-15 + // In Edge, URLs with protocol 'ms-browser-extension:' return "null" for the origin, + // therefore it's impossible to know if a script is same-origin. + it('should not bootstrap for same-origin documents', function() { + expect(allowAutoBootstrap(createFakeDoc({src: protocol + '//something'}, protocol))).toBe(false); + }); + + } else { + it('should bootstrap for same-origin documents', function() { + + expect(allowAutoBootstrap(createFakeDoc({src: protocol + '//something'}, protocol))).toBe(true); + }); + } + + it('should not bootstrap for cross-origin documents', function() { + expect(allowAutoBootstrap(createFakeDoc({src: protocol + '//something-else'}, protocol))).toBe(false); + }); + }); + it('should bootstrap from a script with no source (e.g. src, href or xlink:href attributes)', function() { expect(allowAutoBootstrap(createFakeDoc({src: null}))).toBe(true); From 080357e906e2ec34e669091ef345fc4442e23ea0 Mon Sep 17 00:00:00 2001 From: Jacob Hansson Date: Fri, 21 Apr 2017 06:39:36 -0500 Subject: [PATCH 014/631] feat(ngMock): describe unflushed http requests The current implementation of $httpBackend.verifyNoOutstandingRequest gives an integer number describing how many requests are unflushed. While it's superficially easy to solve test errors from that message by simply adding an additional $httpBackend.flush(), if a developer is truly not expecting the code to make further requests this is not ideal. This change explicitly prints out which additional requests remain unflushed in the error message, helping her determine if the code needs changing, or if an additional flush is appropriate. Before this change: Unflushed requests: 1 After this change: Unflushed requests: 1 GET /some Closes #10596 Closes #15928 --- src/ngMock/angular-mocks.js | 5 ++++- test/ngMock/angular-mocksSpec.js | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index d6e84c75584f..1d21364da7bb 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -1378,6 +1378,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { } } + handleResponse.description = method + ' ' + url; return handleResponse; function handleResponse() { @@ -1884,7 +1885,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { $httpBackend.verifyNoOutstandingRequest = function(digest) { if (digest !== false) $rootScope.$digest(); if (responses.length) { - throw new Error('Unflushed requests: ' + responses.length); + var unflushedDescriptions = responses.map(function(res) { return res.description; }); + throw new Error('Unflushed requests: ' + responses.length + '\n ' + + unflushedDescriptions.join('\n ')); } }; diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 441509376561..4bfad9d3bb10 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -1678,7 +1678,8 @@ describe('ngMock', function() { expect(function() { hb.verifyNoOutstandingRequest(); - }).toThrowError('Unflushed requests: 1'); + }).toThrowError('Unflushed requests: 1\n' + + ' GET /some'); }); @@ -1690,8 +1691,23 @@ describe('ngMock', function() { expect(function() { hb.verifyNoOutstandingRequest(); - }).toThrowError('Unflushed requests: 1'); + }).toThrowError('Unflushed requests: 1\n' + + ' GET /some'); })); + + + it('should describe multiple unflushed requests', function() { + hb.when('GET').respond(200); + hb.when('PUT').respond(200); + hb('GET', '/some', null, noop, {}); + hb('PUT', '/elsewhere', null, noop, {}); + + expect(function() { + hb.verifyNoOutstandingRequest(); + }).toThrowError('Unflushed requests: 2\n' + + ' GET /some\n' + + ' PUT /elsewhere'); + }); }); From 8d7c7f4a8eed3dbf46ecb277c54b5c0f1eb1958e Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Tue, 9 Aug 2016 22:06:55 +0200 Subject: [PATCH 015/631] test(select, ngOptions): add more tests for "required" with "empty" or "unknown" option --- src/ng/directive/ngOptions.js | 1 - test/ng/directive/ngOptionsSpec.js | 127 +++++++++++++++++++---- test/ng/directive/selectSpec.js | 157 ++++++++++++++++++++++------- 3 files changed, 231 insertions(+), 54 deletions(-) diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js index e82d5e49813a..0d4323487cae 100644 --- a/src/ng/directive/ngOptions.js +++ b/src/ng/directive/ngOptions.js @@ -704,7 +704,6 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, ngModelCtrl.$render(); } } - } } diff --git a/test/ng/directive/ngOptionsSpec.js b/test/ng/directive/ngOptionsSpec.js index 7a3c4ba492bf..4aae08cabcf0 100644 --- a/test/ng/directive/ngOptionsSpec.js +++ b/test/ng/directive/ngOptionsSpec.js @@ -2,12 +2,13 @@ describe('ngOptions', function() { - var scope, formElement, element, $compile, linkLog; + var scope, formElement, element, $compile, linkLog, ngModelCtrl; function compile(html) { formElement = jqLite('
' + html + '
'); element = formElement.find('select'); $compile(formElement)(scope); + ngModelCtrl = element.controller('ngModel'); scope.$apply(); } @@ -181,6 +182,7 @@ describe('ngOptions', function() { afterEach(function() { scope.$destroy(); //disables unknown option work during destruction dealoc(formElement); + ngModelCtrl = null; }); function createSelect(attrs, blank, unknown) { @@ -2925,42 +2927,68 @@ describe('ngOptions', function() { }); - describe('ngRequired', function() { + describe('required state', function() { - it('should allow bindings on ngRequired', function() { + it('should set the error if the empty option is selected', function() { + createSelect({ + 'ng-model': 'selection', + 'ng-options': 'item for item in values', + 'required': '' + }, true); + + scope.$apply(function() { + scope.values = ['a', 'b']; + scope.selection = scope.values[0]; + }); + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + + var options = element.find('option'); + + // view -> model + browserTrigger(options[0], 'click'); + expect(element).toBeInvalid(); + expect(ngModelCtrl.$error.required).toBeTruthy(); + + browserTrigger(options[1], 'click'); + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + + // model -> view + scope.$apply('selection = "unmatched value"'); + expect(options[0]).toBeMarkedAsSelected(); + expect(element).toBeInvalid(); + expect(ngModelCtrl.$error.required).toBeTruthy(); + }); + + + it('should validate with empty option and bound ngRequired', function() { createSelect({ 'ng-model': 'value', 'ng-options': 'item.name for item in values', 'ng-required': 'required' }, true); - scope.$apply(function() { scope.values = [{name: 'A', id: 1}, {name: 'B', id: 2}]; scope.required = false; }); - element.val(''); - browserTrigger(element, 'change'); + var options = element.find('option'); + + browserTrigger(options[0], 'click'); expect(element).toBeValid(); - scope.$apply(function() { - scope.required = true; - }); + scope.$apply('required = true'); expect(element).toBeInvalid(); - scope.$apply(function() { - scope.value = scope.values[0]; - }); + scope.$apply('value = values[0]'); expect(element).toBeValid(); - element.val(''); - browserTrigger(element, 'change'); + browserTrigger(options[0], 'click'); expect(element).toBeInvalid(); - scope.$apply(function() { - scope.required = false; - }); + scope.$apply('required = false'); expect(element).toBeValid(); }); @@ -2989,6 +3017,43 @@ describe('ngOptions', function() { }); + it('should NOT set the error if the empty option is present but required attribute is not', + function() { + scope.$apply(function() { + scope.values = ['a', 'b']; + }); + + createSingleSelect(); + + expect(element).toBeValid(); + expect(element).toBePristine(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + } + ); + + + it('should NOT set the error if the unknown option is selected', function() { + createSelect({ + 'ng-model': 'selection', + 'ng-options': 'item for item in values', + 'required': '' + }); + + scope.$apply(function() { + scope.values = ['a', 'b']; + scope.selection = 'a'; + }); + + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + + scope.$apply('selection = "c"'); + expect(element).toEqualSelect(['?'], 'string:a', 'string:b'); + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + }); + + it('should allow falsy values as values', function() { createSelect({ 'ng-model': 'value', @@ -3009,6 +3074,34 @@ describe('ngOptions', function() { expect(element).toBeValid(); expect(scope.value).toBe(false); }); + + + it('should validate after option list was updated', function() { + createSelect({ + 'ng-model': 'selection', + 'ng-options': 'item for item in values', + 'required': '' + }, true); + + scope.$apply(function() { + scope.values = ['A', 'B']; + scope.selection = scope.values[0]; + }); + + expect(element).toEqualSelect('', ['string:A'], 'string:B'); + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + + scope.$apply(function() { + scope.values = ['C', 'D']; + }); + + expect(element).toEqualSelect([''], 'string:C', 'string:D'); + expect(element).toBeInvalid(); + expect(ngModelCtrl.$error.required).toBeTruthy(); + // ngModel sets undefined for invalid values + expect(scope.selection).toBeUndefined(); + }); }); describe('required and empty option', function() { diff --git a/test/ng/directive/selectSpec.js b/test/ng/directive/selectSpec.js index 975e4b594d77..1a372f772973 100644 --- a/test/ng/directive/selectSpec.js +++ b/test/ng/directive/selectSpec.js @@ -7,6 +7,7 @@ describe('select', function() { formElement = jqLite('
' + html + '
'); element = formElement.find('select'); $compile(formElement)(scope); + ngModelCtrl = element.controller('ngModel'); scope.$digest(); } @@ -79,6 +80,7 @@ describe('select', function() { afterEach(function() { scope.$destroy(); //disables unknown option work during destruction dealoc(formElement); + ngModelCtrl = null; }); @@ -190,54 +192,108 @@ describe('select', function() { }); - it('should require', function() { - compile( - ''); + describe('required state', function() { - scope.change = function() { - scope.log += 'change;'; - }; + it('should set the error if the empty option is selected', function() { + compile( + ''); - scope.$apply(function() { - scope.log = ''; - scope.selection = 'c'; + scope.$apply(function() { + scope.selection = 'a'; + }); + + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + + var options = element.find('option'); + + // view -> model + browserTrigger(options[0], 'click'); + expect(element).toBeInvalid(); + expect(ngModelCtrl.$error.required).toBeTruthy(); + + browserTrigger(options[1], 'click'); + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + + // model -> view + scope.$apply('selection = null'); + options = element.find('option'); + expect(options[0]).toBeMarkedAsSelected(); + expect(element).toBeInvalid(); + expect(ngModelCtrl.$error.required).toBeTruthy(); }); - expect(scope.form.select.$error.required).toBeFalsy(); - expect(element).toBeValid(); - expect(element).toBePristine(); - scope.$apply(function() { - scope.selection = ''; + it('should validate with empty option and bound ngRequired', function() { + compile( + ''); + + scope.$apply(function() { + scope.required = false; + }); + + var options = element.find('option'); + + browserTrigger(options[0], 'click'); + expect(element).toBeValid(); + + scope.$apply('required = true'); + expect(element).toBeInvalid(); + + scope.$apply('selection = "a"'); + expect(element).toBeValid(); + expect(element).toEqualSelect('', ['a'], 'b'); + + browserTrigger(options[0], 'click'); + expect(element).toBeInvalid(); + + scope.$apply('required = false'); + expect(element).toBeValid(); }); - expect(scope.form.select.$error.required).toBeTruthy(); - expect(element).toBeInvalid(); - expect(element).toBePristine(); - expect(scope.log).toEqual(''); - element[0].value = 'c'; - browserTrigger(element, 'change'); - expect(element).toBeValid(); - expect(element).toBeDirty(); - expect(scope.log).toEqual('change;'); - }); + it('should not be invalid if no required attribute is present', function() { + compile( + ''); + expect(element).toBeValid(); + expect(element).toBePristine(); + }); - it('should not be invalid if no require', function() { - compile( - ''); - expect(element).toBeValid(); - expect(element).toBePristine(); - }); + it('should NOT set the error if the unknown option is selected', function() { + compile( + ''); + scope.$apply(function() { + scope.selection = 'a'; + }); + + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + + scope.$apply('selection = "c"'); + expect(element).toEqualSelect([unknownValue('c')], 'a', 'b'); + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + }); + + }); it('should work with repeated value options', function() { scope.robots = ['c3p0', 'r2d2']; @@ -2358,6 +2414,35 @@ describe('select', function() { expect(previouslySelectedOptionElement).not.toBe(optionElements[0]); }); + + it('should validate when the options change', function() { + scope.values = ['A', 'B']; + scope.selection = 'A'; + + compile( + '' + ); + + expect(element).toEqualSelect('', ['A'], 'B'); + expect(element).toBeValid(); + expect(ngModelCtrl.$error.required).toBeFalsy(); + + scope.$apply(function() { + // Only when new objects are used, ngRepeat re-creates the element from scratch + scope.values = ['B', 'C']; + }); + + expect(element).toEqualSelect([''], 'B', 'C'); + expect(element).toBeInvalid(); + expect(ngModelCtrl.$error.required).toBeTruthy(); + // ngModel sets undefined for invalid values + expect(scope.selection).toBeUndefined(); + }); + + }); From 5878f07474755cb3df1e727cef4e7e4716f44783 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Tue, 18 Apr 2017 15:24:33 +0200 Subject: [PATCH 016/631] fix(ngOptions): select unknown option if unmatched model does not match empty option When a regular / ngOptions select has an explicit *empty* option, this option can be selected by the user and will set the model to `null`. It is also selected when the model is set to `null` or `undefined`. When the model is set to a value that does not match any option value, and is also not `null` or `undefined`, the *unknown* option is inserted and selected - this is an explicit marker that the select is in an invalid / unknown state, which is different from an allowed empty state. Previously, regular selects followed this logic, whereas ngOptions selects selected the empty option in the case described above. This patch makes the behavior consistent between regular / ngOptions select - the latter will now insert and select the unknown option. The order of the options has been fixed to unknown -> empty -> actual options. --- src/ng/directive/ngOptions.js | 10 ++++-- src/ng/directive/select.js | 12 ++++--- test/helpers/matchers.js | 21 ++++++++++++ test/ng/directive/ngOptionsSpec.js | 51 ++++++++++++++++++++++++++-- test/ng/directive/selectSpec.js | 54 +++++++++++++++--------------- 5 files changed, 112 insertions(+), 36 deletions(-) diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js index 0d4323487cae..97b4507fd871 100644 --- a/src/ng/directive/ngOptions.js +++ b/src/ng/directive/ngOptions.js @@ -473,7 +473,8 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, option.element.setAttribute('selected', 'selected'); } else { - if (providedEmptyOption) { + if (value == null && providedEmptyOption) { + selectCtrl.removeUnknownOption(); selectCtrl.selectEmptyOption(); } else if (selectCtrl.unknownOption.parent().length) { selectCtrl.updateUnknownOption(value); @@ -657,7 +658,12 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, // Ensure that the empty option is always there if it was explicitly provided if (providedEmptyOption) { - selectElement.prepend(selectCtrl.emptyOption); + + if (selectCtrl.unknownOption.parent().length) { + selectCtrl.unknownOption.after(selectCtrl.emptyOption); + } else { + selectElement.prepend(selectCtrl.emptyOption); + } } options.items.forEach(function addOption(option) { diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js index dc828764d3da..1f9d0cba0e7f 100644 --- a/src/ng/directive/select.js +++ b/src/ng/directive/select.js @@ -44,11 +44,13 @@ var SelectController = // to create it in ' + + '' + '' + '' + ''); @@ -411,24 +390,45 @@ describe('select', function() { scope.$digest(); options = element.find('option'); - expect(options.length).toBe(2); - expect(options[0]).toBeMarkedAsSelected(); - expect(options[1]).not.toBeMarkedAsSelected(); + expect(options.length).toBe(3); + expect(options[0]).not.toBeMarkedAsSelected(); + expect(options[1]).toBeMarkedAsSelected(); + expect(options[2]).not.toBeMarkedAsSelected(); scope.selected = 'b'; scope.$digest(); options = element.find('option'); expect(options[0]).not.toBeMarkedAsSelected(); - expect(options[1]).toBeMarkedAsSelected(); + expect(options[1]).not.toBeMarkedAsSelected(); + expect(options[2]).toBeMarkedAsSelected(); - scope.selected = 'no match'; + // This will select the empty option + scope.selected = null; scope.$digest(); + expect(options[0]).toBeMarkedAsSelected(); + expect(options[1]).not.toBeMarkedAsSelected(); + expect(options[2]).not.toBeMarkedAsSelected(); + + // This will add and select the unknown option + scope.selected = 'unmatched value'; + scope.$digest(); options = element.find('option'); + expect(options[0]).toBeMarkedAsSelected(); expect(options[1]).not.toBeMarkedAsSelected(); expect(options[2]).not.toBeMarkedAsSelected(); + expect(options[3]).not.toBeMarkedAsSelected(); + + // Back to matched value + scope.selected = 'b'; + scope.$digest(); + options = element.find('option'); + + expect(options[0]).not.toBeMarkedAsSelected(); + expect(options[1]).not.toBeMarkedAsSelected(); + expect(options[2]).toBeMarkedAsSelected(); }); describe('empty option', function() { From 4b06637f703d2a94baedfda64a8e3ac8eea26403 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Tue, 18 Apr 2017 15:16:15 +0200 Subject: [PATCH 017/631] chore(matchers): improve output for toBeMarkedAsSelected --- test/helpers/matchers.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/helpers/matchers.js b/test/helpers/matchers.js index c543bbb965aa..ac297609e579 100644 --- a/test/helpers/matchers.js +++ b/test/helpers/matchers.js @@ -365,13 +365,15 @@ beforeEach(function() { return { compare: function(actual) { var errors = []; + var optionVal = toJson(actual.value); + if (actual.selected === null || typeof actual.selected === 'undefined' || actual.selected === false) { - errors.push('Expected option property "selected" to be truthy'); + errors.push('Expected option with value ' + optionVal + ' to have property "selected" set to truthy'); } // Support: IE 9 only if (msie !== 9 && actual.hasAttribute('selected') === false) { - errors.push('Expected option to have attribute "selected"'); + errors.push('Expected option with value ' + optionVal + ' to have attribute "selected"'); } var result = { @@ -383,13 +385,15 @@ beforeEach(function() { }, negativeCompare: function(actual) { var errors = []; + var optionVal = toJson(actual.value); + if (actual.selected) { - errors.push('Expected option property "selected" to be falsy'); + errors.push('Expected option with value ' + optionVal + ' property "selected" to be falsy'); } // Support: IE 9 only if (msie !== 9 && actual.hasAttribute('selected')) { - errors.push('Expected option not to have attribute "selected"'); + errors.push('Expected option with value ' + optionVal + ' not to have attribute "selected"'); } var result = { From ff0e61166d3dca59351e3913e0360c24d1bce99c Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Tue, 18 Apr 2017 19:59:42 +0200 Subject: [PATCH 018/631] refactor(select, ngOptions): extract common methods; make consistent --- src/ng/directive/ngOptions.js | 15 +++------------ src/ng/directive/select.js | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js index 97b4507fd871..eecc6ccaa8c6 100644 --- a/src/ng/directive/ngOptions.js +++ b/src/ng/directive/ngOptions.js @@ -449,12 +449,12 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, if (!multiple) { selectCtrl.writeValue = function writeNgOptionsValue(value) { - var selectedOption = options.selectValueMap[selectElement.val()]; + var selectedOption = selectElement[0].options[selectElement[0].selectedIndex]; var option = options.getOptionFromViewValue(value); // Make sure to remove the selected attribute from the previously selected option // Otherwise, screen readers might get confused - if (selectedOption) selectedOption.element.removeAttribute('selected'); + if (selectedOption) selectedOption.removeAttribute('selected'); if (option) { // Don't update the option when it is already selected. @@ -464,7 +464,6 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, if (selectElement[0].value !== option.selectValue) { selectCtrl.removeUnknownOption(); - selectCtrl.unselectEmptyOption(); selectElement[0].value = option.selectValue; option.element.selected = true; @@ -472,15 +471,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, option.element.setAttribute('selected', 'selected'); } else { - - if (value == null && providedEmptyOption) { - selectCtrl.removeUnknownOption(); - selectCtrl.selectEmptyOption(); - } else if (selectCtrl.unknownOption.parent().length) { - selectCtrl.updateUnknownOption(value); - } else { - selectCtrl.renderUnknownOption(value); - } + selectCtrl.selectUnknownOrEmptyOption(value); } }; diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js index 1f9d0cba0e7f..d8a19605f96d 100644 --- a/src/ng/directive/select.js +++ b/src/ng/directive/select.js @@ -86,7 +86,7 @@ var SelectController = self.unselectEmptyOption = function() { if (self.hasEmptyOption) { - self.emptyOption.removeAttr('selected'); + setOptionSelectedStatus(self.emptyOption, false); } }; @@ -128,14 +128,7 @@ var SelectController = var selectedOption = $element[0].options[$element[0].selectedIndex]; setOptionSelectedStatus(jqLite(selectedOption), true); } else { - if (value == null && self.emptyOption) { - self.removeUnknownOption(); - self.selectEmptyOption(); - } else if (self.unknownOption.parent().length) { - self.updateUnknownOption(value); - } else { - self.renderUnknownOption(value); - } + self.selectUnknownOrEmptyOption(value); } }; @@ -178,6 +171,16 @@ var SelectController = return !!optionsMap.get(value); }; + self.selectUnknownOrEmptyOption = function(value) { + if (value == null && self.emptyOption) { + self.removeUnknownOption(); + self.selectEmptyOption(); + } else if (self.unknownOption.parent().length) { + self.updateUnknownOption(value); + } else { + self.renderUnknownOption(value); + } + }; var renderScheduled = false; function scheduleRender() { From e4c2fe6d427cb1540977520f7e31a7e7a30acfda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski?= Date: Wed, 12 Apr 2017 13:19:53 +0200 Subject: [PATCH 019/631] refactor(*): remove workarounds for IE <9, update IE/Edge-related comments --- .github/ISSUE_TEMPLATE.md | 4 +- .../ngdoc/api/directive.template.html | 3 -- docs/content/guide/bootstrap.ngdoc | 4 +- docs/content/guide/ie.ngdoc | 8 ++-- docs/content/misc/faq.ngdoc | 6 +-- src/Angular.js | 10 ++-- src/ng/directive/ngOptions.js | 6 ++- src/ng/directive/select.js | 8 ++-- src/ngSanitize/sanitize.js | 16 ++----- test/AngularSpec.js | 1 + test/helpers/privateMocks.js | 4 +- test/helpers/testabilityPatch.js | 21 +------- test/jqLiteSpec.js | 2 +- test/minErrSpec.js | 1 + test/ng/compileSpec.js | 48 ++++++++----------- test/ng/directive/booleanAttrsSpec.js | 2 +- test/ng/locationSpec.js | 28 ++--------- test/ng/urlUtilsSpec.js | 2 +- 18 files changed, 57 insertions(+), 117 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 82e6b016d1cb..5e11e611ee8e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -29,8 +29,8 @@ https://plnkr.co or similar (you can use this template as a starting point: http **Angular version:** 1.x.y -**Browser:** [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] +**Browser:** [all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] **Anything else:** - \ No newline at end of file + diff --git a/docs/config/templates/ngdoc/api/directive.template.html b/docs/config/templates/ngdoc/api/directive.template.html index 7e14ce0c6411..b30bdb0451f2 100644 --- a/docs/config/templates/ngdoc/api/directive.template.html +++ b/docs/config/templates/ngdoc/api/directive.template.html @@ -18,9 +18,6 @@

Usage

    {% if doc.restrict.element %}
  • as element: - {% if doc.name.indexOf('ng') == 0 -%} - (This directive can be used as custom element, but be aware of IE restrictions). - {%- endif %} {% code %} <{$ doc.name | dashCase $} {%- for param in doc.params %} diff --git a/docs/content/guide/bootstrap.ngdoc b/docs/content/guide/bootstrap.ngdoc index bd13ec1b1d8e..573a989818e6 100644 --- a/docs/content/guide/bootstrap.ngdoc +++ b/docs/content/guide/bootstrap.ngdoc @@ -40,8 +40,8 @@ initialization. 3. If you choose to use the old style directive syntax `ng:` then include xml-namespace in `html` - to make IE happy. (This is here for historical reasons, and we no longer recommend use of - `ng:`.) + when running the page in the XHTML mode. (This is here for historical reasons, and we no longer + recommend use of `ng:`.) diff --git a/docs/content/guide/ie.ngdoc b/docs/content/guide/ie.ngdoc index 8cc554e8ec01..ffd70ba69b4c 100644 --- a/docs/content/guide/ie.ngdoc +++ b/docs/content/guide/ie.ngdoc @@ -7,7 +7,7 @@
    **Note:** AngularJS 1.3 has dropped support for IE8. Read more about it on -[our blog](http://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html). +[our blog](https://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html). AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time addressing issues specific to IE8 or earlier.
    @@ -19,7 +19,7 @@ on IE. The project currently supports and will attempt to fix bugs for IE9 and above. The continuous integration server runs all the tests against IE9, IE10, and IE11. See [Travis CI](https://travis-ci.org/angular/angular.js) and -[ci.angularjs.org](http://ci.angularjs.org). +[ci.angularjs.org](https://ci.angularjs.org). We do not run tests on IE8 and below. A subset of the AngularJS functionality may work on these browsers, but it is up to you to test and decide whether it works for your particular app. @@ -27,8 +27,8 @@ browsers, but it is up to you to test and decide whether it works for your parti To ensure your AngularJS application works on IE please consider: -1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome and Firefox - but does not work in Internet Explorer <= 11 (the most recent version at time of writing). +1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome, Firefox, + Safari and Edge but does not work in Internet Explorer (even 11). 2. For the `type` attribute of buttons, use `ng-attr-type` tags instead of `type="{{ someExpression }}"`. If using the latter, Internet Explorer overwrites the expression with `type="submit"` before AngularJS has a chance to interpolate it. diff --git a/docs/content/misc/faq.ngdoc b/docs/content/misc/faq.ngdoc index cb3838f6e219..e7029067a90d 100644 --- a/docs/content/misc/faq.ngdoc +++ b/docs/content/misc/faq.ngdoc @@ -142,10 +142,8 @@ We run our extensive test suite against the following browsers: the latest versi Firefox, Safari, and Safari for iOS, as well as Internet Explorer versions 9-11. See {@link guide/ie Internet Explorer Compatibility} for more details on supporting legacy IE browsers. -If a browser is untested, it doesn't mean it won't work; for example, older Android (2.3.x) -is supported in the sense that we avoid the dot notation for reserved words as property names, -but we don't actively test changes against it. You can also expect browsers to work that share -a large part of their codebase with a browser we test, such as Opera > version 12 +If a browser is untested, it doesn't mean it won't work. You can also expect browsers to work that +share a large part of their codebase with a browser we test, such as Opera 15 or newer (uses the Blink engine), or the various Firefox derivatives. diff --git a/src/Angular.js b/src/Angular.js index 7f539e81fb52..42942d432c01 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1353,7 +1353,7 @@ function fromJson(json) { var ALL_COLONS = /:/g; function timezoneToOffset(timezone, fallback) { - // Support: IE 9-11 only, Edge 13-14+ + // Support: IE 9-11 only, Edge 13-15+ // IE/Edge do not "understand" colon (`:`) in timezone timezone = timezone.replace(ALL_COLONS, ''); var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; @@ -1380,12 +1380,7 @@ function convertTimezoneToLocal(date, timezone, reverse) { * @returns {string} Returns the string representation of the element. */ function startingTag(element) { - element = jqLite(element).clone(); - try { - // turns out IE does not let you set .html() on elements which - // are not allowed to have children. So we just ignore it. - element.empty(); - } catch (e) { /* empty */ } + element = jqLite(element).clone().empty(); var elemHtml = jqLite('
    ').append(element).html(); try { return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : @@ -1523,6 +1518,7 @@ function allowAutoBootstrap(document) { var script = document.currentScript; if (!script) { + // Support: IE 9-11 only // IE does not have `document.currentScript` return true; } diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js index eecc6ccaa8c6..91cc45bf16aa 100644 --- a/src/ng/directive/ngOptions.js +++ b/src/ng/directive/ngOptions.js @@ -407,7 +407,8 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, } - // we can't just jqLite('