Compare commits

...

37 Commits

Author SHA1 Message Date
Martin Staffa 4a34c8e1b8 docs(CHANGELOG.md): add changes for 1.6.6 2017-08-18 15:12:44 +02:00
Martin Staffa fec2ea8c1b chore(karma): use dots reporter by default
dots is nicer for local testing, as you usually don't run a
ton of test suites, and the progress visualization is more important .
It's also more readable if you skip many tests (i.e. run isolated tests)

Closes #16167
2017-08-18 15:08:22 +02:00
Martin Staffa 7566c7d26f chore(code.angularjs): delete old zip files on snapshot 2017-08-15 10:59:19 +02:00
Martin Staffa 02a19c0f39 chore(code.angularjs): enable directory listings 2017-08-15 10:59:19 +02:00
Martin Staffa 6e6e4eec9d chore(travis): skip build on deployment job when from Pull Request 2017-08-15 10:59:19 +02:00
Martin Staffa 46f14fa4b8 chore(docs-app): only copy relevant assets
This keeps the size of the docs-app build down.
Especially needed to keep the size of the generated build .zip
under 10MB, which is the limit for firebase / gcs https function transfers
2017-08-15 10:59:19 +02:00
Martin Staffa bad2249bcd chore(docs-app): load example files based on active deployment 2017-08-15 10:59:19 +02:00
Georgios Kalpakas c2148ec15c test(ngOptions): fix flaky test on Firefox 54+ and Safari 9
Closes #16149
2017-08-12 12:06:11 +03:00
Martin Staffa 474e40498e chore(travis): manually install yarn
Currently, the yarn version on Travis is outdated:
https://github.com/travis-ci/packer-templates/issues/478
2017-08-11 16:38:15 +02:00
Frederik Prijck e9a6792d7f docs(*): change Angular to AngularJS
Closes #16163
2017-08-11 16:57:43 +03:00
Frederik Prijck adc1501caa docs($compile): update preAssignBindingsEnabled description
Previously, the changelog and migration guide did not mention the fact
that this only applies to controllers which are part of a
directive/component.

This commit ensures the changelog and migration guide mentions this explicitly.

Closes #15740

Closes #16162
2017-08-11 16:26:31 +03:00
Georgios Kalpakas 7a53707c8f chore(karma): run tests on latest Chrome (59) and Firefox (54) available on Saucelabs
Closes #16141
2017-08-04 12:10:31 +03:00
Georgios Kalpakas 57fe5b320d test(*): fix tests involving submit on Chrome 60
On Chrome 60 (at least on Windows) the `submit` event when clicking on a submit
button is not fired on the form element, unless it is already part of the DOM.
2017-08-04 12:10:06 +03:00
andypotts 671bebde0a docs(guide/concepts): simplify currency exchange API example (YQL --> Fixer.io)
Fixes #16130

Closes #16137
2017-08-02 13:34:41 +03:00
Carl b51ded6736 fix($httpParamSerializer): ignore functions
Closes #16133
2017-07-31 16:15:27 +03:00
Zita Nemeckova 3ec1819b91 feat($compile): add strictComponentBindingsEnabled() method
Closes #16129
2017-07-28 13:54:31 +03:00
Chirag Bhatia 009ebec64c fix($resource): do not throw when calling old $cancelRequest()
Closes #16037
2017-07-27 10:40:04 +03:00
Kerry McCullough 9256dbc420 feat($resource): add resource to response for error interceptors
Closes #16109
2017-07-18 17:12:11 +03:00
Jason Bedard 7504656a26 fix($parse): do not shallow-watch computed property keys
Shallow watching is not enough when an object implements a non-pure toString
2017-07-18 00:21:42 -07:00
Jason Bedard e74cdf4b59 refactor($parse): remove unnecessary .constant if when collecting inputs 2017-07-18 00:21:42 -07:00
Jason Bedard 9d6c3f3ec2 fix($parse): support constants in computed keys 2017-07-18 00:21:42 -07:00
Martin Staffa ac0e260765 test(ngTouch.ngClick): exclude failing Edge test 2017-07-14 12:57:55 +02:00
Martin Staffa 28c0213ee5 test(core): expect that Edge cannot auto-bootstrap in extensions 2017-07-14 12:57:55 +02:00
Martin Staffa 8b4d85c015 chore(*): deploy (docs|code) .angularjs.org to Firebase via Travis
- code.angularjs.org and docs.angularjs.org are two separate Firebase projects
- both are automatically deployed via Travis config
- Travis is split up into 2 build stages: first, all tests are run, and if they pass, the deploy
stage runs a single job with both deployments (actual deployment depends on the state of the commit)
- docs. is deployed directly to Firebase hosting
- code. is uploaded to Firebase Google Cloud Storage and uses Firebase hosting rewrites to acces the
files
- jenkins builds still push the code builds to the code.angularjs.org Github repository

Closes #9674
Closes #16093
2017-07-14 12:57:55 +02:00
Martin Staffa 2aeda67909 test(input): exclude tests that are only failing on Edge 2017-07-14 12:57:55 +02:00
Martin Staffa ad68ee192e chore(travis): test on Microsoft Edge 2017-07-14 12:57:55 +02:00
Martin Staffa 970ba117eb chore(grunt): increase task readability 2017-07-14 12:57:55 +02:00
Martin Staffa 30e097b389 chore(travis): use "karma-spec-reporter"
dots reporter creates lots of empty space that makes it tedious to scroll
through the logs. "Spec" is configured to only report on failures.
2017-07-14 12:57:55 +02:00
Martin Staffa e8e02b8bce chore(travis): add commitplease validation to ci-checks
This will provide feedback to contributors without getting in the way of writing invalid commit
messages locally.

The git hook integration is turned off.

Committers who push directly to the repo can be expected to use correct commit messages ;)
Most changes go through PRs anyway.

Note that "Merge commit" messages and everything starting with "WIP" is always allowed
by commitplease. Follow issue https://github.com/jzaefferer/commitplease/issues/101 for more info.

Related to #14888
Closes #16097
2017-07-14 12:57:55 +02:00
Martin Staffa e36a3e89f5 chore(travis): fix bower install
We removed `grunt package` from JOB_UNIT in 4015e0fde5,
but this runs `grunt bower` which JOB_UNIT needs.

This commit adds `grunt bower` to JOB_UNIT.

Closes #16105
2017-07-14 12:57:55 +02:00
Martin Staffa 1102c41196 chore(travis): don't build package for the "unit" job
Source test:unit do not need built or packaged code,
and, and tests:docs only need built code.
2017-07-14 12:57:55 +02:00
Martin Staffa e970c8fce9 chore(doc-gen): insert current tagged version if missing from list of all versions
In commit ce49edc08b, we switched to npm info (now yarn info)
instead of the local git repository information to get the list of currently available versions for
the docs app. This means that during a release the version that is currently tagged is not yet
available on npm, and therefore our list of available versions is incomplete.

We now simply add the current build version (read from build/version.json) to the list of all
versions if it fulfills the following conditions:

- it is not a snapshot build
- it is not already part of the list of all versions (i.e. if you are building locally on a tagged commit)

Closes #15741
Closes #16099
2017-07-12 11:34:34 +02:00
Martin Staffa 04271d6b2c docs(ngRepeat): improve track by note 2017-07-12 11:33:50 +02:00
Nikos Katsos 2e1163ef5c fix($http): do not throw error if Content-Type is not application/json but response is JSON-like
Previously, when the response data was JSON-like, `$http` would try to
`JSON.parse` them and throw if they were not actually JSON. This happened even
if the `Content-Type` header was not `application/json`. As a result, it was not
possible to send `text/plain` data that looked like JSON (e.g. `{abcd}`).
The reason for not relying solely on the `Content-Type` is that many users serve
JSON data without proper headers.

This commit fixes it by returning the raw response text if `$http` fails to
parse a JSON-like response, unless the `Content-Type` header has been explicitly
set to `application/json` (in which case it will still fail with an error).

Fixes #16027

Closes #16075
2017-07-11 18:51:33 +03:00
Eyal Ronel 75befe723a docs($resource): add missing closing square bracket in example
Closes #16090
2017-07-11 09:49:47 +03:00
Frederik Prijck 5e2bc5bbf3 feat($http): allow differentiation between XHR completion, error, abort, timeout
Previously, it wasn't possible to tell if an `$http`-initiated XMLHttpRequest
was completed normally or with an error or it was aborted or timed out.
This commit adds a new property on the `response` object (`xhrStatus`) which
allows to defferentiate between the possible statuses.

Fixes #15924

Closes #15847
2017-07-04 15:25:12 +03:00
Martin Staffa 079c485b92 docs(ngOpen): improve example, correct browser compat note
Firefox supports details since version 49
2017-07-04 12:32:46 +02:00
61 changed files with 1541 additions and 350 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"projects": {
"default": "docs-angularjs-org-9p2"
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ performance/temp*.html
*~
*.swp
angular.js.tmproj
/node_modules/
node_modules/
bower_components/
angular.xcodeproj
.idea
+59 -13
View File
@@ -7,11 +7,10 @@ cache:
yarn: true
directories:
- bower_components
- docs/bower_components
branches:
except:
- /^g3_.*$/
- "/^g3_.*$/"
env:
matrix:
@@ -21,14 +20,15 @@ env:
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
global:
- CXX=g++-4.8 # node 4 likes the G++ v4.8 compiler
# node 4 likes the G++ v4.8 compiler
# see https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements
- CXX=g++-4.8
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
- LOGS_DIR=/tmp/angular-build/logs
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
- secure: oTBjhnOKhs0qDSKTf7fE4f6DYiNDPycvB7qfSF5QRIbJK/LK/J4UtFwetXuXj79HhUZG9qnoT+5e7lPaiaMlpsIKn9ann7ffqFWN1E8TMtpJF+AGigx3djYElwfgf5nEnFUFhwjFzvbfpZNnxVGgX5YbIZpe/WUbHkP4ffU0Wks=
# node 4 likes the G++ v4.8 compiler
# see https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements
addons:
apt:
sources:
@@ -36,22 +36,68 @@ addons:
packages:
- g++-4.8
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.27.5
- export PATH="$HOME/.yarn/bin:$PATH"
before_script:
- du -sh ./node_modules ./bower_components/ ./docs/bower_components/ || true
- ./scripts/travis/before_build.sh
- du -sh ./node_modules ./bower_components/ || true
- "./scripts/travis/before_build.sh"
script:
- ./scripts/travis/build.sh
- "./scripts/travis/build.sh"
after_script:
- ./scripts/travis/tear_down_browser_provider.sh
- ./scripts/travis/print_logs.sh
- "./scripts/travis/tear_down_browser_provider.sh"
- "./scripts/travis/print_logs.sh"
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/d2120f3f2bb39a4531b2
- http://104.197.9.155:8484/hubot/travis/activity #hubot-server
on_success: always # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: always # default: false
on_success: always # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: always # default: false
jobs:
include:
- stage: deploy
env:
- JOB=deploy
before_script: skip
script:
- "./scripts/travis/build.sh"
# Work around the 10min Travis timeout so the code.angularjs firebase+gcs code deploy can complete
before_deploy: |
function keep_alive() {
while true; do
echo -en "\a"
sleep 5
done
}
keep_alive &
deploy:
- provider: firebase
skip_cleanup: true
token:
secure: $FIREBASE_TOKEN
on:
repo: angular/angular.js
all_branches: true
# deploy a new docs version when the commit is tagged on the "latest" npm version
condition: $TRAVIS_TAG != '' && $( jq ".distTag" "package.json" | tr -d "\"[:space:]" ) = latest
- provider: gcs
skip_cleanup: true
access_key_id: GOOGLDB7W2J3LFHICF3R
secret_access_key:
secure: tHIFdSq55qkyZf9zT/3+VkhUrTvOTMuswxXU3KyWaBrSieZqG0UnUDyNm+n3lSfX95zEl/+rJAWbfvhVSxZi13ndOtvRF+MdI1cvow2JynP0aDSiPffEvVrZOmihD6mt2SlMfhskr5FTduQ69kZG6DfLcve1PPDaIwnbOv3phb8=
bucket: code-angularjs-org-338b8.appspot.com
local-dir: upload
detect_encoding: true # detects gzip compression
on:
repo: angular/angular.js
all_branches: true
# upload the build when the commit is tagged or the branch is "master"
condition: $TRAVIS_TAG != '' || ($TRAVIS_PULL_REQUEST = false && $TRAVIS_BRANCH = master)
+50 -15
View File
@@ -1,3 +1,38 @@
<a name="1.6.6"></a>
# 1.6.6 interdimensional-cable (2017-08-18)
## Bug Fixes
- **$httpParamSerializer:** ignore functions
([b51ded](https://github.com/angular/angular.js/commit/b51ded67366865f36c5781dd5d9b801488ec95ea),
[#16133](https://github.com/angular/angular.js/issues/16133))
- **$resource:** do not throw when calling old `$cancelRequest()`
([009ebe](https://github.com/angular/angular.js/commit/009ebec64c81d11b280c635167050e8906e191c6),
[#16037](https://github.com/angular/angular.js/issues/16037))
- **$parse:**
- do not shallow-watch computed property keys
([750465](https://github.com/angular/angular.js/commit/7504656a26202de591e4ac9674333254304edf8a))
- support constants in computed keys
([9d6c3f](https://github.com/angular/angular.js/commit/9d6c3f3ec233279885e37a250d25860d5c15f716))
- **$http:** do not throw error if `Content-Type` is not `application/json` but response is JSON-like
([2e1163](https://github.com/angular/angular.js/commit/2e1163ef5cb56d1933e8ecd7b74020b9df9c6693),
[#16027](https://github.com/angular/angular.js/issues/16027),
[#16075](https://github.com/angular/angular.js/issues/16075))
## New Features
- **$compile:** add `strictComponentBindingsEnabled()` method
([3ec181](https://github.com/angular/angular.js/commit/3ec1819b913c8edf0649e06217dbd5920f29f126),
[#16129](https://github.com/angular/angular.js/issues/16129))
- **$resource:** add resource to response for error interceptors
([9256db](https://github.com/angular/angular.js/commit/9256dbc4201343ce5cd63a9eadf98da4793f45af),
[#16109](https://github.com/angular/angular.js/issues/16109))
- **$http:** allow differentiation between XHR completion, error, abort, timeout
([5e2bc5](https://github.com/angular/angular.js/commit/5e2bc5bbf347a9dfadc08b1514b8be06fd550913),
[#15924](https://github.com/angular/angular.js/issues/15924),
[#15847](https://github.com/angular/angular.js/issues/15847))
<a name="1.6.5"></a>
# 1.6.5 toffee-salinization (2017-07-03)
@@ -131,7 +166,7 @@
## Bug Fixes
- **Angular:**
- **AngularJS:**
- do not auto-bootstrap if the `src` exists but is empty
([3536e8](https://github.com/angular/angular.js/commit/3536e83d8a085b02bd6dcec8324800b7e6c734e4))
- do not auto bootstrap if the currentScript has been clobbered
@@ -700,10 +735,10 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
- **feat($compile): set preAssignBindingsEnabled to false by default
([bcd0d4](https://github.com/angular/angular.js/commit/bcd0d4d896d0dfdd988ff4f849c1d40366125858))**:
Previously, `$compileProvider.preAssignBindingsEnabled` was
set to true by default. This means bindings were pre-assigned in component
constructors. In Angular 1.5+ the place to put the initialization logic
relying on bindings being present is the controller `$onInit` method.
Previously, `$compileProvider.preAssignBindingsEnabled` was set to true by default. This means
bindings were pre-assigned on component/directive controller instances (which made them available
inside the constructors). In AngularJS 1.5+ the place to put the initialization logic relying on
bindings being present is the controller's `$onInit` method.
To migrate follow the example below:
@@ -1902,7 +1937,7 @@ validation), you can overwrite the built-in `step` validator with a custom direc
# 1.5.9 timeturning-lockdown (2016-11-24)
This is an interim release primarily to publish some security fixes, in particular a modification to
ensure that Angular 1 can pass the linter checks for Mozilla add-ons.
ensure that AngularJS can pass the linter checks for Mozilla add-ons.
## Security Fixes
- **bootstrap:**
@@ -1990,7 +2025,7 @@ ensure that Angular 1 can pass the linter checks for Mozilla add-ons.
Previously, `$compileProvider.preAssignBindingsEnabled` was
set to true by default. This means bindings were pre-assigned in component
constructors. In Angular 1.5+ the place to put the initialization logic
constructors. In AngularJS 1.5+ the place to put the initialization logic
relying on bindings being present is the controller `$onInit` method.
To migrate follow the example below:
@@ -5104,12 +5139,12 @@ before the $parsers are applied. Previously, the modelValue
This fixes issues where `input[date]` and `input[number]` cannot
be validated because the viewValue string is parsed into
`Date` and `Number` respectively (starting with Angular 1.3).
`Date` and `Number` respectively (starting with AngularJS 1.3).
It also brings the directives in line with HTML5 constraint
validation, which validates against the input value.
This change is unlikely to cause applications to fail, because even
in Angular 1.2, the value that was validated by pattern could have
in AngularJS 1.2, the value that was validated by pattern could have
been manipulated by the $parsers, as all validation was done
inside this pipeline.
@@ -5220,12 +5255,12 @@ before the $parsers are applied. Previously, the modelValue
This fixes issues where `input[date]` and `input[number]` cannot
be validated because the viewValue string is parsed into
`Date` and `Number` respectively (starting with Angular 1.3).
`Date` and `Number` respectively (starting with AngularJS 1.3).
It also brings the directives in line with HTML5 constraint
validation, which validates against the input value.
This change is unlikely to cause applications to fail, because even
in Angular 1.2, the value that was validated by pattern could have
in AngularJS 1.2, the value that was validated by pattern could have
been manipulated by the $parsers, as all validation was done
inside this pipeline.
@@ -5439,7 +5474,7 @@ describe('$q.when', function() {
it('should not need a call to $timeout.flush() to resolve already resolved promises',
inject(function($q, $timeout) {
$q.when('foo');
// In Angular 1.4.3 a call to `$timeout.flush();` was needed
// In AngularJS 1.4.3 a call to `$timeout.flush();` was needed
$timeout.verifyNoPendingTasks();
}));
@@ -6915,7 +6950,7 @@ it is now implemented in the ngOptions directive itself.
the `select` directive will now use strict comparison of the `ngModel` scope value against `option`
values to determine which option is selected. This means `Number` scope values will not be matched
against numeric option strings.
In Angular 1.3.x, setting `scope.x = 200` would select the `option` with the value 200 in the following `select`:
In AngularJS 1.3.x, setting `scope.x = 200` would select the `option` with the value 200 in the following `select`:
```
<select ng-model="x">
@@ -6924,7 +6959,7 @@ In Angular 1.3.x, setting `scope.x = 200` would select the `option` with the val
</select>
```
In Angular 1.4.x, the 'unknown option' will be selected.
In AngularJS 1.4.x, the 'unknown option' will be selected.
To remedy this, you can simply initialize the model as a string: `scope.x = '200'`, or if you want to
keep the model as a `Number`, you can do the conversion via `$formatters` and `$parsers` on `ngModel`:
@@ -15498,7 +15533,7 @@ with the `$route` service
### Breaking changes
- we now support ISO 8601 extended format datetime strings (YYYY-MM-DDTHH:mm:ss.SSSZ) as defined
in EcmaScript 5 throughout angular. This means that the following apis switched from
in EcmaScript 5 throughout AngularJS. This means that the following apis switched from
YYYY-MM-DDTHH:mm:ssZ to YYYY-MM-DDTHH:mm:ss.SSSZ (note the added millis) when representing dates:
- angular.Date.toString
- angular.String.fromDate
+71 -12
View File
@@ -49,7 +49,6 @@ if (!process.env.TRAVIS && !process.env.JENKINS_HOME) {
}
}
module.exports = function(grunt) {
// this loads all the node_modules that start with `grunt-` as plugins
@@ -64,6 +63,8 @@ module.exports = function(grunt) {
NG_VERSION.cdn = versionInfo.cdnVersion;
var dist = 'angular-' + NG_VERSION.full;
var deployVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.full;
if (versionInfo.cdnVersion == null) {
throw new Error('Unable to read CDN version, are you offline or has the CDN not been properly pushed?\n' +
'Perhaps you want to set the NG1_BUILD_NO_REMOTE_VERSION_REQUESTS environment variable?');
@@ -325,6 +326,15 @@ module.exports = function(grunt) {
expand: true,
dot: true,
dest: dist + '/'
},
firebaseCodeDeploy: {
options: {
mode: 'gzip'
},
src: ['**'],
cwd: 'build',
expand: true,
dest: 'upload/' + deployVersion + '/'
}
},
@@ -359,24 +369,73 @@ module.exports = function(grunt) {
});
//alias tasks
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['eslint', 'package', 'test:unit', 'test:promises-aplus', 'tests:docs', 'test:protractor']);
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', [
'eslint',
'package',
'test:unit',
'test:promises-aplus',
'tests:docs',
'test:protractor'
]);
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
grunt.registerTask('test:jquery', 'Run the jQuery (latest) unit tests with Karma', ['tests:jquery']);
grunt.registerTask('test:jquery-2.2', 'Run the jQuery 2.2 unit tests with Karma', ['tests:jquery-2.2']);
grunt.registerTask('test:jquery-2.1', 'Run the jQuery 2.1 unit tests with Karma', ['tests:jquery-2.1']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', [
'build',
'tests:modules'
]);
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:jquery-2.2', 'test:jquery-2.1', 'test:modules']);
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']);
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', [
'test:jqlite',
'test:jquery',
'test:jquery-2.2',
'test:jquery-2.1',
'test:modules'
]);
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', [
'webdriver',
'connect:testserver',
'protractor:normal'
]);
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', [
'connect:testserver',
'protractor:travis'
]);
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', [
'webdriver',
'connect:testserver',
'protractor:jenkins'
]);
grunt.registerTask('test:e2e', 'Alias for test:protractor', ['test:protractor']);
grunt.registerTask('test:promises-aplus',['build:promises-aplus-adapter', 'shell:promises-aplus-tests']);
grunt.registerTask('minify', ['bower', 'clean', 'build', 'minall']);
grunt.registerTask('test:promises-aplus',[
'build:promises-aplus-adapter',
'shell:promises-aplus-tests'
]);
grunt.registerTask('minify', [
'bower',
'clean',
'build',
'minall'
]);
grunt.registerTask('webserver', ['connect:devserver']);
grunt.registerTask('package', ['bower', 'validate-angular-files', 'clean', 'buildall', 'minall', 'collect-errors', 'write', 'docs', 'copy', 'compress']);
grunt.registerTask('ci-checks', ['ddescribe-iit', 'merge-conflict', 'eslint']);
grunt.registerTask('package', [
'bower',
'validate-angular-files',
'clean',
'buildall',
'minall',
'collect-errors',
'write',
'docs',
'copy',
'compress:build'
]);
grunt.registerTask('ci-checks', [
'ddescribe-iit',
'merge-conflict',
'eslint'
]);
grunt.registerTask('default', ['package']);
};
+34
View File
@@ -1,3 +1,37 @@
@font-face {
font-family: 'Open Sans';
src: url("../components/open-sans-fontface-1.4.0/fonts/Regular/OpenSans-Regular.eot?v=1.1.0");
src: url("../components/open-sans-fontface-1.4.0/fonts/Regular/OpenSans-Regular.eot?#iefix&v=1.1.0") format("embedded-opentype"),
url("../components/open-sans-fontface-1.4.0/fonts/Regular/OpenSans-Regular.woff?v=1.1.0") format("woff"),
url("../components/open-sans-fontface-1.4.0/fonts/Regular/OpenSans-Regular.ttf?v=1.1.0") format("truetype"),
url("../components/open-sans-fontface-1.4.0/fonts/Regular/OpenSans-Regular.svg?v=1.1.0#OpenSansBold") format("svg");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src: url("../components/open-sans-fontface-1.4.0/fonts/Semibold/OpenSans-Semibold.eot?v=1.1.0");
src: url("../components/open-sans-fontface-1.4.0/fonts/Semibold/OpenSans-Semibold.eot?#iefix&v=1.1.0") format("embedded-opentype"),
url("../components/open-sans-fontface-1.4.0/fonts/Semibold/OpenSans-Semibold.woff?v=1.1.0") format("woff"),
url("../components/open-sans-fontface-1.4.0/fonts/Semibold/OpenSans-Semibold.ttf?v=1.1.0") format("truetype"),
url("../components/open-sans-fontface-1.4.0/fonts/Semibold/OpenSans-Semibold.svg?v=1.1.0#OpenSansBold") format("svg");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src: url("../components/open-sans-fontface-1.4.0/fonts/Bold/OpenSans-Bold.eot?v=1.1.0");
src: url("../components/open-sans-fontface-1.4.0/fonts/Bold/OpenSans-Bold.eot?#iefix&v=1.1.0") format("embedded-opentype"),
url("../components/open-sans-fontface-1.4.0/fonts/Bold/OpenSans-Bold.woff?v=1.1.0") format("woff"),
url("../components/open-sans-fontface-1.4.0/fonts/Bold/OpenSans-Bold.ttf?v=1.1.0") format("truetype"),
url("../components/open-sans-fontface-1.4.0/fonts/Bold/OpenSans-Bold.svg?v=1.1.0#OpenSansBold") format("svg");
font-weight: bold;
font-style: normal;
}
html, body {
position: relative;
height: 100%;
+1
View File
@@ -14,6 +14,7 @@ angular.module('docsApp', [
'search',
'tutorials',
'versions',
'deployment',
'ui.bootstrap.dropdown'
])
+7 -1
View File
@@ -18,7 +18,13 @@ angular.module('examples', [])
return {
restrict: 'C',
scope : true,
controller : ['$scope', function($scope) {
controller : ['$scope', 'DEPLOYMENT', function($scope, DEPLOYMENT) {
var exampleIndexFile = (DEPLOYMENT === 'default' ? 'index' : 'index-' + DEPLOYMENT) + '.html';
$scope.getExampleIndex = function(basePath) {
return basePath + '/' + exampleIndexFile;
};
$scope.setTab = function(index) {
var tab = $scope.tabs[index];
$scope.activeTabIndex = index;
+11 -1
View File
@@ -112,6 +112,10 @@ module.exports = new Package('angularjs', [
docTypes: ['indexPage'],
pathTemplate: '.',
outputPathTemplate: '${id}.html'
}, {
docTypes: ['deploymentData'],
pathTemplate: '.',
outputPathTemplate: 'js/${id}.js'
});
computePathsProcessor.pathTemplates.push({
@@ -125,8 +129,14 @@ module.exports = new Package('angularjs', [
outputPathTemplate: 'partials/${area}/${moduleName}/${groupType}.html'
});
computePathsProcessor.pathTemplates.push({
docTypes: ['example'],
pathTemplate: 'examples/${example.id}',
outputPathTemplate: 'examples/${example.id}/index${deploymentQualifier}.html'
});
computeIdsProcessor.idTemplates.push({
docTypes: ['overview', 'tutorial', 'e2e-test', 'indexPage'],
docTypes: ['overview', 'tutorial', 'e2e-test', 'indexPage', 'deploymentData'],
getId: function(doc) { return doc.fileInfo.baseName; },
getAliases: function(doc) { return [doc.id]; }
});
+10
View File
@@ -35,7 +35,17 @@ module.exports = function generateIndexPagesProcessor() {
indexDoc.id = 'index' + (deployment.name === 'default' ? '' : '-' + deployment.name);
var deploymentDoc = {
docType: 'deploymentData',
id: 'deployment-data-' + deployment.name,
template: 'angular-service.template.js',
ngModuleName: 'deployment',
serviceName: 'DEPLOYMENT',
serviceValue: deployment.name
};
docs.push(indexDoc);
docs.push(deploymentDoc);
});
}
};
+9
View File
@@ -47,6 +47,14 @@ module.exports = function generateVersionDocProcessor(gitData) {
var latestMap = {};
// When the docs are built on a tagged commit, yarn info won't include the latest release,
// so we add it manually based on the local version.json file.
var missesCurrentVersion = !currentVersion.isSnapshot && !versions.find(function(version) {
return version === currentVersion.version;
});
if (missesCurrentVersion) versions.push(currentVersion.version);
versions = versions
.filter(function(versionStr) {
return blacklist.indexOf(versionStr) === -1;
@@ -70,6 +78,7 @@ module.exports = function generateVersionDocProcessor(gitData) {
})
.reverse();
// List the latest version for each branch
var latest = sortObject(latestMap, reverse(semver.compare))
.map(function(version) { return makeOption(version, 'Latest'); });
+1 -1
View File
@@ -26,11 +26,11 @@ module.exports = function debugDeployment(getVersion) {
'js/all-versions-data.js',
'js/pages-data.js',
'js/nav-data.js',
'js/deployment-data-debug.js',
'js/docs.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.css',
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
'css/prettify-theme.css',
'css/angular-topnav.css',
'css/docs.css',
+2 -2
View File
@@ -17,7 +17,7 @@ module.exports = function defaultDeployment(getVersion) {
'../angular-sanitize.min.js',
'../angular-touch.min.js',
'../angular-animate.min.js',
'components/marked-' + getVersion('marked') + '/lib/marked.js',
'components/marked-' + getVersion('marked') + '/marked.min.js',
'js/angular-bootstrap/dropdown-toggle.min.js',
'components/lunr-' + getVersion('lunr') + '/lunr.min.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
@@ -26,11 +26,11 @@ module.exports = function defaultDeployment(getVersion) {
'js/all-versions-data.js',
'js/pages-data.js',
'js/nav-data.js',
'js/deployment-data-default.js',
'js/docs.min.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
'css/prettify-theme.css',
'css/angular-topnav.css',
'css/docs.css',
+1 -1
View File
@@ -30,11 +30,11 @@ module.exports = function jqueryDeployment(getVersion) {
'js/all-versions-data.js',
'js/pages-data.js',
'js/nav-data.js',
'js/deployment-data-jquery.js',
'js/docs.min.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
'css/prettify-theme.css',
'css/angular-topnav.css',
'css/docs.css',
@@ -34,7 +34,7 @@ module.exports = function productionDeployment(getVersion) {
cdnUrl + '/angular-sanitize.min.js',
cdnUrl + '/angular-touch.min.js',
cdnUrl + '/angular-animate.min.js',
'components/marked-' + getVersion('marked') + '/lib/marked.js',
'components/marked-' + getVersion('marked') + '/marked.min.js',
'js/angular-bootstrap/dropdown-toggle.min.js',
'components/lunr-' + getVersion('lunr') + '/lunr.min.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
@@ -43,11 +43,11 @@ module.exports = function productionDeployment(getVersion) {
'https://code.angularjs.org/snapshot/docs/js/all-versions-data.js',
'js/pages-data.js',
'js/nav-data.js',
'js/deployment-data-production.js',
'js/docs.min.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
'components/open-sans-fontface-' + getVersion('open-sans-fontface') + '/open-sans.css',
'css/prettify-theme.css',
'css/angular-topnav.css',
'css/docs.css',
@@ -18,7 +18,7 @@
</div>
{% endfor %}
<iframe class="runnable-example-frame" src="{$ doc.example.deployments.default.outputPath $}" name="{$ doc.example.id $}"></iframe>
<iframe class="runnable-example-frame" ng-src="{{getExampleIndex('{$ doc.example.deployments.default.path $}')}}" name="{$ doc.example.id $}"></iframe>
</div>
</div>
@@ -0,0 +1,8 @@
@ngdoc error
@name $compile:missingattr
@fullName Missing required attribute
@description
This error may occur only when `$compileProvider.strictComponentBindingsEnabled` is set to `true`.
Then all attributes mentioned in `bindings` without `?` must be set. If one or more aren't set,
the first one will throw an error.
+5 -14
View File
@@ -186,7 +186,7 @@ Right now, the `InvoiceController` contains all logic of our example. When the a
is a good practice to move view-independent logic from the controller into a
<a name="service">{@link services service}</a>, so it can be reused by other parts
of the application as well. Later on, we could also change that service to load the exchange rates
from the web, e.g. by calling the Yahoo Finance API, without changing the controller.
from the web, e.g. by calling the [Fixer.io](http://fixer.io) exchange rate API, without changing the controller.
Let's refactor our example and move the currency conversion into a service in another file:
@@ -300,7 +300,7 @@ to something shorter like `a`.
## Accessing the backend
Let's finish our example by fetching the exchange rates from the Yahoo Finance API.
Let's finish our example by fetching the exchange rates from the [Fixer.io](http://fixer.io) exchange rate API.
The following example shows how this is done with Angular:
<example name="guide-concepts-3" ng-app-included="true">
@@ -323,10 +323,6 @@ The following example shows how this is done with Angular:
<file name="finance3.js">
angular.module('finance3', [])
.factory('currencyConverter', ['$http', function($http) {
var YAHOO_FINANCE_URL_PATTERN =
'//query.yahooapis.com/v1/public/yql?q=select * from ' +
'yahoo.finance.xchange where pair in ("PAIRS")&format=json&' +
'env=store://datatables.org/alltableswithkeys';
var currencies = ['USD', 'EUR', 'CNY'];
var usdToForeignRates = {};
@@ -335,15 +331,10 @@ The following example shows how this is done with Angular:
};
var refresh = function() {
var url = YAHOO_FINANCE_URL_PATTERN.
replace('PAIRS', 'USD' + currencies.join('","USD'));
var url = 'https://api.fixer.io/latest?base=USD&symbols=' + currencies.join(",");
return $http.get(url).then(function(response) {
var newUsdToForeignRates = {};
angular.forEach(response.data.query.results.rate, function(rate) {
var currency = rate.id.substring(3,6);
newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);
});
usdToForeignRates = newUsdToForeignRates;
usdToForeignRates = response.data.rates;
usdToForeignRates['USD'] = 1;
});
};
+10 -10
View File
@@ -75,8 +75,8 @@ commits for more info.
- **$location** now uses `'!'` as the default hash-prefix for hash-bang URLs, instead of the empty
string. ([Details](guide/migration#commit-aa077e8))
- **$compile** will (by default) not pre-assign bindings on controller instances.
([Details](guide/migration#commit-bcd0d4))
- **$compile** will (by default) not pre-assign bindings on component/directive controller
instances. ([Details](guide/migration#commit-bcd0d4))
- **http** imposes additional restrictions to **JSONP** requests for security reasons
(see [details](guide/migration#migrate1.5to1.6-ng-services-$http) below):
@@ -412,14 +412,14 @@ if the option does not provide a value attribute.
<major />
<a name="commit-bcd0d4"></a>
**Due to [bcd0d4](https://github.com/angular/angular.js/commit/bcd0d4d896d0dfdd988ff4f849c1d40366125858)**,
pre-assigning bindings on controller instances is disabled by default. It is still possible to turn
it back on, which should help during the migration. Pre-assigning bindings has been deprecated and
will be removed in a future version, so we strongly recommend migrating your applications to not
rely on it as soon as possible.
pre-assigning bindings on component/directive controller instances is disabled by default, which
means that they will no longer be available inside the constructors. It is still possible to turn it
back on, which should help during the migration. Pre-assigning bindings has been deprecated and will
be removed in a future version, so we strongly recommend migrating your applications to not rely on
it as soon as possible.
Initialization logic that relies on bindings being present should be put in the controller's
`$onInit()` method, which is guaranteed to always be called _after_ the bindings have been
assigned.
`$onInit()` method, which is guaranteed to always be called _after_ the bindings have been assigned.
Before:
@@ -1684,12 +1684,12 @@ before the $parsers are applied. Previously, the `$modelValue`
This fixes issues where `input[date]` and `input[number]` cannot
be validated because the `$viewValue` string is parsed into
`Date` and `Number` respectively (starting with Angular 1.3).
`Date` and `Number` respectively (starting with AngularJS 1.3).
It also brings the directives in line with HTML5 constraint
validation, which validates against the input value.
This change is unlikely to cause applications to fail, because even
in Angular 1.2, the value that was validated by pattern could have
in AngularJS 1.2, the value that was validated by pattern could have
been manipulated by the $parsers, as all validation was done
inside this pipeline.
+1 -2
View File
@@ -100,8 +100,7 @@ Protection from JSON Hijacking is provided if the server prefixes all JSON reque
Angular will automatically strip the prefix before processing it as JSON.
For more information please visit {@link $http#json-vulnerability-protection JSON Hijacking Protection}.
Bear in mind that calling `$http.jsonp`, like in [our Yahoo! finance example](https://docs.angularjs.org/guide/concepts#accessing-the-backend),
gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
Bear in mind that calling `$http.jsonp` gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
instant remote code execution in your application: the result of these requests is handed off
to the browser as regular `<script>` tag.
+11 -8
View File
@@ -52,13 +52,14 @@ var getMergedEslintConfig = function(filepath) {
};
};
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
var copyComponent = function(component, pattern, base, sourceFolder, packageFile) {
pattern = pattern || '/**/*';
base = base || '';
sourceFolder = sourceFolder || '../node_modules';
packageFile = packageFile || 'package.json';
var version = require(path.resolve(sourceFolder, component, packageFile)).version;
return gulp
.src(sourceFolder + '/' + component + pattern)
.src(sourceFolder + '/' + component + pattern, {base: sourceFolder + '/' + component + '/' + base})
.pipe(gulp.dest(outputFolder + '/components/' + component + '-' + version));
};
@@ -96,12 +97,14 @@ gulp.task('assets', function() {
.pipe(gulp.dest(outputFolder));
}
})),
copyComponent('bootstrap', '/dist/**/*'),
copyComponent('open-sans-fontface'),
copyComponent('lunr', '/*.js'),
copyComponent('google-code-prettify'),
copyComponent('jquery', '/dist/*.js'),
copyComponent('marked', '/**/*.js')
copyComponent('bootstrap', '/dist/css/bootstrap?(.min).css', 'dist'),
copyComponent('bootstrap', '/dist/fonts/*', 'dist'),
copyComponent('open-sans-fontface', '/fonts/{Regular,Semibold,Bold}/*'),
copyComponent('lunr', '/lunr?(.min).js'),
copyComponent('google-code-prettify', '/**/{lang-css,prettify}.js'),
copyComponent('jquery', '/dist/jquery.js', 'dist'),
copyComponent('marked', '/lib/marked.js'),
copyComponent('marked', '/marked.min.js')
);
});
+24
View File
@@ -0,0 +1,24 @@
{
"hosting": {
"public": "build/docs",
"ignore": [
"/index.html",
"/index-debug.html",
"/index-jquery.html"
],
"rewrites": [
{
"source": "/",
"destination": "/index-production.html"
},
{
"source": "/index.html",
"destination": "/index-production.html"
},
{
"source": "**/*!(.jpg|.jpeg|.gif|.png|.html|.js|.json|.css|.svg|.ttf|.woff|.woff2|.eot)",
"destination": "/index-production.html"
}
]
}
}
+25 -4
View File
@@ -10,8 +10,16 @@ module.exports = function(config, specificOptions) {
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 2,
browserNoActivityTimeout: 30000,
reporters: ['dots'],
specReporter: {
maxLogLines: 5, // limit number of lines logged per test
suppressErrorSummary: true, // do not print error summary
suppressFailed: false, // do not print information about failed tests
suppressPassed: true, // do not print information about passed tests
suppressSkipped: false, // do not print information about skipped tests
showSpecTiming: false, // print the time elapsed for each spec
failFast: false // test would finish with error when a first fail occurs.
},
// SauceLabs config for local development.
sauceLabs: {
testName: specificOptions.testName || 'AngularJS',
@@ -37,12 +45,12 @@ module.exports = function(config, specificOptions) {
'SL_Chrome': {
base: 'SauceLabs',
browserName: 'chrome',
version: '51'
version: '59'
},
'SL_Firefox': {
base: 'SauceLabs',
browserName: 'firefox',
version: '47'
version: '54'
},
'SL_Safari_8': {
base: 'SauceLabs',
@@ -74,6 +82,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 +134,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': {
base: 'BrowserStack',
device: 'iPhone 6S',
+7 -1
View File
@@ -30,6 +30,7 @@
"changez-angular": "^2.1.2",
"cheerio": "^0.17.0",
"commitizen": "^2.3.0",
"commitplease": "^2.7.10",
"cross-spawn": "^4.0.0",
"cz-conventional-changelog": "1.1.4",
"dgeni": "^0.4.0",
@@ -69,6 +70,7 @@
"karma-ng-scenario": "^1.0.0",
"karma-sauce-launcher": "^1.1.0",
"karma-script-launcher": "^1.0.0",
"karma-spec-reporter": "^0.0.31",
"load-grunt-tasks": "^3.5.0",
"lodash": "~2.4.1",
"log4js": "^0.6.27",
@@ -94,9 +96,13 @@
"stringmap": "^0.2.2"
},
"dependencies": {},
"commitplease": {
"style": "angular",
"nohook": true
},
"config": {
"commitizen": {
"path": "node_modules/cz-conventional-changelog"
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
Firebase for docs.angularjs.org
===============================
The docs are deployed to Google Firebase hosting via Travis deployment config, which expects
firebase.json and .firebaserc in the repository root.
See travis.yml for the complete deployment config.
See /scripts/code.angularjs.org-firebase/readme.firebase.code.md for the firebase deployment to
code.angularjs.org
@@ -0,0 +1,5 @@
{
"env": {
"es6": true
}
}
@@ -0,0 +1,5 @@
{
"projects": {
"default": "code-angularjs-org-338b8"
}
}
@@ -0,0 +1,21 @@
{
"hosting": {
"public": "public",
"redirects": [
{
"source": "/:version/docs",
"destination": "/:version/docs/index.html",
"type": 301
}
],
"rewrites": [
{
"source": "/**",
"function": "sendStoredFile"
}
]
},
"storage": {
"rules": "storage.rules"
}
}
@@ -0,0 +1,208 @@
'use strict';
const functions = require('firebase-functions');
const gcs = require('@google-cloud/storage')();
const path = require('path');
const gcsBucketId = `${process.env.GCLOUD_PROJECT}.appspot.com`;
const BROWSER_CACHE_DURATION = 300;
const CDN_CACHE_DURATION = 600;
function sendStoredFile(request, response) {
let filePathSegments = request.path.split('/').filter((segment) => {
// Remove empty leading or trailing path parts
return segment !== '';
});
const version = filePathSegments[0];
const isDocsPath = filePathSegments[1] === 'docs';
const lastSegment = filePathSegments[filePathSegments.length - 1];
const bucket = gcs.bucket(gcsBucketId);
let downloadSource;
let fileName;
if (isDocsPath && filePathSegments.length === 2) {
fileName = 'index.html';
filePathSegments = [version, 'docs', fileName];
} else {
fileName = lastSegment;
}
if (!fileName) {
//Root
return getDirectoryListing('/').catch(sendErrorResponse);
}
downloadSource = path.join.apply(null, filePathSegments);
downloadAndSend(downloadSource).catch(error => {
if (isDocsPath && error.code === 404) {
fileName = 'index.html';
filePathSegments = [version, 'docs', fileName];
downloadSource = path.join.apply(null, filePathSegments);
return downloadAndSend(downloadSource);
}
return Promise.reject(error);
}).catch(error => {
// If file not found, try the path as a directory
return error.code === 404 ? getDirectoryListing(request.path.slice(1)) : Promise.reject(error);
}).catch(sendErrorResponse);
function downloadAndSend(downloadSource) {
const file = bucket.file(downloadSource);
return file.getMetadata().then(data => {
return new Promise((resolve, reject) => {
const readStream = file.createReadStream()
.on('error', error => {
reject(error);
})
.on('response', () => {
resolve(response);
});
response
.status(200)
.set({
'Content-Type': data[0].contentType,
'Cache-Control': `public, max-age=${BROWSER_CACHE_DURATION}, s-maxage=${CDN_CACHE_DURATION}`
});
readStream.pipe(response);
});
});
}
function sendErrorResponse(error) {
let code = 500;
let message = `General error. Please try again later.
If the error persists, please create an issue in the
<a href="https://github.com/angular/angular.js/issues">AngularJS Github repository</a>`;
if (error.code === 404) {
message = 'File or directory not found';
code = 404;
}
return response.status(code).send(message);
}
function getDirectoryListing(path) {
if (!path.endsWith('/')) path += '/';
const getFilesOptions = {
delimiter: '/',
autoPaginate: false
};
if (path !== '/') getFilesOptions.prefix = path;
let fileList = [];
let directoryList = [];
return getContent(getFilesOptions).then(() => {
let contentList = '';
directoryList.forEach(directoryPath => {
const dirName = directoryPath.split('/').reverse()[1];
contentList += `<a href="${dirName}/">${dirName}/</a><br>`;
});
fileList.forEach(file => {
const fileName = file.metadata.name.split('/').pop();
contentList += `<a href="${fileName}">${fileName}</a><br>`;
});
// A trailing slash in the base creates correct relative links when the url is accessed
// without trailing slash
const base = request.originalUrl.endsWith('/') ? request.originalUrl : request.originalUrl + '/';
let directoryListing = `
<base href="${base}">
<h1>Index of ${path}</h1>
<hr>
<pre>${contentList}</pre>`;
return response
.status(200)
.set({
'Cache-Control': `public, max-age=${BROWSER_CACHE_DURATION}, s-maxage=${CDN_CACHE_DURATION}`
})
.send(directoryListing);
});
function getContent(options) {
return bucket.getFiles(options).then(data => {
const files = data[0];
const nextQuery = data[1];
const apiResponse = data[2];
if (!files.length && (!apiResponse || !apiResponse.prefixes)) {
return Promise.reject({
code: 404
});
}
fileList = fileList.concat(files);
if (apiResponse && apiResponse.prefixes) {
directoryList = directoryList.concat(apiResponse.prefixes);
}
if (nextQuery) {
// If the results are paged, get the next page
return getContent(nextQuery);
}
return true;
});
}
}
}
function deleteOldSnapshotZip(event) {
const object = event.data;
const bucketId = object.bucket;
const filePath = object.name;
const contentType = object.contentType;
const resourceState = object.resourceState;
const bucket = gcs.bucket(bucketId);
if (contentType !== 'application/zip' ||
!filePath.startsWith('snapshot/') ||
resourceState === 'not_exists' // Deletion event
) {
return;
}
bucket.getFiles({
prefix: 'snapshot/',
delimiter: '/',
autoPaginate: false
}).then(function(data) {
const files = data[0];
const oldZipFiles = files.filter(file => {
return file.metadata.name !== filePath && file.metadata.contentType === 'application/zip';
});
oldZipFiles.forEach(function(file) {
file.delete();
});
});
}
exports.sendStoredFile = functions.https.onRequest(sendStoredFile);
exports.deleteOldSnapshotZip = functions.storage.object().onChange(deleteOldSnapshotZip);
@@ -0,0 +1,10 @@
{
"name": "functions-firebase-code.angularjs.org",
"description": "Cloud Functions to serve files from gcs to code.angularjs.org",
"dependencies": {
"@google-cloud/storage": "^1.1.1",
"firebase-admin": "^4.2.1",
"firebase-functions": "^0.5.9"
},
"private": true
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
google-site-verification: googleb96cceae5888d79f.html
@@ -0,0 +1,5 @@
User-agent: *
Disallow: /*docs/
Disallow: /*i18n/
Disallow: /*.zip$
@@ -0,0 +1,12 @@
Firebase for code.angularjs.org
===============================
This folder contains the Google Firebase scripts for the code.angularjs.org setup.
firebase.json contains the rewrite rules that route every subdirectory request to the cloud function
in functions/index.js that serves the docs from the Firebase Google Cloud Storage bucket.
The deployment to the Google Cloud Storage bucket happens automatically via Travis. See the travis.yml
file in the repository root.
See /readme.firebase.docs.md for the firebase deployment to docs.angularjs.org
@@ -0,0 +1,7 @@
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth!=null;
}
}
}
+3 -14
View File
@@ -59,23 +59,12 @@ function _update_code() {
echo "-- Pushing code.angularjs.org"
git push origin master
for backend in "$@" ; do
echo "-- Refreshing code.angularjs.org: backend=$backend"
# FIXME: We gave up publishing to code.angularjs.org because the GCE automatically removes firewall
# rules that allow access to port 8003.
# curl http://$backend:8003/gitFetchSite.php
done
}
function publish {
# The TXT record for backends.angularjs.org is a CSV of the IP addresses for
# the currently serving Compute Engine backends.
# code.angularjs.org is served out of port 8003 on these backends.
backends=("$(dig backends.angularjs.org +short TXT | python -c 'print raw_input()[1:-1].replace(",", "\n")')")
_update_code ${backends[@]}
# publish updates the code.angularjs.org Github repository
# the deployment to Firebase happens via Travis
_update_code
}
source $(dirname $0)/../utils.inc
+17 -4
View File
@@ -4,15 +4,28 @@ set -e
yarn global add grunt-cli@1.2.0
mkdir -p $LOGS_DIR
mkdir -p "$LOGS_DIR"
if [ $JOB != "ci-checks" ]; then
if [ "$JOB" != "ci-checks" ]; then
echo "start_browser_provider"
./scripts/travis/start_browser_provider.sh
fi
if [ $JOB != "ci-checks" ]; then
# ci-checks and unit tests do not run against the packaged code
if [ "$JOB" != "ci-checks" ] && [ "$JOB" != "unit" ]; then
grunt package
fi
# unit runs the docs tests too which need a built version of the code
if [ "$JOB" = "unit" ]; then
grunt bower
grunt validate-angular-files
grunt build
fi
# check this after the package, because at this point the browser_provider
# has probably arrived
if [ "$JOB" != "ci-checks" ]; then
echo "wait_for_browser_provider"
./scripts/travis/wait_for_browser_provider.sh
fi
fi
+50 -26
View File
@@ -5,32 +5,56 @@ set -e
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
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"
else
BROWSERS="SL_Chrome,SL_Firefox,SL_Safari_8,SL_Safari_9,SL_IE_9,SL_IE_10,SL_IE_11,SL_iOS"
fi
case "$JOB" in
"ci-checks")
grunt ci-checks
grunt test:promises-aplus
grunt test:unit --browsers="$BROWSERS" --reporters=dots
grunt tests:docs --browsers="$BROWSERS" --reporters=dots
elif [ "$JOB" == "docs-e2e" ]; then
grunt test:travis-protractor --specs="docs/app/e2e/**/*.scenario.js"
elif [ "$JOB" == "e2e" ]; then
if [[ $TEST_TARGET == jquery* ]]; then
export USE_JQUERY=1
fi
if [[ $TRAVIS_PULL_REQUEST != 'false' ]]; then
# validate commit messages of all commits in the PR
# convert commit range to 2 dots, as commitplease uses `git log`.
# See https://github.com/travis-ci/travis-ci/issues/4596 for more info
echo "Validate commit messages in PR:"
yarn run commitplease -- "${TRAVIS_COMMIT_RANGE/.../..}"
fi
;;
"unit")
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_EDGE,BS_iOS"
else
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
export TARGET_SPECS="build/docs/ptore2e/**/default_test.js"
if [[ "$TEST_TARGET" == jquery* ]]; then
TARGET_SPECS="build/docs/ptore2e/**/jquery_test.js"
fi
grunt test:promises-aplus
grunt test:unit --browsers="$BROWSERS" --reporters=spec
grunt tests:docs --browsers="$BROWSERS" --reporters=spec
;;
"docs-e2e")
grunt test:travis-protractor --specs="docs/app/e2e/**/*.scenario.js"
;;
"e2e")
if [[ $TEST_TARGET == jquery* ]]; then
export USE_JQUERY=1
fi
export TARGET_SPECS="test/e2e/tests/**/*.js,$TARGET_SPECS"
grunt test:travis-protractor --specs="$TARGET_SPECS"
else
echo "Unknown job type. Please set JOB=ci-checks, JOB=unit or JOB=e2e-*."
fi
export TARGET_SPECS="build/docs/ptore2e/**/default_test.js"
if [[ "$TEST_TARGET" == jquery* ]]; then
TARGET_SPECS="build/docs/ptore2e/**/jquery_test.js"
fi
export TARGET_SPECS="test/e2e/tests/**/*.js,$TARGET_SPECS"
grunt test:travis-protractor --specs="$TARGET_SPECS"
;;
"deploy")
# we never deploy on Pull requests, so it's safe to skip the build here
if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then
grunt package
grunt compress:firebaseCodeDeploy
else
echo "Skipping build because Travis has been triggered by Pull Request"
fi
;;
*)
echo "Unknown job type. Please set JOB=ci-checks, JOB=unit, JOB=deploy or JOB=e2e-*."
;;
esac
+40
View File
@@ -1414,6 +1414,31 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return preAssignBindingsEnabled;
};
/**
* @ngdoc method
* @name $compileProvider#strictComponentBindingsEnabled
*
* @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, otherwise just return the
* current strictComponentBindingsEnabled state
* @returns {*} current value if used as getter or itself (chaining) if used as setter
*
* @kind function
*
* @description
* Call this method to enable/disable strict component bindings check. If enabled, the compiler will enforce that
* for all bindings of a component that are not set as optional with `?`, an attribute needs to be provided
* on the component's HTML tag.
*
* The default value is false.
*/
var strictComponentBindingsEnabled = false;
this.strictComponentBindingsEnabled = function(enabled) {
if (isDefined(enabled)) {
strictComponentBindingsEnabled = enabled;
return this;
}
return strictComponentBindingsEnabled;
};
var TTL = 10;
/**
@@ -3441,12 +3466,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}
function strictBindingsCheck(attrName, directiveName) {
if (strictComponentBindingsEnabled) {
throw $compileMinErr('missingattr',
'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!',
attrName, directiveName);
}
}
// Set up $watches for isolate scope and controller bindings.
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
var removeWatchCollection = [];
var initialChanges = {};
var changes;
forEach(bindings, function initializeBinding(definition, scopeName) {
var attrName = definition.attrName,
optional = definition.optional,
@@ -3458,7 +3491,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
case '@':
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
strictBindingsCheck(attrName, directive.name);
destination[scopeName] = attrs[attrName] = undefined;
}
removeWatch = attrs.$observe(attrName, function(value) {
if (isString(value) || isBoolean(value)) {
@@ -3485,6 +3520,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
case '=':
if (!hasOwnProperty.call(attrs, attrName)) {
if (optional) break;
strictBindingsCheck(attrName, directive.name);
attrs[attrName] = undefined;
}
if (optional && !attrs[attrName]) break;
@@ -3529,6 +3565,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
case '<':
if (!hasOwnProperty.call(attrs, attrName)) {
if (optional) break;
strictBindingsCheck(attrName, directive.name);
attrs[attrName] = undefined;
}
if (optional && !attrs[attrName]) break;
@@ -3554,6 +3591,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
break;
case '&':
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
strictBindingsCheck(attrName, directive.name);
}
// Don't assign Object.prototype method to scope
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
+8 -3
View File
@@ -317,15 +317,20 @@
*
* ## A note about browser compatibility
*
* Edge, Firefox, and Internet Explorer do not support the `details` element, it is
* Internet Explorer and Edge do not support the `details` element, it is
* recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead.
*
* @example
<example name="ng-open">
<file name="index.html">
<label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/>
<label>Toggle details: <input type="checkbox" ng-model="open"></label><br/>
<details id="details" ng-open="open">
<summary>Show/Hide me</summary>
<summary>List</summary>
<ul>
<li>Apple</li>
<li>Orange</li>
<li>Durian</li>
</ul>
</details>
</file>
<file name="protractor.js" type="protractor">
+3 -1
View File
@@ -215,7 +215,9 @@
* more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
* mapped to the same DOM element, which is not possible.)
*
* Note that the tracking expression must come last, after any filters, and the alias expression.
* <div class="alert alert-warning">
* <strong>Note:</strong> the `track by` expression must come last - after any filters, and the alias expression.
* </div>
*
* For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
* will be associated by item identity in the array.
+17 -10
View File
@@ -41,7 +41,7 @@ function $HttpParamSerializerProvider() {
if (!params) return '';
var parts = [];
forEachSorted(params, function(value, key) {
if (value === null || isUndefined(value)) return;
if (value === null || isUndefined(value) || isFunction(value)) return;
if (isArray(value)) {
forEach(value, function(v) {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
@@ -137,10 +137,15 @@ function defaultHttpResponseTransform(data, headers) {
if (tempData) {
var contentType = headers('Content-Type');
if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
var hasJsonContentType = contentType && (contentType.indexOf(APPLICATION_JSON) === 0);
if (hasJsonContentType || isJsonLike(tempData)) {
try {
data = fromJson(tempData);
} catch (e) {
if (!hasJsonContentType) {
return data;
}
throw $httpMinErr('baddata', 'Data must be a valid JSON object. Received: "{0}". ' +
'Parse error: "{1}"', data, e);
}
@@ -453,6 +458,7 @@ function $HttpProvider() {
* - **headers** `{function([headerName])}` Header getter function.
* - **config** `{Object}` The configuration object that was used to generate the request.
* - **statusText** `{string}` HTTP status text of the response.
* - **xhrStatus** `{string}` Status of the XMLHttpRequest (`complete`, `error`, `timeout` or `abort`).
*
* A response status code between 200 and 299 is considered a success status and will result in
* the success callback being called. Any response status code outside of that range is
@@ -1294,9 +1300,9 @@ function $HttpProvider() {
} else {
// serving from cache
if (isArray(cachedResp)) {
resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3], cachedResp[4]);
} else {
resolvePromise(cachedResp, 200, {}, 'OK');
resolvePromise(cachedResp, 200, {}, 'OK', 'complete');
}
}
} else {
@@ -1353,10 +1359,10 @@ function $HttpProvider() {
* - resolves the raw $http promise
* - calls $apply
*/
function done(status, response, headersString, statusText) {
function done(status, response, headersString, statusText, xhrStatus) {
if (cache) {
if (isSuccess(status)) {
cache.put(url, [status, response, parseHeaders(headersString), statusText]);
cache.put(url, [status, response, parseHeaders(headersString), statusText, xhrStatus]);
} else {
// remove promise from the cache
cache.remove(url);
@@ -1364,7 +1370,7 @@ function $HttpProvider() {
}
function resolveHttpPromise() {
resolvePromise(response, status, headersString, statusText);
resolvePromise(response, status, headersString, statusText, xhrStatus);
}
if (useApplyAsync) {
@@ -1379,7 +1385,7 @@ function $HttpProvider() {
/**
* Resolves the raw $http promise.
*/
function resolvePromise(response, status, headers, statusText) {
function resolvePromise(response, status, headers, statusText, xhrStatus) {
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
status = status >= -1 ? status : 0;
@@ -1388,12 +1394,13 @@ function $HttpProvider() {
status: status,
headers: headersGetter(headers),
config: config,
statusText: statusText
statusText: statusText,
xhrStatus: xhrStatus
});
}
function resolvePromiseWithResult(result) {
resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText, result.xhrStatus);
}
function removePendingReq() {
+18 -7
View File
@@ -64,7 +64,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
var jsonpDone = jsonpReq(url, callbackPath, function(status, text) {
// jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING)
var response = (status === 200) && callbacks.getResponse(callbackPath);
completeRequest(callback, status, response, '', text);
completeRequest(callback, status, response, '', text, 'complete');
callbacks.removeCallback(callbackPath);
});
} else {
@@ -99,18 +99,29 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
status,
response,
xhr.getAllResponseHeaders(),
statusText);
statusText,
'complete');
};
var requestError = function() {
// The response is always empty
// See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
completeRequest(callback, -1, null, null, '');
completeRequest(callback, -1, null, null, '', 'error');
};
var requestAborted = function() {
completeRequest(callback, -1, null, null, '', 'abort');
};
var requestTimeout = function() {
// The response is always empty
// See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
completeRequest(callback, -1, null, null, '', 'timeout');
};
xhr.onerror = requestError;
xhr.onabort = requestError;
xhr.ontimeout = requestError;
xhr.onabort = requestAborted;
xhr.ontimeout = requestTimeout;
forEach(eventHandlers, function(value, key) {
xhr.addEventListener(key, value);
@@ -160,14 +171,14 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
}
}
function completeRequest(callback, status, response, headersString, statusText) {
function completeRequest(callback, status, response, headersString, statusText, xhrStatus) {
// cancel timeout and subsequent timeout promise resolution
if (isDefined(timeoutId)) {
$browserDefer.cancel(timeoutId);
}
jsonpDone = xhr = null;
callback(status, response, headersString, statusText);
callback(status, response, headersString, statusText, xhrStatus);
}
};
+9 -16
View File
@@ -705,7 +705,7 @@ function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
findConstantAndWatchExpressions(ast.property, $filter, astIsPure);
}
ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
ast.toWatch = [ast];
ast.toWatch = ast.constant ? [] : [ast];
break;
case AST.CallExpression:
isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false;
@@ -714,9 +714,7 @@ function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
forEach(ast.arguments, function(expr) {
findConstantAndWatchExpressions(expr, $filter, astIsPure);
allConstants = allConstants && expr.constant;
if (!expr.constant) {
argsToWatch.push.apply(argsToWatch, expr.toWatch);
}
argsToWatch.push.apply(argsToWatch, expr.toWatch);
});
ast.constant = allConstants;
ast.toWatch = isStatelessFilter ? argsToWatch : [ast];
@@ -733,9 +731,7 @@ function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
forEach(ast.elements, function(expr) {
findConstantAndWatchExpressions(expr, $filter, astIsPure);
allConstants = allConstants && expr.constant;
if (!expr.constant) {
argsToWatch.push.apply(argsToWatch, expr.toWatch);
}
argsToWatch.push.apply(argsToWatch, expr.toWatch);
});
ast.constant = allConstants;
ast.toWatch = argsToWatch;
@@ -745,17 +741,14 @@ function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
argsToWatch = [];
forEach(ast.properties, function(property) {
findConstantAndWatchExpressions(property.value, $filter, astIsPure);
allConstants = allConstants && property.value.constant && !property.computed;
if (!property.value.constant) {
argsToWatch.push.apply(argsToWatch, property.value.toWatch);
}
allConstants = allConstants && property.value.constant;
argsToWatch.push.apply(argsToWatch, property.value.toWatch);
if (property.computed) {
findConstantAndWatchExpressions(property.key, $filter, astIsPure);
if (!property.key.constant) {
argsToWatch.push.apply(argsToWatch, property.key.toWatch);
}
//`{[key]: value}` implicitly does `key.toString()` which may be non-pure
findConstantAndWatchExpressions(property.key, $filter, /*parentIsPure=*/false);
allConstants = allConstants && property.key.constant;
argsToWatch.push.apply(argsToWatch, property.key.toWatch);
}
});
ast.constant = allConstants;
ast.toWatch = argsToWatch;
+4 -4
View File
@@ -1354,8 +1354,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
return function() {
return angular.isNumber(status)
? [status, data, headers, statusText]
: [200, status, data, headers];
? [status, data, headers, statusText, 'complete']
: [200, status, data, headers, 'complete'];
};
}
@@ -1391,14 +1391,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
var response = wrapped.response(method, url, data, headers, wrapped.params(url));
xhr.$$respHeaders = response[2];
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
copy(response[3] || ''));
copy(response[3] || ''), copy(response[4]));
}
function handleTimeout() {
for (var i = 0, ii = responses.length; i < ii; i++) {
if (responses[i] === handleResponse) {
responses.splice(i, 1);
callback(-1, undefined, '');
callback(-1, undefined, '', undefined, 'timeout');
break;
}
}
+14 -5
View File
@@ -192,7 +192,12 @@ function shallowClearAndCopy(src, dst) {
* [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
* `response` and `responseError`. Both `response` and `responseError` interceptors get called
* with `http response` object. See {@link ng.$http $http interceptors}.
* with `http response` object. See {@link ng.$http $http interceptors}. In addition, the
* resource instance or array object is accessible by the `resource` property of the
* `http response` object.
* Keep in mind that the associated promise will be resolved with the value returned by the
* response interceptor, if one is specified. The default response interceptor returns
* `response.resource` (i.e. the resource instance or array).
* - **`hasBody`** - `{boolean}` - allows to specify if a request body should be included or not.
* If not specified only POST, PUT and PATCH requests will have a body.
*
@@ -267,8 +272,7 @@ function shallowClearAndCopy(src, dst) {
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
* rendering until the resource(s) are loaded.
*
* On failure, the promise is rejected with the {@link ng.$http http response} object, without
* the `resource` property.
* On failure, the promise is rejected with the {@link ng.$http http response} object.
*
* If an interceptor object was provided, the promise will instead be resolved with the value
* returned by the interceptor.
@@ -488,7 +492,7 @@ angular.module('ngResource', ['ng']).
* $resourceProvider.defaults.actions.update = {
* method: 'PUT'
* };
* });
* }]);
* ```
*
* Or you can even overwrite the whole `actions` list and specify your own:
@@ -776,6 +780,9 @@ angular.module('ngResource', ['ng']).
response.resource = value;
return response;
}, function(response) {
response.resource = value;
return $q.reject(response);
});
promise = promise['finally'](function() {
@@ -823,7 +830,9 @@ angular.module('ngResource', ['ng']).
function cancelRequest(value) {
promise.catch(noop);
timeoutDeferred.resolve(value);
if (timeoutDeferred !== null) {
timeoutDeferred.resolve(value);
}
}
};
+22 -4
View File
@@ -1731,8 +1731,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;
@@ -1749,10 +1748,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);
+1 -1
View File
@@ -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();
+223
View File
@@ -180,6 +180,15 @@ describe('$compile', function() {
inject();
});
it('should allow strictComponentBindingsEnabled to be configured', function() {
module(function($compileProvider) {
expect($compileProvider.strictComponentBindingsEnabled()).toBe(false); // the default
$compileProvider.strictComponentBindingsEnabled(true);
expect($compileProvider.strictComponentBindingsEnabled()).toBe(true);
});
inject();
});
it('should allow onChangesTtl to be configured', function() {
module(function($compileProvider) {
expect($compileProvider.onChangesTtl()).toBe(10); // the default
@@ -2557,6 +2566,16 @@ describe('$compile', function() {
template: '<span></span>'
};
});
directive('prototypeMethodNameAsScopeVarD', function() {
return {
scope: {
'constructor': '<?',
'valueOf': '<'
},
restrict: 'AE',
template: '<span></span>'
};
});
directive('watchAsScopeVar', function() {
return {
scope: {
@@ -2865,6 +2884,57 @@ describe('$compile', function() {
})
);
it('should throw an error for undefined non-optional "=" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-a></div>'
)($rootScope);
};
expect(func).toThrowMinErr('$compile',
'missingattr',
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
'ScopeVarA\' is non-optional and must be set!');
});
});
it('should not throw an error for set non-optional "=" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-a constructor="constructor" value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
});
});
it('should not throw an error for undefined optional "=" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-a value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
});
});
it('should handle "@" bindings with same method names in Object.prototype correctly when not present', inject(
function($rootScope, $compile) {
var func = function() {
@@ -2902,6 +2972,57 @@ describe('$compile', function() {
})
);
it('should throw an error for undefined non-optional "@" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-b></div>'
)($rootScope);
};
expect(func).toThrowMinErr('$compile',
'missingattr',
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
'ScopeVarB\' is non-optional and must be set!');
});
});
it('should not throw an error for set non-optional "@" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-b constructor="constructor" value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
});
});
it('should not throw an error for undefined optional "@" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-b value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
});
});
it('should handle "&" bindings with same method names in Object.prototype correctly when not present', inject(
function($rootScope, $compile) {
var func = function() {
@@ -2934,6 +3055,108 @@ describe('$compile', function() {
})
);
it('should throw an error for undefined non-optional "&" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-c></div>'
)($rootScope);
};
expect(func).toThrowMinErr('$compile',
'missingattr',
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
'ScopeVarC\' is non-optional and must be set!');
});
});
it('should not throw an error for set non-optional "&" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-c constructor="constructor" value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
});
});
it('should not throw an error for undefined optional "&" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-c value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
});
});
it('should throw an error for undefined non-optional "<" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-d></div>'
)($rootScope);
};
expect(func).toThrowMinErr('$compile',
'missingattr',
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
'ScopeVarD\' is non-optional and must be set!');
});
});
it('should not throw an error for set non-optional "<" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-d constructor="constructor" value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
});
});
it('should not throw an error for undefined optional "<" bindings when ' +
'strictComponentBindingsEnabled is true', function() {
module(function($compileProvider) {
$compileProvider.strictComponentBindingsEnabled(true);
});
inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-d value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
});
});
it('should not throw exception when using "watch" as binding in Firefox', inject(
function($rootScope, $compile) {
$rootScope.watch = 'watch';
+14 -2
View File
@@ -386,6 +386,9 @@ describe('form', function() {
doc = jqLite('<form ng-submit="submitMe()">' +
'<input type="submit" value="submit">' +
'</form>');
// Support: Chrome 60+ (on Windows)
// We need to add the form to the DOM in order for `submit` events to be properly fired.
window.document.body.appendChild(doc[0]);
var assertPreventDefaultListener = function(e) {
reloadPrevented = e.defaultPrevented || (e.returnValue === false);
@@ -420,15 +423,18 @@ describe('form', function() {
inject(function($timeout) {
doc = jqLite('<div>' +
'<form ng-submit="submitMe()">' +
'<button ng-click="destroy()"></button>' +
'<button type="submit" ng-click="destroy()"></button>' +
'</form>' +
'</div>');
// Support: Chrome 60+ (on Windows)
// We need to add the form to the DOM in order for `submit` events to be properly fired.
window.document.body.appendChild(doc[0]);
var form = doc.find('form'),
destroyed = false,
nextTurn = false,
submitted = false,
reloadPrevented;
reloadPrevented = 'never called';
scope.destroy = function() {
// yes, I know, scope methods should not do direct DOM manipulation, but I wanted to keep
@@ -466,6 +472,12 @@ describe('form', function() {
// the issue in the wild, I'm not going to bother to do it
// now. (i)
// Support: Chrome 60+ (on Windows)
// Chrome 60+ on Windows does not fire `submit` events when the form is not attached to
// the DOM. Verify that the `submit` listener was either never fired or (if fired) the
// reload was prevented.
expect(reloadPrevented).not.toBe(false);
// prevent mem leak in test
form[0].removeEventListener('submit', assertPreventDefaultListener);
})
+56 -40
View File
@@ -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('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}"/>');
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('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}"/>');
$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(20456, 3, 1, 0, 0, 0));
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');
});
expect(inputElm.val()).toBe('20456-04');
});
}
it('should only change the month of a bound date', function() {
var inputElm = helper.compileInput('<input type="month" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
@@ -899,17 +904,19 @@ describe('input', function() {
expect(inputElm).toBeValid();
});
it('should allow four or more digits in year', function() {
var inputElm = helper.compileInput('<input type="week" ng-model="value" ng-model-options="{timezone: \'UTC\'}"/>');
if (!isEdge) {
it('should allow four or more digits in year', function() {
var inputElm = helper.compileInput('<input type="week" ng-model="value" ng-model-options="{timezone: \'UTC\'}"/>');
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('<input type="week" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
@@ -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('<input type="datetime-local" ng-model="value" />');
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('<input type="datetime-local" ng-model="value" />');
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('<input type="datetime-local" ng-model="val" name="alias" />', {
@@ -1800,19 +1811,20 @@ describe('input', function() {
}
);
it('should allow four or more digits in year', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
if (!isEdge) {
it('should allow four or more digits in year', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="value" ng-model-options="{timezone: \'UTC\'}" />');
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');
}
);
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('<input type="date" ng-model="val" name="alias" />', {
@@ -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');
}
});
+16 -6
View File
@@ -12,15 +12,20 @@ describe('event directives', function() {
describe('ngSubmit', function() {
it('should get called on form submit', inject(function($rootScope, $compile) {
element = $compile('<form action="/foo" ng-submit="submitted = true">' +
'<input type="submit"/>' +
element = $compile(
'<form action="/foo" ng-submit="submitted = true">' +
'<input type="submit" />' +
'</form>')($rootScope);
$rootScope.$digest();
// Support: Chrome 60+
// We need to add the form to the DOM in order for `submit` events to be properly fired.
window.document.body.appendChild(element[0]);
// prevent submit within the test harness
element.on('submit', function(e) { e.preventDefault(); });
expect($rootScope.submitted).not.toBeDefined();
expect($rootScope.submitted).toBeUndefined();
browserTrigger(element.children()[0]);
expect($rootScope.submitted).toEqual(true);
@@ -33,15 +38,20 @@ describe('event directives', function() {
}
};
element = $compile('<form action="/foo" ng-submit="formSubmission($event)">' +
'<input type="submit"/>' +
element = $compile(
'<form action="/foo" ng-submit="formSubmission($event)">' +
'<input type="submit" />' +
'</form>')($rootScope);
$rootScope.$digest();
// Support: Chrome 60+ (on Windows)
// We need to add the form to the DOM in order for `submit` events to be properly fired.
window.document.body.appendChild(element[0]);
// prevent submit within the test harness
element.on('submit', function(e) { e.preventDefault(); });
expect($rootScope.formSubmitted).not.toBeDefined();
expect($rootScope.formSubmitted).toBeUndefined();
browserTrigger(element.children()[0]);
expect($rootScope.formSubmitted).toEqual('foo');
+127 -74
View File
@@ -2922,84 +2922,137 @@ describe('ngOptions', function() {
});
it('should not re-set the `selected` property if it already has the correct value', function() {
scope.values = [{name: 'A'}, {name: 'B'}];
createMultiSelect();
// Support: Safari 9
// This test relies defining a getter/setter `selected` property on either `<option>` elements
// or their prototype. Some browsers (including Safari 9) are very flakey when the
// getter/setter is not defined on the prototype (probably due to some bug). On Safari 9, the
// getter/setter that is already defined on the `<option>` element's prototype is not
// configurable, so we can't overwrite it with our spy.
if (!/\b9(?:\.\d+)+ safari/i.test(window.navigator.userAgent)) {
it('should not re-set the `selected` property if it already has the correct value', function() {
scope.values = [{name: 'A'}, {name: 'B'}];
createMultiSelect();
var options = element.find('option');
var optionsSetSelected = [];
var _selected = [];
var options = element.find('option');
var optionsSetSelected = [];
var _selected = [];
// Set up spies
forEach(options, function(option, i) {
optionsSetSelected[i] = jasmine.createSpy('optionSetSelected' + i);
_selected[i] = option.selected;
Object.defineProperty(option, 'selected', {
get: function() { return _selected[i]; },
set: optionsSetSelected[i].and.callFake(function(value) { _selected[i] = value; })
// Set up spies
var optionProto = Object.getPrototypeOf(options[0]);
var originalSelectedDescriptor = isFunction(Object.getOwnPropertyDescriptor) &&
Object.getOwnPropertyDescriptor(optionProto, 'selected');
var addSpiesOnProto = originalSelectedDescriptor && originalSelectedDescriptor.configurable;
forEach(options, function(option, i) {
var setSelected = function(value) { _selected[i] = value; };
optionsSetSelected[i] = jasmine.createSpy('optionSetSelected' + i).and.callFake(setSelected);
setSelected(option.selected);
});
if (!addSpiesOnProto) {
forEach(options, function(option, i) {
Object.defineProperty(option, 'selected', {
get: function() { return _selected[i]; },
set: optionsSetSelected[i]
});
});
} else {
// Support: Firefox 54+
// We cannot use the above (simpler) method on all browsers because of Firefox 54+, which
// is very flaky when the getter/setter property is defined on the element itself and not
// the prototype. (Possibly the result of some (buggy?) optimization.)
var getSelected = function(index) { return _selected[index]; };
var setSelected = function(index, value) { optionsSetSelected[index](value); };
var getSelectedOriginal = function(option) {
return originalSelectedDescriptor.get.call(option);
};
var setSelectedOriginal = function(option, value) {
originalSelectedDescriptor.set.call(option, value);
};
var getIndexAndCall = function(option, foundFn, notFoundFn, value) {
for (var i = 0, ii = options.length; i < ii; ++i) {
if (options[i] === option) return foundFn(i, value);
}
return notFoundFn(option, value);
};
Object.defineProperty(optionProto, 'selected', {
get: function() {
return getIndexAndCall(this, getSelected, getSelectedOriginal);
},
set: function(value) {
return getIndexAndCall(this, setSelected, setSelectedOriginal, value);
}
});
}
// Select `optionA`
scope.$apply('selected = [values[0]]');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(true);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Select `optionB` (`optionA` remains selected)
scope.$apply('selected.push(values[1])');
expect(optionsSetSelected[0]).not.toHaveBeenCalled();
expect(optionsSetSelected[1]).toHaveBeenCalledOnceWith(true);
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionA` (`optionB` remains selected)
scope.$apply('selected.shift()');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(false);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(false);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Reselect `optionA` (`optionB` remains selected)
scope.$apply('selected.push(values[0])');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(true);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionB` (`optionA` remains selected)
scope.$apply('selected.shift()');
expect(optionsSetSelected[0]).not.toHaveBeenCalled();
expect(optionsSetSelected[1]).toHaveBeenCalledOnceWith(false);
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionA`
scope.$apply('selected.length = 0');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(false);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(false);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Support: Firefox 54+
// Restore `originalSelectedDescriptor`
if (addSpiesOnProto) {
Object.defineProperty(optionProto, 'selected', originalSelectedDescriptor);
}
});
// Select `optionA`
scope.$apply('selected = [values[0]]');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(true);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Select `optionB` (`optionA` remains selected)
scope.$apply('selected.push(values[1])');
expect(optionsSetSelected[0]).not.toHaveBeenCalled();
expect(optionsSetSelected[1]).toHaveBeenCalledOnceWith(true);
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionA` (`optionB` remains selected)
scope.$apply('selected.shift()');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(false);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(false);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Reselect `optionA` (`optionB` remains selected)
scope.$apply('selected.push(values[0])');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(true);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionB` (`optionA` remains selected)
scope.$apply('selected.shift()');
expect(optionsSetSelected[0]).not.toHaveBeenCalled();
expect(optionsSetSelected[1]).toHaveBeenCalledOnceWith(false);
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionA`
scope.$apply('selected.length = 0');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(false);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(false);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
});
}
if (window.MutationObserver) {
//IE9 and IE10 do not support MutationObserver
+56 -1
View File
@@ -174,11 +174,12 @@ describe('$httpBackend', function() {
});
it('should complete the request on timeout', function() {
callback.and.callFake(function(status, response, headers, statusText) {
callback.and.callFake(function(status, response, headers, statusText, xhrStatus) {
expect(status).toBe(-1);
expect(response).toBe(null);
expect(headers).toBe(null);
expect(statusText).toBe('');
expect(xhrStatus).toBe('timeout');
});
$backend('GET', '/url', null, callback, {});
xhr = MockXhr.$$lastInstance;
@@ -189,6 +190,60 @@ describe('$httpBackend', function() {
expect(callback).toHaveBeenCalledOnce();
});
it('should complete the request on abort', function() {
callback.and.callFake(function(status, response, headers, statusText, xhrStatus) {
expect(status).toBe(-1);
expect(response).toBe(null);
expect(headers).toBe(null);
expect(statusText).toBe('');
expect(xhrStatus).toBe('abort');
});
$backend('GET', '/url', null, callback, {});
xhr = MockXhr.$$lastInstance;
expect(callback).not.toHaveBeenCalled();
xhr.onabort();
expect(callback).toHaveBeenCalledOnce();
});
it('should complete the request on error', function() {
callback.and.callFake(function(status, response, headers, statusText, xhrStatus) {
expect(status).toBe(-1);
expect(response).toBe(null);
expect(headers).toBe(null);
expect(statusText).toBe('');
expect(xhrStatus).toBe('error');
});
$backend('GET', '/url', null, callback, {});
xhr = MockXhr.$$lastInstance;
expect(callback).not.toHaveBeenCalled();
xhr.onerror();
expect(callback).toHaveBeenCalledOnce();
});
it('should complete the request on success', function() {
callback.and.callFake(function(status, response, headers, statusText, xhrStatus) {
expect(status).toBe(200);
expect(response).toBe('response');
expect(headers).toBe('');
expect(statusText).toBe('');
expect(xhrStatus).toBe('complete');
});
$backend('GET', '/url', null, callback, {});
xhr = MockXhr.$$lastInstance;
expect(callback).not.toHaveBeenCalled();
xhr.statusText = '';
xhr.response = 'response';
xhr.status = 200;
xhr.onload();
expect(callback).toHaveBeenCalledOnce();
});
it('should abort request on timeout', function() {
callback.and.callFake(function(status, response) {
expect(status).toBe(-1);
+76 -2
View File
@@ -448,6 +448,28 @@ describe('$http', function() {
expect(callback).toHaveBeenCalledOnce();
});
it('should pass xhrStatus in response object when a request is successful', function() {
$httpBackend.expect('GET', '/url').respond(200, 'SUCCESS', {}, 'OK');
$http({url: '/url', method: 'GET'}).then(function(response) {
expect(response.xhrStatus).toBe('complete');
callback();
});
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
});
it('should pass xhrStatus in response object when a request fails', function() {
$httpBackend.expect('GET', '/url').respond(404, 'ERROR', {}, 'Not Found');
$http({url: '/url', method: 'GET'}).then(null, function(response) {
expect(response.xhrStatus).toBe('complete');
callback();
});
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
});
it('should pass in the response object when a request failed', function() {
$httpBackend.expect('GET', '/url').respond(543, 'bad error', {'request-id': '123'});
@@ -1380,8 +1402,45 @@ describe('$http', function() {
expect(errCallback.calls.mostRecent().args[0]).toEqualMinErr('$http', 'baddata');
});
});
it('should not throw an error if JSON is invalid but content-type is not application/json', function() {
$httpBackend.expect('GET', '/url').respond('{abcd}', {'Content-Type': 'text/plain'});
$http.get('/url').then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
});
it('should not throw an error if JSON is invalid but content-type is not specified', function() {
$httpBackend.expect('GET', '/url').respond('{abcd}');
$http.get('/url').then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
});
it('should return response unprocessed if JSON is invalid but content-type is not application/json', function() {
var response = '{abcd}';
$httpBackend.expect('GET', '/url').respond(response, {'Content-Type': 'text/plain'});
$http.get('/url').then(callback);
$httpBackend.flush();
expect(callback.calls.mostRecent().args[0].data).toBe(response);
});
it('should return response unprocessed if JSON is invalid but content-type is not specified', function() {
var response = '{abcd}';
$httpBackend.expect('GET', '/url').respond(response);
$http.get('/url').then(callback);
$httpBackend.flush();
expect(callback.calls.mostRecent().args[0].data).toBe(response);
});
});
it('should have access to response headers', function() {
$httpBackend.expect('GET', '/url').respond(200, 'response', {h1: 'header1'});
@@ -1623,6 +1682,17 @@ describe('$http', function() {
expect(callback).toHaveBeenCalledOnce();
}));
it('should cache xhrStatus as well', inject(function($rootScope) {
doFirstCacheRequest('GET', 201, null);
callback.and.callFake(function(response) {
expect(response.xhrStatus).toBe('complete');
});
$http({method: 'get', url: '/url', cache: cache}).then(callback);
$rootScope.$digest();
expect(callback).toHaveBeenCalledOnce();
}));
it('should use cache even if second request was made before the first returned', function() {
$httpBackend.expect('GET', '/url').respond(201, 'fake-response');
@@ -1788,6 +1858,7 @@ describe('$http', function() {
function(response) {
expect(response.data).toBeUndefined();
expect(response.status).toBe(-1);
expect(response.xhrStatus).toBe('timeout');
expect(response.headers()).toEqual(Object.create(null));
expect(response.config.url).toBe('/some');
callback();
@@ -2273,7 +2344,6 @@ describe('$http param serializers', function() {
expect(defSer({someDate: new Date('2014-07-15T17:30:00.000Z')})).toEqual('someDate=2014-07-15T17:30:00.000Z');
expect(jqrSer({someDate: new Date('2014-07-15T17:30:00.000Z')})).toEqual('someDate=2014-07-15T17:30:00.000Z');
});
});
describe('default array serialization', function() {
@@ -2281,6 +2351,10 @@ describe('$http param serializers', function() {
it('should serialize arrays by repeating param name', function() {
expect(defSer({a: 'b', foo: ['bar', 'baz']})).toEqual('a=b&foo=bar&foo=baz');
});
it('should NOT serialize functions', function() {
expect(defSer({foo: 'foov', bar: function() {}})).toEqual('foo=foov');
});
});
describe('jquery array and objects serialization', function() {
+33 -8
View File
@@ -3568,35 +3568,55 @@ describe('parser', function() {
it('should watch ES6 object computed property changes', function() {
var count = 0;
var values = [];
var lastValue;
scope.$watch('{[a]: true}', function(val) {
count++;
values.push(val);
}, true);
lastValue = val;
});
scope.$digest();
expect(count).toBe(1);
expect(values[0]).toEqual({'undefined': true});
expect(lastValue).toEqual({'undefined': true});
scope.$digest();
expect(count).toBe(1);
expect(values[0]).toEqual({'undefined': true});
expect(lastValue).toEqual({'undefined': true});
scope.a = true;
scope.$digest();
expect(count).toBe(2);
expect(values[1]).toEqual({'true': true});
expect(lastValue).toEqual({'true': true});
scope.a = 'abc';
scope.$digest();
expect(count).toBe(3);
expect(values[2]).toEqual({'abc': true});
expect(lastValue).toEqual({'abc': true});
scope.a = undefined;
scope.$digest();
expect(count).toBe(4);
expect(values[3]).toEqual({'undefined': true});
expect(lastValue).toEqual({'undefined': true});
});
it('should not shallow-watch ES6 object computed properties in case of stateful toString', function() {
var count = 0;
var lastValue;
scope.$watch('{[a]: true}', function(val) {
count++;
lastValue = val;
});
scope.a = {toString: function() { return this.b; }};
scope.a.b = 1;
//TODO: would be great if it didn't throw!
expect(function() { scope.$apply(); }).toThrowMinErr('$rootScope', 'infdig');
expect(lastValue).toEqual({1: true});
expect(function() { scope.$apply('a.b = 2'); }).toThrowMinErr('$rootScope', 'infdig');
expect(lastValue).toEqual({2: true});
});
});
@@ -3772,6 +3792,11 @@ describe('parser', function() {
expect($parse('5 != null').constant).toBe(true);
expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
expect($parse('{[standard]: 4/3, wide: 16/9}').constant).toBe(false);
expect($parse('{["key"]: 1}').constant).toBe(true);
expect($parse('[0].length').constant).toBe(true);
expect($parse('[0][0]').constant).toBe(true);
expect($parse('{x: 1}.x').constant).toBe(true);
expect($parse('{x: 1}["x"]').constant).toBe(true);
}));
it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
+32 -20
View File
@@ -1343,8 +1343,8 @@ describe('ngMock', function() {
hb.flush();
expect(callback).toHaveBeenCalledTimes(2);
expect(callback.calls.argsFor(0)).toEqual([201, 'second', '', '']);
expect(callback.calls.argsFor(1)).toEqual([200, 'first', '', '']);
expect(callback.calls.argsFor(0)).toEqual([201, 'second', '', '', 'complete']);
expect(callback.calls.argsFor(1)).toEqual([200, 'first', '', '', 'complete']);
});
@@ -1354,7 +1354,7 @@ describe('ngMock', function() {
hb('GET', '/url1', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK');
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK', 'complete');
});
it('should default status code to 200', function() {
@@ -1377,7 +1377,19 @@ describe('ngMock', function() {
hb('GET', '/url1', null, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK');
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK', 'complete');
});
it('should default xhrStatus to complete', function() {
callback.and.callFake(function(status, response, headers, x, xhrStatus) {
expect(xhrStatus).toBe('complete');
});
hb.expect('GET', '/url1').respond('some-data');
hb('GET', '/url1', null, callback);
hb.flush();
expect(callback).toHaveBeenCalled();
});
it('should take function', function() {
@@ -1388,7 +1400,7 @@ describe('ngMock', function() {
hb('GET', '/some?q=s', 'data', callback, {a: 'b'});
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some?q=s;data;a=b;q=s', 'Connection: keep-alive', 'Moved Permanently');
expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some?q=s;data;a=b;q=s', 'Connection: keep-alive', 'Moved Permanently', undefined);
});
it('should decode query parameters in respond() function', function() {
@@ -1400,7 +1412,7 @@ describe('ngMock', function() {
hb('GET', '/url?query=l%E2%80%A2ng%20string%20w%2F%20spec%5Eal%20char%24&id=1234&orderBy=-name', null, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'id=1234;orderBy=-name;query=l•ng string w/ spec^al char$', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'id=1234;orderBy=-name;query=l•ng string w/ spec^al char$', '', '', undefined);
});
it('should include regex captures in respond() params when keys provided', function() {
@@ -1412,7 +1424,7 @@ describe('ngMock', function() {
hb('GET', '/1234/article/cool-angular-article', null, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'id=1234;name=cool-angular-article', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'id=1234;name=cool-angular-article', '', '', undefined);
});
it('should default response headers to ""', function() {
@@ -1425,8 +1437,8 @@ describe('ngMock', function() {
hb.flush();
expect(callback).toHaveBeenCalledTimes(2);
expect(callback.calls.argsFor(0)).toEqual([200, 'first', '', '']);
expect(callback.calls.argsFor(1)).toEqual([200, 'second', '', '']);
expect(callback.calls.argsFor(0)).toEqual([200, 'first', '', '', 'complete']);
expect(callback.calls.argsFor(1)).toEqual([200, 'second', '', '', 'complete']);
});
it('should be able to override response of expect definition', function() {
@@ -1436,7 +1448,7 @@ describe('ngMock', function() {
hb('GET', '/url1', null, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '', 'complete');
});
it('should be able to override response of when definition', function() {
@@ -1446,7 +1458,7 @@ describe('ngMock', function() {
hb('GET', '/url1', null, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '', 'complete');
});
it('should be able to override response of expect definition with chaining', function() {
@@ -1455,7 +1467,7 @@ describe('ngMock', function() {
hb('GET', '/url1', null, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '', 'complete');
});
it('should be able to override response of when definition with chaining', function() {
@@ -1464,7 +1476,7 @@ describe('ngMock', function() {
hb('GET', '/url1', null, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'second', '', '', 'complete');
});
});
@@ -1657,7 +1669,7 @@ describe('ngMock', function() {
canceler(); // simulate promise resolution
expect(callback).toHaveBeenCalledWith(-1, undefined, '');
expect(callback).toHaveBeenCalledWith(-1, undefined, '', undefined, 'timeout');
hb.verifyNoOutstandingExpectation();
hb.verifyNoOutstandingRequest();
});
@@ -1669,7 +1681,7 @@ describe('ngMock', function() {
hb('GET', '/url1', null, callback, null, 200);
$timeout.flush(300);
expect(callback).toHaveBeenCalledWith(-1, undefined, '');
expect(callback).toHaveBeenCalledWith(-1, undefined, '', undefined, 'timeout');
hb.verifyNoOutstandingExpectation();
hb.verifyNoOutstandingRequest();
}));
@@ -1831,7 +1843,7 @@ describe('ngMock', function() {
hb[shortcut]('/foo').respond('bar');
hb(method, '/foo', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'bar', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'bar', '', '', 'complete');
});
});
});
@@ -1846,7 +1858,7 @@ describe('ngMock', function() {
hb[routeShortcut](this, '/route').respond('path');
hb(this, '/route', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '', 'complete');
}
);
they('should match colon delimited parameters in ' + routeShortcut + ' $prop method', methods,
@@ -1854,7 +1866,7 @@ describe('ngMock', function() {
hb[routeShortcut](this, '/route/:id/path/:s_id').respond('path');
hb(this, '/route/123/path/456', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '', 'complete');
}
);
they('should ignore query param when matching in ' + routeShortcut + ' $prop method', methods,
@@ -1862,7 +1874,7 @@ describe('ngMock', function() {
hb[routeShortcut](this, '/route/:id').respond('path');
hb(this, '/route/123?q=str&foo=bar', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'path', '', '', 'complete');
}
);
});
@@ -2545,7 +2557,7 @@ describe('ngMockE2E', function() {
$browser.defer.flush();
expect(realHttpBackend).not.toHaveBeenCalled();
expect(callback).toHaveBeenCalledOnceWith(200, 'passThrough override', '', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'passThrough override', '', '', 'complete');
}));
it('should pass through to an httpBackend that uses the same $browser service', inject(function($browser) {
+20
View File
@@ -1205,6 +1205,7 @@ describe('basic usage', function() {
expect(callback).toHaveBeenCalledOnce();
var response = callback.calls.mostRecent().args[0];
expect(response.resource).toBe(ccs);
expect(response.status).toBe(404);
expect(response.config).toBeDefined();
});
@@ -2101,6 +2102,25 @@ describe('cancelling requests', function() {
expect(creditCard.$cancelRequest).toBe(noop);
});
it('should not break when calling old `$cancelRequest` after the response arrives', function() {
$httpBackend.whenGET('/CreditCard').respond({});
var CreditCard = $resource('/CreditCard', {}, {
get: {
method: 'GET',
cancellable: true
}
});
var creditCard = CreditCard.get();
var cancelRequest = creditCard.$cancelRequest;
$httpBackend.flush();
expect(cancelRequest).not.toBe(noop);
expect(cancelRequest).not.toThrow();
});
});
describe('configuring `cancellable` on the provider', function() {
+2 -2
View File
@@ -191,7 +191,7 @@ describe('ngView', function() {
it('should use inline content route changes', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {template: '<div>{{1+3}}</div>'});
$routeProvider.when('/bar', {template: 'angular is da best'});
$routeProvider.when('/bar', {template: 'AngularJS is da best'});
$routeProvider.when('/blank', {template: ''});
});
@@ -204,7 +204,7 @@ describe('ngView', function() {
$location.path('/bar');
$rootScope.$digest();
expect(element.text()).toEqual('angular is da best');
expect(element.text()).toEqual('AngularJS is da best');
$location.path('/blank');
$rootScope.$digest();
+13 -10
View File
@@ -265,18 +265,21 @@ describe('ngClick (touch)', function() {
expect($rootScope.tapped).toBe(true);
}));
it('should click when target element is an SVG', inject(
function($rootScope, $compile, $rootElement) {
element = $compile('<svg ng-click="tapped = true"></svg>')($rootScope);
$rootElement.append(element);
$rootScope.$digest();
if (!/\bEdge\//.test(window.navigator.userAgent)) {
// Edge cannot blur svg elements
it('should click when target element is an SVG', inject(
function($rootScope, $compile, $rootElement) {
element = $compile('<svg ng-click="tapped = true"></svg>')($rootScope);
$rootElement.append(element);
$rootScope.$digest();
browserTrigger(element, 'touchstart');
browserTrigger(element, 'touchend');
browserTrigger(element, 'click', {x:1, y:1});
browserTrigger(element, 'touchstart');
browserTrigger(element, 'touchend');
browserTrigger(element, 'click', {x:1, y:1});
expect($rootScope.tapped).toEqual(true);
}));
expect($rootScope.tapped).toEqual(true);
}));
}
describe('the clickbuster', function() {
var element1, element2;
+27 -1
View File
@@ -1093,7 +1093,7 @@ colors@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
colors@^1.1.0, colors@~1.1.2:
colors@^1.1.0, colors@^1.1.2, colors@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
@@ -1166,6 +1166,16 @@ commitizen@^2.3.0:
shelljs "0.7.5"
strip-json-comments "2.0.1"
commitplease@^2.7.10:
version "2.7.10"
resolved "https://registry.yarnpkg.com/commitplease/-/commitplease-2.7.10.tgz#129af5abb365b46f25e652020c5d1548c947f163"
dependencies:
chalk "^1.1.1"
git-tools "^0.2.1"
ini "^1.3.4"
object-assign "^4.1.0"
semver "^5.1.0"
commondir@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-0.0.1.tgz#89f00fdcd51b519c578733fec563e6a6da7f5be2"
@@ -2475,6 +2485,12 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
git-tools@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/git-tools/-/git-tools-0.2.1.tgz#6e1846af2c0e91ab59258b48f9b53c1279b3b273"
dependencies:
spawnback "~1.0.0"
glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@@ -3649,6 +3665,12 @@ karma-script-launcher@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/karma-script-launcher/-/karma-script-launcher-1.0.0.tgz#cd017c4de5ef09e5a9da793276176108dd4b542d"
karma-spec-reporter@^0.0.31:
version "0.0.31"
resolved "https://registry.yarnpkg.com/karma-spec-reporter/-/karma-spec-reporter-0.0.31.tgz#4830dc7148a155c7d7a186e632339a0d80fadec3"
dependencies:
colors "^1.1.2"
karma@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/karma/-/karma-1.7.0.tgz#6f7a1a406446fa2e187ec95398698f4cee476269"
@@ -5906,6 +5928,10 @@ spawn-sync@^1.0.15:
concat-stream "^1.4.7"
os-shim "^0.1.2"
spawnback@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/spawnback/-/spawnback-1.0.0.tgz#f73662f7e54d95367eca74d6426c677dd7ea686f"
spdx-correct@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"