Compare commits

...

55 Commits

Author SHA1 Message Date
Peter Bacon Darwin f276ad0d51 docs(CHANGELOG): add release notes for 1.5.4 2016-04-14 09:13:48 +01:00
Pete Bacon Darwin 33f817b99c feat($componentController): provide isolated scope if none is passed (#14425)
Closes #14425
2016-04-13 18:34:39 +01:00
Martin Staffa 6a4124d0fb perf(ngOptions): use documentFragment to populate select
This changes the way option elements are generated when the ngOption collection changes.
Previously, we would re-use option elements when possible (updating their text and
label). Now, we first remove all currently displayed options and the create new options for the
collection. The new options are first appended to a documentFragment, which is in the end appended
to the selectElement.

Using documentFragment improves render performance in IE with large option collections
(> 100 elements) considerably.

Creating new options from scratch fixes issues in IE where the select would become unresponsive
to user input.

Fixes #13607
Fixes #13239
Fixes #12076
2016-04-13 18:42:10 +02:00
Georgios Kalpakas 26d1b34321 docs(ngView): add known issue about asynchronously loaded ngView
Closes #14424
2016-04-13 18:45:40 +03:00
Georgios Kalpakas 7fba6b603f revert: "fix(ngRoute): allow ngView to be included in an asynchronously loaded template"
This reverts commit 5e37b2a7fd.
Eagerly loading `$route`, could break tests, because it might request the root or default route
template (something `$httpBackend` would know nothing about).

It will be re-applied for `v1.6.x`, with a breaking change notice and possibly a way to disable
the feature is tests.

Fixes #14337
2016-04-13 18:45:06 +03:00
Jason Bedard c115b37c33 perf($compile): use createMap() for directive bindings to allow fast forEach
Closes #12529
2016-04-13 12:05:30 +03:00
Georgii Dolzhykov d20ba95e28 docs(Module): fix parameter names for .decorator()
Closes #14413
2016-04-12 14:52:18 +03:00
aortyl 7945e5010a docs(guide/scope): add comma for readability
Closes #14411
2016-04-11 22:22:36 +03:00
Jason Bedard 21d148aedc refactor($compile): move setting of controller data to single location
Closes #13421
2016-04-11 18:54:41 +01:00
Georgios Kalpakas 4c8aeefb62 fix($compile): do not use noop() as controller for multiple components
Currently, custom annotations are copied from the CDO onto the controller constructor.
Using `noop()` when no controller has been specified, pollutes it with custom annotations and
makes one component's annotations available to all other components that have `noop()` as their
controller.

Fixes #14391
Closes #14402
2016-04-11 18:43:17 +01:00
a510 d384834fde fix($injector): ensure functions with overridden toString() are annotated properly
Closes #14361
2016-04-11 13:03:56 +03:00
David Rodenas Pico f975d8d448 fix(ngClass): fix watching of an array expression containing an object
Closes #14405
2016-04-11 12:40:36 +03:00
Georgios Kalpakas 2602daf993 refactor($compile): remove unnecessary call to isDefined()
(As discussed in https://github.com/angular/angular.js/pull/14406/files#r59131398.)
2016-04-11 12:33:07 +03:00
Gene McCulley 9f681c459a docs(numberFilter): fix the description of the returned value
Closes #14408
2016-04-11 12:28:08 +03:00
Peter Bacon Darwin d9448dcb9f fix($compile): still trigger $onChanges even if the inner value already matches the new value
Closes #14406
2016-04-10 20:31:01 +01:00
Jurko Gospodnetić e9c718a465 fix(ngMock): fix collecting stack trace in inject() on IE10+, PhantomJS
Add support for collecting current stack trace information in browsers
(e.g. IE10+, PhantomJS) that do not automatically store the current stack trace
information in a newly created `Error` object's `stack` property, but
only add it there once the `Error` gets thrown.

The original implementation works fine in Firefox & Chrome, but fails on IE10+
and PhantomJS where it, for example, breaks Karma's error reporting in cases
when an exception is thrown in a test like the following:

```
it('the holy crusade', inject(function() {
  var x = {};
  x.holyGrail();
}));
```

In this case, the ngMock `inject()` implementation would incorrectly add the
word `undefined` at the end of the collected error stack trace information,
thus causing the main error description to be reported back to Karma as
`undefined`.

The added test makes sure this functionality:

- works as expected in browsers supporting JavaScript stack trace
  collection, e.g. Chrome, Firefox, IE10+, Opera & PhantomJS
- does not add any bogus stack track information in browsers that do
  not support JavaScript stack trace collection, e.g. IE9

Fixes #13591
Closes #13592

Closes #13593
2016-04-09 20:51:22 +03:00
Georgios Kalpakas 4dc44f7205 test(helpers): fix error message generation for toHaveBeenCalledOnce[With] matchers
Jasmine 2.4 (maybe earlier) does not support returning an array containing both the "normal" and the
negative error messages. It will always concatenate them.

Closes #14275
2016-04-09 17:06:47 +03:00
Jan Niehusmann 499e1b2adf fix($compile): handle boolean attributes in @ bindings
Commit db5e0ff handles initial values of boolean attributes. The same
change needs to be applied inside the attrs.$observe() call.

Closes #14070
2016-04-09 16:53:41 +03:00
Peter Bacon Darwin 28c6c4dae2 docs(jqlite): add known issue
Closes #14251
2016-04-08 21:27:21 +01:00
Peter Bacon Darwin 791148a328 chore(package.json): update dgeni-packages to 0.12.0
This gives us `@knownissues` tags
2016-04-08 21:27:05 +01:00
Peter Bacon Darwin 01b1845088 feat($http): support handling additional XHR events
Closes #14367
Closes #11547
Closes #1934
2016-04-08 19:16:56 +01:00
Chris Chua 56c861c9e1 feat($http): support handling additional XHR events
Closes #11547
Closes #1934
2016-04-08 19:16:49 +01:00
Martin Staffa 451e0f6175 docs(README): fix typo 2016-04-08 19:00:20 +02:00
Martin Staffa aee7f1c72f docs(guide/accessibility): make jshint happy 2016-04-08 18:55:22 +02:00
Pablo Iván G. Soto 5fe28a0422 docs(README): add https links, improve style 2016-04-08 18:37:04 +02:00
Martin Staffa f56586d9a9 docs(guide/Accessibility): fix markdown errors, tweak layout 2016-04-08 17:32:13 +02:00
mohamed amr ec0baadcb6 feat(ngAria): add support for aria-readonly based on ngReadonly
Closes #14140
Closes #14077
2016-04-08 17:32:12 +02:00
Martin Staffa 9147d5c04a docs(ngComponentRouter): add note about shims needed for IE 2016-04-08 17:32:12 +02:00
Josh cb801378c9 docs(ngComponentRouter): fix typo
Simple typo fix from `betweent he` to `between the`

Closes #14396
2016-04-08 15:47:01 +02:00
Martin Staffa 79604f4628 fix(ngAnimate): remove event listeners only after all listeners have been called
The fix for removing the event callbacks on destroy introduced in
ce7f400011 removed the events too early, so that the event callbacks
for the "close" phase in "leave" animations were not called.

This commit fixes the behavior so that the event callbacks are only removed during on('$destroy')
when no animation is currently active on the element. When an animation is active, the event callbacks
will be removed after all callbacks have run, and if the element has no parent (has been removed from
the DOM).

Closes #14321
2016-04-08 15:47:00 +02:00
Martin Staffa bf6cb8ab0d feat(ngAnimate): let $animate.off() remove all listeners for an element 2016-04-08 15:46:59 +02:00
Andrew de74b3fd85 docs(guide/Components): fix small single letter typo
line 136: 'not' should be 'note'

Closes #14390
2016-04-08 01:32:00 +02:00
Martin Staffa c7a92d2a9a fix(ngAnimate): fire callbacks when document is hidden
Since commit a3a7afd3aa, animations are not run
when the document is hidden (only their structural or class change effects are executed).
However, some libraries rely on the $animate.on() callbacks to be called even when no actual animation
runs.
This commit restores the behavior for the ngAnimate.$animate functions.
Note that callbacks still won't be called if animations are disabled, because this would be be a potential
breaking change, as some applications might rely on this implementation.

Fixes #14120
2016-04-08 01:31:59 +02:00
Martin Staffa da284fc354 test(ngAnimate): test calling callbacks for various constellations 2016-04-08 01:31:58 +02:00
Martin Staffa 90da3059ce fix(ngAnimate): fire callbacks in the correct order for certain skipped animations 2016-04-08 01:31:58 +02:00
SHAHRUKH-KHAN 651fd05eca docs(angular.equals): add example
This Pull requests improves the doc by adding a example to `angular.equals` function.

Closes #14232
2016-04-07 15:17:54 +02:00
glenr4 85d7e09bef docs(ngAnimate): fix toggle button in example
The toggle button code on line 153 only sets bool to true, rather than toggling it.
The proposed change fixes this.

Closes #14387
2016-04-07 15:17:54 +02:00
andykuszyk 243a57648b docs(misc/Getting Started): fix markdown for headings
Closes #14353
2016-04-07 15:17:54 +02:00
Maciej Kołodziejczak 158dec330c docs(ngComponentRouter): fix a typo
Closes #14357
2016-04-07 15:17:53 +02:00
Andrew a1e63c8d4a docs(guide/Components): clarify output events with extra example and note
Add additional line from example which demonstrates using the snake cased attribute binding in
parent component template.
Add note clarifying camelCase to snake-case requirement to use the Output binding callback feature.

Closes #14365
2016-04-07 15:17:53 +02:00
Artur 8cc9978d15 docs(guide/Components): fix typo, improve style
Closes #14384
2016-04-07 15:17:52 +02:00
Robin Janssens 78a404da0e docs($httpBackend): update response data types
Updated docs to reflect that response data can either be an array, object _or_ a string
Technically, response data can be anything that can be handled by angular.copy,
but since string and JSON data is most commonly mocked, the main types are sufficient.

Closes #14346
2016-04-07 15:17:52 +02:00
Michał Gołębiowski f1bb8369b5 refactor(jshint): don't assume browser-only globals
(cherry-picked from ddad26402b)

Fixes #13442
Closes #14345
2016-04-06 20:43:06 +02:00
Peter Bacon Darwin 76b6941b4c docs(componentRouter): add custom installation instructions 2016-04-05 21:10:19 +01:00
Peter Bacon Darwin e72990dc37 fix($compile): don't throw if controller is named 2016-04-04 21:26:13 +01:00
Peter Bacon Darwin f338e96ccc feat($compile): put custom annotations on DDO
Closes #14369
Closes #14279
Closes #14284
2016-04-04 20:07:31 +01:00
Peter Bacon Darwin 0ad2b70862 fix($compile): ensure that $onChanges hook is called correctly
Due to the way that we instantiate controllers, the `$onChanges` hook
was not always available at the time we were trying to trigger the initial
call to this hook. For instance, if the hook was actually defined inside
the constructor function.

This commit fixes that but also fixes the fact that the initial call was
being made in the postDigest anyway, which was incorrect because the
it should have been made before the `$onInit` call.

Closes #14355
Closes #14359
2016-04-01 17:41:41 +01:00
Georgios Kalpakas 5261a374a9 test($browser): fix typo in property name (histroy --> history)
(The semantics of the test aren't affected, because we just needed a falsy value.)
2016-03-31 13:30:25 +03:00
Martin Staffa 832eba5fc9 fix(ngOptions): set select value when model matches disabled option
When ngModel is set to a value that matches a disabled option, ngOptions will now select the option
in the select element, which will set select.val() to the option hash value and visually
show the option value / label as selected in the select box. Previously, disabled
options forced the unknown value.
The previous behavior is inconsistent with both default HTML behavior and select with
ngModel but without ngOptions. Both allow disabled values to be selected programmatically.

A common use case for this behavior is an option that was previously valid, but has
been disabled, and cannot be selected again.

This commit removes a duplicate test, and all other tests that previously checked that disabled
options are not set have been adjusted to the ensure the opposite.

Fixes #12756
2016-03-30 11:42:07 +02:00
cscport 2b569fc0b2 docs(angular.bootstrap): fix capitalization in error message
Closes #14325
2016-03-27 15:43:37 +03:00
Lucas Mirelmann ee6aeb08bf docs(ngParseExt): Fix package name 2016-03-27 00:00:14 +01:00
Peter Bacon Darwin 8d43d8b8e7 feat($compile): add isFirstChange() method to onChanges object
Closes #14318
Closes #14323
2016-03-26 20:06:13 +00:00
Lucas Mirelmann d08f5c6986 feat(ngParseExt): New ngParseExt module
New ngParseExt module

Including this module into an application will extend $parse to allow identifiers
following ES6 identifiers
2016-03-26 20:41:11 +01:00
Lucas Mirelmann 3e7fa19197 feat($parse): Add the ability to define the identifier characters
Add the ability to define the identifier starts and identifier continue characters
2016-03-26 20:41:01 +01:00
Lucas Mirelmann 5d379db72b chore(bower): Add parse-ext repository 2016-03-26 20:40:49 +01:00
87 changed files with 5907 additions and 5131 deletions
+8 -3
View File
@@ -1,19 +1,24 @@
{
"bitwise": true,
"esversion": 6,
"immed": true,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
"trailing": true,
"maxlen": 200,
"boss": true,
"eqnull": true,
"expr": true,
"globalstrict": true,
"laxbreak": true,
"loopfunc": true,
"strict": "global",
"sub": true,
"undef": true,
"indent": 2
"indent": 2,
"globals": {
"ArrayBuffer": false,
"Uint8Array": false
}
}
+83
View File
@@ -1,3 +1,86 @@
<a name="1.5.4"></a>
# 1.5.4 graduated-sophistry (2016-04-14)
## Bug Fixes
- **$compile:**
- do not use `noop()` as controller for multiple components
([4c8aeefb](https://github.com/angular/angular.js/commit/4c8aeefb624de7436ad95f3cd525405e0c3f493e),
[#14391](https://github.com/angular/angular.js/issues/14391), [#14402](https://github.com/angular/angular.js/issues/14402))
- still trigger `$onChanges` even if the inner value already matches the new value
([d9448dcb](https://github.com/angular/angular.js/commit/d9448dcb9f901ceb04deda1d5f3d5aac8442a718),
[#14406](https://github.com/angular/angular.js/issues/14406))
- handle boolean attributes in `@` bindings
([499e1b2a](https://github.com/angular/angular.js/commit/499e1b2adf27f32d671123f8dceadb3df2ad84a9),
[#14070](https://github.com/angular/angular.js/issues/14070))
- don't throw if controller is named
([e72990dc](https://github.com/angular/angular.js/commit/e72990dc3714c8b847185ddb64fd5fd00e5cceab))
- ensure that `$onChanges` hook is called correctly
([0ad2b708](https://github.com/angular/angular.js/commit/0ad2b70862d49ecc4355a16d767c0ca9358ecc3e),
[#14355](https://github.com/angular/angular.js/issues/14355), [#14359](https://github.com/angular/angular.js/issues/14359))
- **$injector:** ensure functions with overridden `toString()` are annotated properly
([d384834f](https://github.com/angular/angular.js/commit/d384834fdee140a716298bd065f304f8fba4725e),
[#14361](https://github.com/angular/angular.js/issues/14361))
- **ngAnimate:**
- remove event listeners only after all listeners have been called
([79604f46](https://github.com/angular/angular.js/commit/79604f462899c118a99d610995083ff82d38aa35),
[#14321](https://github.com/angular/angular.js/issues/14321))
- fire callbacks when document is hidden
([c7a92d2a](https://github.com/angular/angular.js/commit/c7a92d2a9a436dddd65de721c9837a93e915d939),
[#14120](https://github.com/angular/angular.js/issues/14120))
- fire callbacks in the correct order for certain skipped animations
([90da3059](https://github.com/angular/angular.js/commit/90da3059cecfefaecf136b01cd87aee6775a8778))
- **ngClass:** fix watching of an array expression containing an object
([f975d8d4](https://github.com/angular/angular.js/commit/f975d8d4481e0b8cdba553f0e5ad9ec1688adae8),
[#14405](https://github.com/angular/angular.js/issues/14405))
- **ngMock:** fix collecting stack trace in `inject()` on IE10+, PhantomJS
([e9c718a4](https://github.com/angular/angular.js/commit/e9c718a465d28b9f2691e3acab944f7c31aa9fb6),
[#13591](https://github.com/angular/angular.js/issues/13591), [#13592](https://github.com/angular/angular.js/issues/13592), [#13593](https://github.com/angular/angular.js/issues/13593))
- **ngOptions:** set select value when model matches disabled option
([832eba5f](https://github.com/angular/angular.js/commit/832eba5fc952312e6b99127123e6e75bdf729006),
[#12756](https://github.com/angular/angular.js/issues/12756))
## Features
- **$compile:**
- put custom annotations on DDO
([f338e96c](https://github.com/angular/angular.js/commit/f338e96ccc739efc4b24022eae406c3d5451d422),
[#14369](https://github.com/angular/angular.js/issues/14369), [#14279](https://github.com/angular/angular.js/issues/14279), [#14284](https://github.com/angular/angular.js/issues/14284))
- add `isFirstChange()` method to onChanges object
([8d43d8b8](https://github.com/angular/angular.js/commit/8d43d8b8e7aacf97ddb9aa48bff25db57249cdd5),
[#14318](https://github.com/angular/angular.js/issues/14318), [#14323](https://github.com/angular/angular.js/issues/14323))
- **$componentController:** provide isolated scope if none is passed (#14425)
([33f817b9](https://github.com/angular/angular.js/commit/33f817b99cb20e566b381e7202235fe99b4a742a),
[#14425](https://github.com/angular/angular.js/issues/14425))
- **$http:**
- support handling additional XHR events
([01b18450](https://github.com/angular/angular.js/commit/01b18450882da9bb9c903d43c0daddbc03c2c35d) and
[56c861c9](https://github.com/angular/angular.js/commit/56c861c9e114c45790865e5635eaae8d32eb649a),
[#14367](https://github.com/angular/angular.js/issues/14367), [#11547](https://github.com/angular/angular.js/issues/11547), [#1934](https://github.com/angular/angular.js/issues/1934))
- **$parse:** add the ability to define the identifier characters
([3e7fa191](https://github.com/angular/angular.js/commit/3e7fa19197c54a764225ad27c0c0bf72263daa8d))
- **ngAnimate:** let $animate.off() remove all listeners for an element
([bf6cb8ab](https://github.com/angular/angular.js/commit/bf6cb8ab0d157083a1ed55743e3fffe728daa6f3))
- **ngAria:** add support for aria-readonly based on ngReadonly
([ec0baadc](https://github.com/angular/angular.js/commit/ec0baadcb68a4fa8da27d76b7e6a4e0840acd7fa),
[#14140](https://github.com/angular/angular.js/issues/14140), [#14077](https://github.com/angular/angular.js/issues/14077))
- **ngParseExt:** new ngParseExt module
([d08f5c69](https://github.com/angular/angular.js/commit/d08f5c698624f6243685b16f2d458cb9a980ebde))
## Performance Improvements
- **$compile:** use createMap() for directive bindings to allow fast `forEach`
([c115b37c](https://github.com/angular/angular.js/commit/c115b37c336f3a5936187279057b29c76078caf2),
[#12529](https://github.com/angular/angular.js/issues/12529))
- **ngOptions:** use `documentFragment` to populate `select` options
([6a4124d0](https://github.com/angular/angular.js/commit/6a4124d0fb17cd7fc0e8bf5a1ca4d785a1d11c1c),
[#13607](https://github.com/angular/angular.js/issues/13607), [#13239](https://github.com/angular/angular.js/issues/13239), [#12076](https://github.com/angular/angular.js/issues/12076))
<a name="1.5.3"></a>
# 1.5.3 diplohaplontic-meiosis (2016-03-25)
+10 -2
View File
@@ -135,6 +135,9 @@ module.exports = function(grunt) {
ngMock: {
files: { src: 'src/ngMock/**/*.js' },
},
ngParseExt: {
files: { src: 'src/ngParseExt/**/*.js' },
},
ngResource: {
files: { src: 'src/ngResource/**/*.js' },
},
@@ -231,7 +234,11 @@ module.exports = function(grunt) {
dest: 'build/angular-aria.js',
src: util.wrap(files['angularModules']['ngAria'], 'module')
},
'promises-aplus-adapter': {
parseext: {
dest: 'build/angular-parse-ext.js',
src: util.wrap(files['angularModules']['ngParseExt'], 'module')
},
"promises-aplus-adapter": {
dest:'tmp/promises-aplus-adapter++.js',
src:['src/ng/q.js', 'lib/promises-aplus/promises-aplus-test-adapter.js']
}
@@ -249,7 +256,8 @@ module.exports = function(grunt) {
resource: 'build/angular-resource.js',
route: 'build/angular-route.js',
sanitize: 'build/angular-sanitize.js',
aria: 'build/angular-aria.js'
aria: 'build/angular-aria.js',
parseext: 'build/angular-parse-ext.js'
},
+18 -13
View File
@@ -8,20 +8,21 @@ synchronizes data from your UI (view) with your JavaScript objects (model) throu
binding. To help you structure your application better and make it easy to test, AngularJS teaches
the browser how to do dependency injection and inversion of control.
Oh yeah and it helps with server-side communication, taming async callbacks with promises and
deferreds. It also makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
piece of cake. Best of all?? It makes development fun!
It also helps with server-side communication, taming async callbacks with promises and deferreds,
and it makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
piece of cake. Best of all? It makes development fun!
* Web site: http://angularjs.org
* Tutorial: http://docs.angularjs.org/tutorial
* API Docs: http://docs.angularjs.org/api
* Developer Guide: http://docs.angularjs.org/guide
* Web site: https://angularjs.org
* Tutorial: https://docs.angularjs.org/tutorial
* API Docs: https://docs.angularjs.org/api
* Developer Guide: https://docs.angularjs.org/guide
* Contribution guidelines: [CONTRIBUTING.md](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md)
* Dashboard: http://dashboard.angularjs.org
* Dashboard: https://dashboard.angularjs.org
Building AngularJS
---------
[Once you have your environment set up](http://docs.angularjs.org/misc/contribute) just run:
[Once you have set up your environment](https://docs.angularjs.org/misc/contribute), just run:
grunt package
@@ -37,8 +38,12 @@ To execute end-to-end (e2e) tests, use:
grunt package
grunt test:e2e
To learn more about the grunt tasks, run `grunt --help` and also read our
[contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
To learn more about the grunt tasks, run `grunt --help`
Contribute & Develop
--------------------
We've set up a separate document for our [contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/README.md?pixel)](https://github.com/igrigorik/ga-beacon)
@@ -48,7 +53,7 @@ What to use AngularJS for and when to use it
AngularJS is the next generation framework where each component is designed to work with every other component in an interconnected way like a well-oiled machine. AngularJS is JavaScript MVC made easy and done right. (Well it is not really MVC, read on, to understand what this means.)
#### MVC, no, MV* done the right way!
MVC, short for Model-View-Controller, is a design pattern, i.e. how the code should be organized and how the different parts of an application separated for proper readability and debugging. Model is the data and the database. View is the user interface and what the user sees. Controller is the main link between Model and View. These are the three pillars of major programming frameworks present on the market today. On the other hand AngularJS works on MV*, short for Model-View-_Whatever_. The _Whatever_ is AngularJS's way of telling that you may create any kind of linking between the Model and the View here.
MVC, short for Model-View-Controller, is a design pattern, i.e. how the code should be organized and how the different parts of an application separated for proper readability and debugging. Model is the data and the database. View is the user interface and what the user sees. Controller is the main link between Model and View. These are the three pillars of major programming frameworks present on the market today. On the other hand AngularJS works on MV*, short for Model-View-_Whatever_. The _Whatever_ is AngularJS's way of telling that you may create any kind of linking between the Model and the View here.
Unlike other frameworks in any programming language, where MVC, the three separate components, each one has to be written and then connected by the programmer, AngularJS helps the programmer by asking him/her to just create these and everything else will be taken care of by AngularJS.
+4
View File
@@ -120,6 +120,10 @@ var angularFiles = {
'ngMessages': [
'src/ngMessages/messages.js'
],
'ngParseExt': [
'src/ngParseExt/ucd.js',
'src/ngParseExt/module.js'
],
'ngResource': [
'src/ngResource/resource.js'
],
+4 -1
View File
@@ -19,9 +19,12 @@
"dump": false,
/* e2e */
"protractor": false,
"browser": false,
"element": false,
"by": false,
"$": false,
"$$": false,
/* testabilityPatch / matchers */
"inject": false,
@@ -39,4 +42,4 @@
"browserTrigger": false,
"jqLiteCacheSize": false
}
}
}
+1
View File
@@ -54,6 +54,7 @@ module.exports = new Package('angularjs', [
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/tutorial-step'));
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/sortOrder'));
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/installation'));
})
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
name: 'installation'
};
@@ -0,0 +1,92 @@
{% extends "base.template.html" %}
{% block content %}
<h1>
{% if doc.title %}{$ doc.title | marked $}{% else %}{$ doc.name | code $}{% endif %}
</h1>
{% if doc.installation or doc.installation == '' %}
{$ doc.installation | marked $}
{% else %}
<h2>Installation</h2>
<p>First include {$ doc.packageFile | code $} in your HTML:</p>
{% code %}
<script src="angular.js">
<script src="{$ doc.packageFile $}">
{% endcode %}
<p>You can download this file from the following places:</p>
<ul>
<li>
<a href="https://developers.google.com/speed/libraries/devguide#angularjs">Google CDN</a><br>
e.g. {$ ("//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/" + doc.packageFile) | code $}
</li>
<li>
<a href="http://bower.io">Bower</a><br>
e.g. {% code %}bower install {$ doc.packageName $}@X.Y.Z{% endcode %}
</li>
<li>
<a href="http://code.angularjs.org/">code.angularjs.org</a><br>
e.g. {% code %}"//code.angularjs.org/X.Y.Z/{$ doc.packageFile $}"{% endcode %}
</li>
</ul>
<p>where X.Y.Z is the AngularJS version you are running.</p>
<p>Then load the module in your application by adding it as a dependent module:</p>
{% code %}
angular.module('app', ['{$ doc.name $}']);
{% endcode %}
<p>With that you&apos;re ready to get started!</p>
{% endif %}
{$ doc.description | marked $}
{% if doc.knownIssueDocs %}
<div class="known-issues">
<h2 id="known-issues">Known Issues</h2>
<table class="definition-table">
<tr><th>Name</th><th>Description</th></tr>
{% for issueDoc in doc.knownIssueDocs -%}
<tr>
<td>{$ issueDoc.id | link(issueDoc.name, issueDoc) $}</td>
<td>
{% for issue in issueDoc.knownIssues -%}
{$ issue | marked $}
{% endfor -%}
</td>
</tr>
{% endfor -%}
</table>
</div>
{% endif %}
<div class="component-breakdown">
<h2>Module Components</h2>
{% for componentGroup in doc.componentGroups %}
<div>
<h3 class="component-heading" id="{$ componentGroup.groupType | dashCase $}">{$ componentGroup.groupType | title $}</h3>
<table class="definition-table">
<tr>
<th>Name</th>
<th>Description</th>
</tr>
{% for component in componentGroup.components %}
<tr>
<td>{$ component.id | link(component.name, component) $}</td>
<td>{$ component.description | firstParagraph | marked $}</td>
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
</div>
{% if doc.usage %}
<h2>Usage</h2>
{$ doc.usage | marked $}
{% endif %}
{% endblock %}
+90 -69
View File
@@ -10,7 +10,7 @@ The goal of ngAria is to improve Angular's default accessibility by enabling com
[ARIA](http://www.w3.org/TR/wai-aria/) attributes that convey state or semantic information for
assistive technologies used by persons with disabilities.
##Including ngAria
## Including ngAria
Using {@link ngAria ngAria} is as simple as requiring the ngAria module in your application. ngAria hooks into
standard AngularJS directives and quietly injects accessibility support into your application
@@ -20,7 +20,7 @@ at runtime.
angular.module('myApp', ['ngAria'])...
```
###Using ngAria
### Using ngAria
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
added it as a dependency, you can test a few things:
* Using your favorite element inspector, look for attributes added by ngAria in your own code.
@@ -28,12 +28,13 @@ added it as a dependency, you can test a few things:
* Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support.
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
##Supported directives
## Supported directives
Currently, ngAria interfaces with the following directives:
* {@link guide/accessibility#ngmodel ngModel}
* {@link guide/accessibility#ngdisabled ngDisabled}
* {@link guide/accessibility#ngrequired ngRequired}
* {@link guide/accessibility#ngreadonly ngReadonly}
* {@link guide/accessibility#ngvaluechecked ngChecked}
* {@link guide/accessibility#ngvaluechecked ngValue}
* {@link guide/accessibility#ngshow ngShow}
@@ -57,12 +58,62 @@ attributes (if they have not been explicitly specified by the developer):
* aria-valuenow
* aria-invalid
* aria-required
* aria-readonly
###Example
### Example
<example module="ngAria_ngModelExample" deps="angular-aria.js">
<file name="index.html">
<style>
<file name="index.html">
<form ng-controller="formsController">
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
aria-label="Custom Checkbox" show-attrs>
<span class="icon" aria-hidden="true"></span>
Custom Checkbox
</some-checkbox>
</form>
</file>
<file name="script.js">
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
.controller('formsController', function($scope){
$scope.checked = false;
$scope.toggleCheckbox = function(){
$scope.checked = !$scope.checked;
};
})
.directive('someCheckbox', function(){
return {
restrict: 'E',
link: function($scope, $el, $attrs) {
$el.on('keypress', function(event){
event.preventDefault();
if(event.keyCode === 32 || event.keyCode === 13){
$scope.toggleCheckbox();
$scope.$apply();
}
});
}
};
})
.directive('showAttrs', function() {
return function($scope, $el, $attrs) {
var pre = document.createElement('pre');
$el.after(pre);
$scope.$watch(function() {
var $attrs = {};
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
if (item.name !== 'show-$attrs') {
$attrs[item.name] = item.value;
}
});
return $attrs;
}, function(newAttrs, oldAttrs) {
pre.textContent = JSON.stringify(newAttrs, null, 2);
}, true);
};
});
</file>
<file name="style.css">
[role=checkbox] {
cursor: pointer;
display: inline-block;
@@ -81,58 +132,7 @@ attributes (if they have not been explicitly specified by the developer):
pre {
white-space: pre-wrap;
}
</style>
<div>
<form ng-controller="formsController">
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
aria-label="Custom Checkbox" show-attrs>
<span class="icon" aria-hidden="true"></span>
Custom Checkbox
</some-checkbox>
</form>
</div>
<script>
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
.controller('formsController', function($scope){
$scope.checked = false;
$scope.toggleCheckbox = function(){
$scope.checked = !$scope.checked;
}
})
.directive('someCheckbox', function(){
return {
restrict: 'E',
link: function($scope, $el, $attrs) {
$el.on('keypress', function(event){
event.preventDefault();
if(event.keyCode === 32 || event.keyCode === 13){
$scope.toggleCheckbox();
$scope.$apply();
}
});
}
}
})
.directive('showAttrs', function() {
return function($scope, $el, $attrs) {
var pre = document.createElement('pre');
$el.after(pre);
$scope.$watch(function() {
var $attrs = {};
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
if (item.name !== 'show-$attrs') {
$attrs[item.name] = item.value;
}
});
return $attrs;
}, function(newAttrs, oldAttrs) {
pre.textContent = JSON.stringify(newAttrs, null, 2);
}, true);
}
});
</script>
</file>
</file>
</example>
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
@@ -147,7 +147,7 @@ To ease the transition between native inputs and custom controls, ngAria now sup
The original directives were created for native inputs only, so ngAria extends
support to custom elements by managing `aria-checked` for accessibility.
###Example
### Example
```html
<custom-checkbox ng-checked="val"></custom-checkbox>
@@ -169,7 +169,7 @@ using ngAria with {@link ng.ngDisabled ngDisabled} will also
add `aria-disabled`. This tells assistive technologies when a non-native input is disabled, helping
custom controls to be more accessible.
###Example
### Example
```html
<md-checkbox ng-disabled="disabled"></md-checkbox>
@@ -181,8 +181,10 @@ Becomes:
<md-checkbox disabled aria-disabled="true"></md-checkbox>
```
>You can check whether a control is legitimately disabled for a screen reader by visiting
<div class="alert alert-info">
You can check whether a control is legitimately disabled for a screen reader by visiting
[chrome://accessibility](chrome://accessibility) and inspecting [the accessibility tree](http://www.paciellogroup.com/blog/2015/01/the-browser-accessibility-tree/).
</div>
<h2 id="ngrequired">ngRequired</h2>
@@ -191,7 +193,7 @@ The boolean `required` attribute is only valid for native form controls such as
as required, using ngAria with {@link ng.ngRequired ngRequired} will also add
`aria-required`. This tells accessibility APIs when a custom control is required.
###Example
### Example
```html
<md-checkbox ng-required="val"></md-checkbox>
@@ -203,9 +205,28 @@ Becomes:
<md-checkbox ng-required="val" aria-required="true"></md-checkbox>
```
<h2 id="ngreadonly">ngReadonly</h2>
The boolean `readonly` attribute is only valid for native form controls such as `input` and
`textarea`. To properly indicate custom element directives such as `<md-checkbox>` or `<custom-input>`
as required, using ngAria with {@link ng.ngReadonly ngReadonly} will also add
`aria-readonly`. This tells accessibility APIs when a custom control is read-only.
### Example
```html
<md-checkbox ng-readonly="val"></md-checkbox>
```
Becomes:
```html
<md-checkbox ng-readonly="val" aria-readonly="true"></md-checkbox>
```
<h2 id="ngshow">ngShow</h2>
>The {@link ng.ngShow ngShow} directive shows or hides the
The {@link ng.ngShow ngShow} directive shows or hides the
given HTML element based on the expression provided to the `ngShow` attribute. The element is
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
@@ -222,7 +243,7 @@ screen reader users won't accidentally focus on "mystery elements". Managing tab
child control can be complex and affect performance, so its best to just stick with the default
`display: none` CSS. See the [fourth rule of ARIA use](http://www.w3.org/TR/aria-in-html/#fourth-rule-of-aria-use).
###Example
### Example
```css
.ng-hide {
display: block;
@@ -242,7 +263,7 @@ Becomes:
<h2 id="nghide">ngHide</h2>
>The {@link ng.ngHide ngHide} directive shows or hides the
The {@link ng.ngHide ngHide} directive shows or hides the
given HTML element based on the expression provided to the `ngHide` attribute. The element is
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
@@ -283,11 +304,11 @@ Becomes:
<h2 id="ngmessages">ngMessages</h2>
The new ngMessages module makes it easy to display form validation or other messages with priority
The ngMessages module makes it easy to display form validation or other messages with priority
sequencing and animation. To expose these visual messages to screen readers,
ngAria injects `aria-live="assertive"`, causing them to be read aloud any time a message is shown,
regardless of the user's focus location.
###Example
### Example
```html
<div ng-messages="myForm.myName.$error">
@@ -305,7 +326,7 @@ Becomes:
</div>
```
##Disabling attributes
## Disabling attributes
The attribute magic of ngAria may not work for every scenario. To disable individual attributes,
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
tell ngAria to ignore the attribute globally.
@@ -343,7 +364,7 @@ tell ngAria to ignore the attribute globally.
</file>
</example>
##Common Accessibility Patterns
## Common Accessibility Patterns
Accessibility best practices that apply to web apps in general also apply to Angular.
+4 -2
View File
@@ -133,6 +133,8 @@ components should follow a few simple conventions:
For a deletion, that means the component doesn't delete the `hero` itself, but sends it back to
the owner component via the correct event.
```html
<!-- note that we use snake case for bindings in the template as usual -->
<editable-field on-update="$ctrl.update('location', value)"></editable-field><br>
<button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
```
- That way, the parent component can decide what to do with the event (e.g. delete an item or update the properties)
@@ -156,7 +158,7 @@ of the component. The following hook methods can be implemented:
this element). This is a good place to put initialization code for your controller.
* `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys
are the names of the bound properties that have changed, and the values are an object of the form
`{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as
`{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a component such as
cloning the bound value to prevent accidental mutation of the outer value.
* `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
external resources, watches and event handlers.
@@ -169,7 +171,7 @@ of the component. The following hook methods can be implemented:
Since the compilation process is rather different in Angular 1 there is no direct mapping and care should
be taken when upgrading.
By implementing these methods, you component can take part in its lifecycle.
By implementing these methods, your component can hook into its lifecycle.
- **An application is a tree of components:**
Ideally, the whole application should be a tree of components that implement clearly defined inputs
+1 -1
View File
@@ -257,7 +257,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
2. **Watcher registration**
During template linking directives register {@link
During template linking, directives register {@link
ng.$rootScope.Scope#$watch watches} on the scope. These watches will be
used to propagate model values to the DOM.
+4 -4
View File
@@ -16,9 +16,9 @@ becoming an Angular expert.
starter app with a directory layout, test harness, and scripts to begin building your application.
#Further Steps
# Further Steps
##Watch Videos
## Watch Videos
If you havent had a chance to watch the videos from the homepage, please check out:
@@ -29,13 +29,13 @@ If you havent had a chance to watch the videos from the homepage, please chec
And visit our [YouTube channel](http://www.youtube.com/user/angularjs) for more AngularJS video presentations and
tutorials.
##Subscribe
## Subscribe
* Subscribe to the [mailing list](http://groups.google.com/forum/?fromgroups#!forum/angular). Ask questions here!
* Follow us on [Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fangularjs.org%2F&region=follow_link&screen_name=angularjs&source=followbutton&variant=2.0)
* Add us to your circles on [Google+](https://plus.google.com/110323587230527980117/posts)
##Read more
## Read more
The AngularJS documentation includes the {@link guide/index Developer Guide} covering concepts and the
{@link ./api API Reference} for syntax and usage.
+44 -5
View File
@@ -1,5 +1,9 @@
"use strict";
var fs = require('fs');
var _ = require('lodash');
var stripJsonComments = require('strip-json-comments');
var gulp = require('gulp');
var log = require('gulp-util').log;
var concat = require('gulp-concat');
@@ -25,6 +29,23 @@ var ignoredFiles = '!src/angular.bind.js';
var assets = 'app/assets/**/*';
var getJshintConfig = function(filepath) {
return JSON.parse(stripJsonComments(fs.readFileSync(filepath, {encoding: 'utf-8'})));
};
var getMergedJshintConfig = function(filepath) {
// "extends" doesn't work in configuration passed by an object, we need to do the extending ourselves.
var config = getJshintConfig(filepath);
var baseConfig = getJshintConfig('../.jshintrc-base');
_.merge(config, baseConfig);
delete config.extends;
// Examples don't run in strict mode; accept that for now.
config.strict = false;
return config;
};
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
pattern = pattern || '/**/*';
sourceFolder = sourceFolder || bowerFolder;
@@ -90,17 +111,35 @@ gulp.task('assets', ['bower'], function() {
gulp.task('doc-gen', ['bower'], function() {
var dgeni = new Dgeni([require('./config')]);
return dgeni.generate().catch(function(error) {
return dgeni.generate().catch(function() {
process.exit(1);
});
});
// JSHint the example and protractor test files
gulp.task('jshint', ['doc-gen'], function() {
gulp.src([outputFolder + '/ptore2e/**/*.js', outputFolder + '/examples/**/*.js'])
.pipe(jshint())
.pipe(jshint.reporter('jshint-stylish'))
.pipe(jshint.reporter('fail'));
var examplesConfig = getMergedJshintConfig('../docs/app/test/.jshintrc');
// Some tests use `alert` which is not assumed to be available even with `"browser": true`.
examplesConfig.globals.alert = false;
var protractorConfig = getMergedJshintConfig('../docs/app/e2e/.jshintrc');
return merge(
gulp.src([
outputFolder + '/examples/**/*.js',
'!' + outputFolder + '/examples/**/protractor.js',
])
.pipe(jshint(examplesConfig))
.pipe(jshint.reporter('jshint-stylish'))
.pipe(jshint.reporter('fail')),
gulp.src([
outputFolder + '/ptore2e/**/*.js',
outputFolder + '/examples/**/protractor.js',
])
.pipe(jshint(protractorConfig))
.pipe(jshint.reporter('jshint-stylish'))
.pipe(jshint.reporter('fail'))
);
});
+4
View File
@@ -9,4 +9,8 @@ npm run test-i18n
node src/closureSlurper.js
npm run test-i18n-ucd
echo "Generating ngParseExt"
node ucd/src/extract.js
+53
View File
@@ -0,0 +1,53 @@
var extractValues = require('../src/extractValues.js').extractValues;
var stream = require('stream');
function StringStream(str) {
return new stream.Readable({
read: function(n) {
this.push(str);
str = null;
}
});
}
describe('extractValues', function() {
it('should extract the values from the xml', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><char cp="0002" IDS="Y"></char><char cp="0003" IDS="N"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0002' ] ] });
done();
});
});
it('should extract the values from the xml if the last element matches', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><char cp="0002" IDS="Y"></char><char cp="0003" IDS="Y"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0003' ] ] });
done();
});
});
it('should support `reserved`', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><reserved first-cp="0002" last-cp="0005" IDS="N"></reserved><char cp="0006" IDS="Y"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
done();
});
});
it('should support `surrogate`', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><surrogate first-cp="0002" last-cp="0005" IDS="N"></surrogate><char cp="0006" IDS="Y"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
done();
});
});
it('should support `noncharactere`', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><noncharacter first-cp="0002" last-cp="0005" IDS="N"></noncharacter><char cp="0006" IDS="Y"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
done();
});
});
});
+54
View File
@@ -0,0 +1,54 @@
var generateCodeModule = require('../src/generateCode.js');
var generateCode = generateCodeModule.generateCode;
var generateFunction = generateCodeModule.generateFunction;
describe('generateFunction', function() {
it('should generate function with ranges', function() {
expect(generateFunction([ [ '0001', '0003' ] ], 'IDS_Y')).toEqual('\
function IDS_Y(cp) {\n\
if (0x0001 <= cp && cp <= 0x0003) return true;\n\
return false;\n\
}\n');
});
it('should generate function with multiple ranges', function() {
expect(generateFunction([ [ '0001', '0003' ], [ '0005', '0009'] ], 'IDS_Y')).toEqual('\
function IDS_Y(cp) {\n\
if (0x0001 <= cp && cp <= 0x0003) return true;\n\
if (0x0005 <= cp && cp <= 0x0009) return true;\n\
return false;\n\
}\n');
});
it('should generate function with unique values', function() {
expect(generateFunction([ [ '0001', '0001' ], [ '0005', '0009'] ], 'IDS_Y')).toEqual('\
function IDS_Y(cp) {\n\
if (cp === 0x0001) return true;\n\
if (0x0005 <= cp && cp <= 0x0009) return true;\n\
return false;\n\
}\n');
});
});
describe('generateCode', function() {
it('should generate the function for all the values', function() {
expect(generateCode({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ], IDC_Y : [ [ '0002', '0002' ], [ '0007', '0007' ] ] })).toEqual('\
/******************************************************\n\
* Generated file, do not modify *\n\
* *\n\
*****************************************************/\n\
"use strict";\n\
function IDS_Y(cp) {\n\
if (cp === 0x0001) return true;\n\
if (cp === 0x0006) return true;\n\
return false;\n\
}\n\
function IDC_Y(cp) {\n\
if (cp === 0x0002) return true;\n\
if (cp === 0x0007) return true;\n\
return false;\n\
}\n\
');
});
});
+28
View File
@@ -0,0 +1,28 @@
"use strict";
var fs = require('fs');
var zlib = require('zlib');
var extractValues = require('./extractValues').extractValues;
var generateCode = require('./generateCode').generateCode;
var generateextractValues = require('./extractValues').extractValues;
// ID_Start and ID_Continue
var propertiesToExtract = {'IDS': 'Y', 'IDC': 'Y'};
function main() {
extractValues(
fs.createReadStream('./ucd/src/ucd.all.flat.xml.gz').pipe(zlib.createGunzip()),
propertiesToExtract,
writeFile);
function writeFile(validRanges) {
var code = generateCode(validRanges);
try {
var stats = fs.lstatSync('../src/ngParseExt');
} catch (e) {
fs.mkdirSync('../src/ngParseExt');
}
fs.writeFile('../src/ngParseExt/ucd.js', code);
}
}
main();
+58
View File
@@ -0,0 +1,58 @@
/**
* Extract values from a stream.
*/
exports.extractValues = extractValues;
var sax = require('sax/lib/sax');
var saxStrict = true;
var saxOptions = {};
var validXMLTagNames = { char: 'Y', reserved: 'Y', surrogate: 'Y', noncharacter: 'Y'};
function extractValues(stream, propertiesToExtract, callback) {
var saxStream = sax.createStream(saxStrict, saxOptions);
var firstValid = {};
var lastValid = {};
var keys = Object.keys(propertiesToExtract);
var keyValues = keys.map(function(k) { return propertiesToExtract[k]; });
var validRanges = {};
for (var i in keys) {
validRanges[keys[i] + '_' + keyValues[i]] = [];
}
saxStream.onopentag = onOpenTag;
stream
.pipe(saxStream)
.on('end', doCallback);
function onOpenTag(node) {
var property;
if (validXMLTagNames[node.name]) {
for (var i in keys) {
property = keyValues[i];
if (node.attributes[keys[i]] === property) validProperty(keys[i] + '_' + property, node);
else invalidProperty(keys[i] + '_' + property);
}
}
}
function validProperty(property, node) {
if (!firstValid[property]) firstValid[property] =
node.attributes.cp || node.attributes['first-cp'];
lastValid[property] = node.attributes.cp || node.attributes['last-cp'];
}
function invalidProperty(property) {
if (!firstValid[property]) return;
validRanges[property].push([firstValid[property], lastValid[property]]);
firstValid[property] = null;
}
function doCallback() {
for (var i in keys) {
property = keys[i] + '_' + keyValues[i];
invalidProperty(property);
}
callback(validRanges);
}
}
+33
View File
@@ -0,0 +1,33 @@
exports.generateCode = generateCode;
exports.generateFunction = generateFunction;
function generateCode(validRanges) {
var code = '/******************************************************\n' +
' * Generated file, do not modify *\n' +
' * *\n' +
' *****************************************************/\n' +
'"use strict";\n';
var keys = Object.keys(validRanges);
for (var i in keys) {
code += generateFunction(validRanges[keys[i]], keys[i]);
}
return code;
}
function generateFunction(positiveElements, functionName) {
var result = [];
result.push('function ', functionName, '(cp) {\n');
positiveElements.forEach(function(range) {
if (range[0] === range[1]) {
result.push(' if (cp === 0x', range[0], ')');
} else {
result.push(' if (0x', range[0], ' <= cp && cp <= 0x', range[1], ')');
}
result.push(' return true;\n');
});
result.push(' return false;\n}\n');
return result.join('');
}
Binary file not shown.
+229 -995
View File
File diff suppressed because it is too large Load Diff
+2332 -3499
View File
File diff suppressed because it is too large Load Diff
+10 -6
View File
@@ -17,7 +17,8 @@
"preinstall": "node scripts/npm/check-node-modules.js --purge",
"postinstall": "node scripts/npm/copy-npm-shrinkwrap.js",
"commit": "git-cz",
"test-i18n": "jasmine-node i18n/spec"
"test-i18n": "jasmine-node i18n/spec",
"test-i18n-ucd": "jasmine-node i18n/ucd/spec"
},
"devDependencies": {
"angular-benchpress": "0.x.x",
@@ -29,7 +30,7 @@
"commitizen": "^2.3.0",
"cz-conventional-changelog": "1.1.4",
"dgeni": "^0.4.0",
"dgeni-packages": "^0.11.0",
"dgeni-packages": "^0.12.0",
"event-stream": "~3.1.0",
"glob": "^6.0.1",
"grunt": "~0.4.2",
@@ -38,7 +39,7 @@
"grunt-contrib-compress": "~0.12.0",
"grunt-contrib-connect": "~0.8.0",
"grunt-contrib-copy": "~0.6.0",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-jshint": "^1.0.0",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-jscs": "^2.1.0",
@@ -47,7 +48,7 @@
"gulp": "~3.8.0",
"gulp-concat": "^2.4.1",
"gulp-foreach": "0.0.1",
"gulp-jshint": "~1.4.2",
"gulp-jshint": "^2.0.0",
"gulp-rename": "^1.2.0",
"gulp-sourcemaps": "^1.2.2",
"gulp-uglify": "^1.0.1",
@@ -55,7 +56,8 @@
"jasmine-core": "^2.4.0",
"jasmine-node": "^2.0.0",
"jasmine-reporters": "~1.0.1",
"jshint-stylish": "~1.0.0",
"jshint": "^2.9.1",
"jshint-stylish": "^2.1.0",
"karma": "^0.13.19",
"karma-browserstack-launcher": "^0.1.8",
"karma-chrome-launcher": "^0.2.2",
@@ -75,10 +77,12 @@
"q-io": "^1.10.9",
"qq": "^0.3.5",
"rewire": "~2.1.0",
"sax": "^1.1.1",
"semver": "~4.0.3",
"shelljs": "~0.3.0",
"sorted-object": "^1.0.0",
"stringmap": "^0.2.2"
"stringmap": "^0.2.2",
"strip-json-comments": "^2.0.1"
},
"licenses": [
{
+1
View File
@@ -11,6 +11,7 @@ REPOS=(
angular-message-format
angular-messages
angular-mocks
angular-parse-ext
angular-resource
angular-route
angular-sanitize
+2 -1
View File
@@ -1,7 +1,8 @@
{
"extends": "../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
/* auto/injector.js */
"createInjector": false,
+43 -7
View File
@@ -102,6 +102,7 @@
* @ngdoc module
* @name ng
* @module ng
* @installation
* @description
*
* # ng (core module)
@@ -168,7 +169,7 @@ var
* documentMode is an IE-only property
* http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
*/
msie = document.documentMode;
msie = window.document.documentMode;
/**
@@ -978,6 +979,41 @@ function shallowCopy(src, dst) {
* @param {*} o1 Object or value to compare.
* @param {*} o2 Object or value to compare.
* @returns {boolean} True if arguments are equal.
*
* @example
<example module="equalsExample" name="equalsExample">
<file name="index.html">
<div ng-controller="ExampleController">
<form novalidate>
<h3>User 1</h3>
Name: <input type="text" ng-model="user1.name">
Age: <input type="number" ng-model="user1.age">
<h3>User 2</h3>
Name: <input type="text" ng-model="user2.name">
Age: <input type="number" ng-model="user2.age">
<div>
<br/>
<input type="button" value="Compare" ng-click="compare()">
</div>
User 1: <pre>{{user1 | json}}</pre>
User 2: <pre>{{user2 | json}}</pre>
Equal: <pre>{{result}}</pre>
</form>
</div>
</file>
<file name="script.js">
angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) {
$scope.user1 = {};
$scope.user2 = {};
$scope.result;
$scope.compare = function() {
$scope.result = angular.equals($scope.user1, $scope.user2);
};
}]);
</file>
</example>
*/
function equals(o1, o2) {
if (o1 === o2) return true;
@@ -1024,8 +1060,8 @@ var csp = function() {
if (!isDefined(csp.rules)) {
var ngCspElement = (document.querySelector('[ng-csp]') ||
document.querySelector('[data-ng-csp]'));
var ngCspElement = (window.document.querySelector('[ng-csp]') ||
window.document.querySelector('[data-ng-csp]'));
if (ngCspElement) {
var ngCspAttribute = ngCspElement.getAttribute('ng-csp') ||
@@ -1100,7 +1136,7 @@ var jq = function() {
var i, ii = ngAttrPrefixes.length, prefix, name;
for (i = 0; i < ii; ++i) {
prefix = ngAttrPrefixes[i];
if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
if (el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
name = el.getAttribute(prefix + 'jq');
break;
}
@@ -1165,7 +1201,7 @@ function toJsonReplacer(key, value) {
val = undefined;
} else if (isWindow(value)) {
val = '$WINDOW';
} else if (value && document === value) {
} else if (value && window.document === value) {
val = '$DOCUMENT';
} else if (isScope(value)) {
val = '$SCOPE';
@@ -1617,11 +1653,11 @@ function bootstrap(element, modules, config) {
element = jqLite(element);
if (element.injector()) {
var tag = (element[0] === document) ? 'document' : startingTag(element);
var tag = (element[0] === window.document) ? 'document' : startingTag(element);
//Encode angle brackets to prevent input from being sanitized to empty string #8683
throw ngMinErr(
'btstrpd',
"App Already Bootstrapped with this Element '{0}'",
"App already bootstrapped with this element '{0}'",
tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
}
+1 -1
View File
@@ -3,4 +3,4 @@
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {
(function(window) {
+3 -3
View File
@@ -1,5 +1,5 @@
jqLite(document).ready(function() {
angularInit(document, bootstrap);
jqLite(window.document).ready(function() {
angularInit(window.document, bootstrap);
});
})(window, document);
})(window);
+2 -1
View File
@@ -57,6 +57,7 @@
/**
* @ngdoc module
* @name auto
* @installation
* @description
*
* Implicit module which gets automatically added to each {@link auto.$injector $injector}.
@@ -70,7 +71,7 @@ var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');
function extractArgs(fn) {
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
var fnText = Function.prototype.toString.call(fn).replace(STRIP_COMMENTS, ''),
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
return args;
}
+7 -4
View File
@@ -114,6 +114,9 @@
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
* parent element is reached.
*
* @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See
* https://github.com/angular/angular.js/issues/14251 for more information.
*
* @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
* @returns {Object} jQuery object.
*/
@@ -240,7 +243,7 @@ function jqLiteBuildFragment(html, context) {
}
function jqLiteParseHTML(html, context) {
context = context || document;
context = context || window.document;
var parsed;
if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
@@ -266,7 +269,7 @@ function jqLiteWrapNode(node, wrapper) {
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
var jqLiteContains = Node.prototype.contains || function(arg) {
var jqLiteContains = window.Node.prototype.contains || function(arg) {
// jshint bitwise: false
return !!(this.compareDocumentPosition(arg) & 16);
// jshint bitwise: true
@@ -538,8 +541,8 @@ var JQLitePrototype = JQLite.prototype = {
}
// check if document is already loaded
if (document.readyState === 'complete') {
setTimeout(trigger);
if (window.document.readyState === 'complete') {
window.setTimeout(trigger);
} else {
this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
+3 -3
View File
@@ -197,9 +197,9 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#decorator
* @module ng
* @param {string} The name of the service to decorate.
* @param {Function} This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance.
* @param {string} name The name of the service to decorate.
* @param {Function} decorFn This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance.
* @description
* See {@link auto.$provide#decorator $provide.decorator()}.
*/
+1 -1
View File
@@ -3,4 +3,4 @@
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
(function(window, angular) {
+6 -1
View File
@@ -326,6 +326,9 @@ var $AnimateProvider = ['$provide', function($provide) {
* // remove all the animation event listeners listening for `enter`
* $animate.off('enter');
*
* // remove listeners for all animation events from the container element
* $animate.off(container);
*
* // remove all the animation event listeners listening for `enter` on the given element and its children
* $animate.off('enter', container);
*
@@ -334,7 +337,9 @@ var $AnimateProvider = ['$provide', function($provide) {
* $animate.off('enter', container, callback);
* ```
*
* @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...)
* @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move,
* addClass, removeClass, etc...), or the container element. If it is the element, all other
* arguments are ignored.
* @param {DOMElement=} container the container element the event listener was placed on
* @param {Function=} callback the callback function that was registered as the listener
*/
+75 -38
View File
@@ -298,8 +298,8 @@
* this element). This is a good place to put initialization code for your controller.
* * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
* `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
* object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component
* such as cloning the bound value to prevent accidental mutation of the outer value.
* object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
* component such as cloning the bound value to prevent accidental mutation of the outer value.
* * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
* external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
* the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
@@ -846,6 +846,9 @@
var $compileMinErr = minErr('$compile');
function UNINITIALIZED_VALUE() {}
var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();
/**
* @ngdoc provider
* @name $compileProvider
@@ -870,7 +873,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function parseIsolateBindings(scope, directiveName, isController) {
var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/;
var bindings = {};
var bindings = createMap();
forEach(scope, function(definition, scopeName) {
if (definition in bindingCache) {
@@ -1089,7 +1092,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
*/
this.component = function registerComponent(name, options) {
var controller = options.controller || noop;
var controller = options.controller || function() {};
function factory($injector) {
function makeInjectable(fn) {
@@ -1103,7 +1106,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
var template = (!options.template && !options.templateUrl ? '' : options.template);
return {
var ddo = {
controller: controller,
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
template: makeInjectable(template),
@@ -1114,14 +1117,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
restrict: 'E',
require: options.require
};
// Copy annotations (starting with $) over to the DDO
forEach(options, function(val, key) {
if (key.charAt(0) === '$') ddo[key] = val;
});
return ddo;
}
// Copy any annotation properties (starting with $) over to the factory function
// TODO(pete) remove the following `forEach` before we release 1.6.0
// The component-router@0.2.0 looks for the annotations on the controller constructor
// Nothing in Angular looks for annotations on the factory function but we can't remove
// it from 1.5.x yet.
// Copy any annotation properties (starting with $) over to the factory and controller constructor functions
// These could be used by libraries such as the new component router
forEach(options, function(val, key) {
if (key.charAt(0) === '$') {
factory[key] = val;
controller[key] = val;
// Don't try to copy over annotations to named controller
if (isFunction(controller)) controller[key] = val;
}
});
@@ -1258,7 +1274,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
$controller, $rootScope, $sce, $animate, $$sanitizeUri) {
var SIMPLE_ATTR_NAME = /^\w/;
var specialAttrHolder = document.createElement('div');
var specialAttrHolder = window.document.createElement('div');
@@ -1589,7 +1605,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (debugInfoEnabled) {
content = ' ' + (directiveName || '') + ': ' + (comment || '') + ' ';
}
return document.createComment(content);
return window.document.createComment(content);
};
return compile;
@@ -1612,7 +1628,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var domNode = $compileNodes[i];
if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) {
jqLiteWrapNode(domNode, $compileNodes[i] = document.createElement('span'));
jqLiteWrapNode(domNode, $compileNodes[i] = window.document.createElement('span'));
}
}
@@ -2305,7 +2321,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
replaceDirective = directive;
}
/* jshint -W021 */
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
/* jshint +W021 */
templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
controllerDirectives: controllerDirectives,
newScopeDirective: (newScopeDirective !== directive) && newScopeDirective,
@@ -2369,7 +2387,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
attrs, removeScopeBindingWatches, removeControllerBindingWatches;
attrs, scopeBindingInfo;
if (compileNode === linkNode) {
attrs = templateAttrs;
@@ -2408,23 +2426,26 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
compile.$$addScopeClass($element, true);
isolateScope.$$isolateBindings =
newIsolateScopeDirective.$$isolateBindings;
removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope,
isolateScope.$$isolateBindings,
newIsolateScopeDirective);
if (removeScopeBindingWatches) {
isolateScope.$on('$destroy', removeScopeBindingWatches);
if (scopeBindingInfo.removeWatches) {
isolateScope.$on('$destroy', scopeBindingInfo.removeWatches);
}
}
// Initialize bindToController bindings
// Initialize controllers
for (var name in elementControllers) {
var controllerDirective = controllerDirectives[name];
var controller = elementControllers[name];
var bindings = controllerDirective.$$bindings.bindToController;
// Initialize bindToController bindings
if (controller.identifier && bindings) {
removeControllerBindingWatches =
controller.bindingInfo =
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
} else {
controller.bindingInfo = {};
}
var controllerResult = controller();
@@ -2432,11 +2453,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// If the controller constructor has a return value, overwrite the instance
// from setupControllers
controller.instance = controllerResult;
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
removeControllerBindingWatches && removeControllerBindingWatches();
removeControllerBindingWatches =
controller.bindingInfo.removeWatches && controller.bindingInfo.removeWatches();
controller.bindingInfo =
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
}
// Store controllers on the $element data
// For transclude comment nodes this will be a noop and will be done at transclusion time
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
}
// Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy
@@ -2450,6 +2474,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// Handle the init and destroy lifecycle hooks on all controllers that have them
forEach(elementControllers, function(controller) {
var controllerInstance = controller.instance;
if (isFunction(controllerInstance.$onChanges)) {
controllerInstance.$onChanges(controller.bindingInfo.initialChanges);
}
if (isFunction(controllerInstance.$onInit)) {
controllerInstance.$onInit();
}
@@ -2600,14 +2627,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
controller = attrs[directive.name];
}
var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
// For directives with element transclusion the element is a comment.
// In this case .data will not attach any data.
// Instead, we save the controllers for the element in a local hash and attach to .data
// later, once we have the actual element.
elementControllers[directive.name] = controllerInstance;
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
elementControllers[directive.name] = $controller(controller, locals, true, directive.controllerAs);
}
return elementControllers;
}
@@ -2906,7 +2926,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
switch (type) {
case 'svg':
case 'math':
var wrapper = document.createElement('div');
var wrapper = window.document.createElement('div');
wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>';
return wrapper.childNodes[0].childNodes;
default:
@@ -3050,7 +3070,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// - remove them from the DOM
// - allow them to still be traversed with .nextSibling
// - allow a single fragment.qSA to fetch all elements being removed
var fragment = document.createDocumentFragment();
var fragment = window.document.createDocumentFragment();
for (i = 0; i < removeCount; i++) {
fragment.appendChild(elementsToRemove[i]);
}
@@ -3096,6 +3116,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// only occurs for isolate scopes and new scopes with controllerAs.
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
var removeWatchCollection = [];
var initialChanges = {};
var changes;
forEach(bindings, function initializeBinding(definition, scopeName) {
var attrName = definition.attrName,
@@ -3111,7 +3132,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
destination[scopeName] = attrs[attrName] = void 0;
}
attrs.$observe(attrName, function(value) {
if (isString(value)) {
if (isString(value) || isBoolean(value)) {
var oldValue = destination[scopeName];
recordChanges(scopeName, value, oldValue);
destination[scopeName] = value;
@@ -3128,6 +3149,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// the value to boolean rather than a string, so we special case this situation
destination[scopeName] = lastValue;
}
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
break;
case '=':
@@ -3183,11 +3205,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
parentGet = $parse(attrs[attrName]);
destination[scopeName] = parentGet(scope);
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) {
var oldValue = destination[scopeName];
recordChanges(scopeName, newParentValue, oldValue);
destination[scopeName] = newParentValue;
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
if (newValue === oldValue) {
// If the new and old values are identical then this is the first time the watch has been triggered
// So instead we use the current value on the destination as the old value
oldValue = destination[scopeName];
}
recordChanges(scopeName, newValue, oldValue);
destination[scopeName] = newValue;
}, parentGet.literal);
removeWatchCollection.push(removeWatch);
@@ -3224,7 +3251,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
previousValue = changes[key].previousValue;
}
// Store this change
changes[key] = {previousValue: previousValue, currentValue: currentValue};
changes[key] = new SimpleChange(previousValue, currentValue);
}
}
@@ -3234,15 +3261,25 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
changes = undefined;
}
return removeWatchCollection.length && function removeWatches() {
for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
removeWatchCollection[i]();
return {
initialChanges: initialChanges,
removeWatches: removeWatchCollection.length && function removeWatches() {
for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
removeWatchCollection[i]();
}
}
};
}
}];
}
function SimpleChange(previous, current) {
this.previousValue = previous;
this.currentValue = current;
}
SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; };
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
/**
* Converts all accepted directives format into proper directive name.
+5 -1
View File
@@ -78,7 +78,11 @@ function classDirective(name, selector) {
updateClasses(oldClasses, newClasses);
}
}
oldVal = shallowCopy(newVal);
if (isArray(newVal)) {
oldVal = newVal.map(function(v) { return shallowCopy(v); });
} else {
oldVal = shallowCopy(newVal);
}
}
}
};
+1 -1
View File
@@ -292,7 +292,7 @@ var ngIncludeFillContentDirective = ['$compile',
// support innerHTML, so detect this here and try to generate the contents
// specially.
$element.empty();
$compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
$compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope,
function namespaceAdaptedClone(clone) {
$element.append(clone);
}, {futureParentElement: $element});
+44 -100
View File
@@ -245,7 +245,7 @@ var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s
// jshint maxlen: 100
var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) {
function parseOptionsExpression(optionsExp, selectElement, scope) {
@@ -406,8 +406,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
// we can't just jqLite('<option>') since jqLite is not smart enough
// to create it in <select> and IE barfs otherwise.
var optionTemplate = document.createElement('option'),
optGroupTemplate = document.createElement('optgroup');
var optionTemplate = window.document.createElement('option'),
optGroupTemplate = window.document.createElement('optgroup');
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
@@ -432,7 +432,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
var options;
var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
// This stores the newly created options before they are appended to the select.
// Since the contents are removed from the fragment when it is appended,
// we only need to create it once.
var listFragment = $document[0].createDocumentFragment();
var renderEmptyOption = function() {
if (!providedEmptyOption) {
@@ -467,7 +470,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
selectCtrl.writeValue = function writeNgOptionsValue(value) {
var option = options.getOptionFromViewValue(value);
if (option && !option.disabled) {
if (option) {
// Don't update the option when it is already selected.
// For example, the browser will select the first option by default. In that case,
// most properties are set automatically - except the `selected` attribute, which we
@@ -529,7 +532,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
if (value) {
value.forEach(function(item) {
var option = options.getOptionFromViewValue(item);
if (option && !option.disabled) option.element.selected = true;
if (option) option.element.selected = true;
});
}
};
@@ -581,6 +584,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
emptyOption = jqLite(optionTemplate.cloneNode(false));
}
selectElement.empty();
// We need to do this here to ensure that the options object is defined
// when we first hit it in writeNgOptionsValue
updateOptions();
@@ -590,6 +595,12 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
// ------------------------------------------------------------------ //
function addOptionElement(option, parent) {
var optionElement = optionTemplate.cloneNode(false);
parent.appendChild(optionElement);
updateOptionElement(option, optionElement);
}
function updateOptionElement(option, element) {
option.element = element;
@@ -606,133 +617,66 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
if (option.value !== element.value) element.value = option.selectValue;
}
function addOrReuseElement(parent, current, type, templateElement) {
var element;
// Check whether we can reuse the next element
if (current && lowercase(current.nodeName) === type) {
// The next element is the right type so reuse it
element = current;
} else {
// The next element is not the right type so create a new one
element = templateElement.cloneNode(false);
if (!current) {
// There are no more elements so just append it to the select
parent.appendChild(element);
} else {
// The next element is not a group so insert the new one
parent.insertBefore(element, current);
}
}
return element;
}
function removeExcessElements(current) {
var next;
while (current) {
next = current.nextSibling;
jqLiteRemove(current);
current = next;
}
}
function skipEmptyAndUnknownOptions(current) {
var emptyOption_ = emptyOption && emptyOption[0];
var unknownOption_ = unknownOption && unknownOption[0];
// We cannot rely on the extracted empty option being the same as the compiled empty option,
// because the compiled empty option might have been replaced by a comment because
// it had an "element" transclusion directive on it (such as ngIf)
if (emptyOption_ || unknownOption_) {
while (current &&
(current === emptyOption_ ||
current === unknownOption_ ||
current.nodeType === NODE_TYPE_COMMENT ||
(nodeName_(current) === 'option' && current.value === ''))) {
current = current.nextSibling;
}
}
return current;
}
function updateOptions() {
var previousValue = options && selectCtrl.readValue();
// We must remove all current options, but cannot simply set innerHTML = null
// since the providedEmptyOption might have an ngIf on it that inserts comments which we
// must preserve.
// Instead, iterate over the current option elements and remove them or their optgroup
// parents
if (options) {
for (var i = options.items.length - 1; i >= 0; i--) {
var option = options.items[i];
if (option.group) {
jqLiteRemove(option.element.parentNode);
} else {
jqLiteRemove(option.element);
}
}
}
options = ngOptions.getOptions();
var groupMap = {};
var currentElement = selectElement[0].firstChild;
var groupElementMap = {};
// Ensure that the empty option is always there if it was explicitly provided
if (providedEmptyOption) {
selectElement.prepend(emptyOption);
}
currentElement = skipEmptyAndUnknownOptions(currentElement);
options.items.forEach(function updateOption(option) {
var group;
options.items.forEach(function addOption(option) {
var groupElement;
var optionElement;
if (isDefined(option.group)) {
// This option is to live in a group
// See if we have already created this group
group = groupMap[option.group];
groupElement = groupElementMap[option.group];
if (!group) {
if (!groupElement) {
// We have not already created this group
groupElement = addOrReuseElement(selectElement[0],
currentElement,
'optgroup',
optGroupTemplate);
// Move to the next element
currentElement = groupElement.nextSibling;
groupElement = optGroupTemplate.cloneNode(false);
listFragment.appendChild(groupElement);
// Update the label on the group element
groupElement.label = option.group;
// Store it for use later
group = groupMap[option.group] = {
groupElement: groupElement,
currentOptionElement: groupElement.firstChild
};
groupElementMap[option.group] = groupElement;
}
// So now we have a group for this option we add the option to the group
optionElement = addOrReuseElement(group.groupElement,
group.currentOptionElement,
'option',
optionTemplate);
updateOptionElement(option, optionElement);
// Move to the next element
group.currentOptionElement = optionElement.nextSibling;
addOptionElement(option, groupElement);
} else {
// This option is not in a group
optionElement = addOrReuseElement(selectElement[0],
currentElement,
'option',
optionTemplate);
updateOptionElement(option, optionElement);
// Move to the next element
currentElement = optionElement.nextSibling;
addOptionElement(option, listFragment);
}
});
// Now remove all excess options and group
Object.keys(groupMap).forEach(function(key) {
removeExcessElements(groupMap[key].currentOptionElement);
});
removeExcessElements(currentElement);
selectElement[0].appendChild(listFragment);
ngModelCtrl.$render();
+1 -1
View File
@@ -34,7 +34,7 @@ var SelectController =
//
// We can't just jqLite('<option>') since jqLite is not smart enough
// to create it in <select> and IE barfs otherwise.
self.unknownOption = jqLite(document.createElement('option'));
self.unknownOption = jqLite(window.document.createElement('option'));
self.renderUnknownOption = function(val) {
var unknownVal = '? ' + hashKey(val) + ' ?';
self.unknownOption.val(unknownVal);
+3 -1
View File
@@ -93,7 +93,9 @@ function currencyFilter($locale) {
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
* If this is not provided then the fraction size is computed from the current locale's number
* formatting pattern. In the case of the default locale, it will be 3.
* @returns {string} Number rounded to fractionSize and places a “,” after each third digit.
* @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current
* locale (e.g., in the en_US locale it will have "." as the decimal separator and
* include "," group separators after each third digit).
*
* @example
<example module="numberFilterExample">
+27 -1
View File
@@ -801,6 +801,12 @@ function $HttpProvider() {
* - **headers** `{Object}` Map of strings or functions which return strings representing
* HTTP headers to send to the server. If the return value of a function is null, the
* header will not be sent. Functions accept a config object as an argument.
* - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object.
* To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`.
* The handler will be called in the context of a `$apply` block.
* - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload
* object. To bind events to the XMLHttpRequest object, use `eventHandlers`.
* The handler will be called in the context of a `$apply` block.
* - **xsrfHeaderName** `{string}` Name of HTTP header to populate with the XSRF token.
* - **xsrfCookieName** `{string}` Name of cookie containing the XSRF token.
* - **transformRequest**
@@ -1259,11 +1265,31 @@ function $HttpProvider() {
}
$httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
config.withCredentials, config.responseType);
config.withCredentials, config.responseType,
createApplyHandlers(config.eventHandlers),
createApplyHandlers(config.uploadEventHandlers));
}
return promise;
function createApplyHandlers(eventHandlers) {
if (eventHandlers) {
var applyHandlers = {};
forEach(eventHandlers, function(eventHandler, key) {
applyHandlers[key] = function() {
if (useApplyAsync) {
$rootScope.$applyAsync(eventHandler);
} else if ($rootScope.$$phase) {
eventHandler();
} else {
$rootScope.$apply(eventHandler);
}
};
});
return applyHandlers;
}
}
/**
* Callback registered to $httpBackend():
+9 -1
View File
@@ -54,7 +54,7 @@ function $HttpBackendProvider() {
function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
// TODO(vojta): fix the signature
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
$browser.$$incOutstandingRequestCount();
url = url || $browser.url();
@@ -114,6 +114,14 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
xhr.onerror = requestError;
xhr.onabort = requestError;
forEach(eventHandlers, function(value, key) {
xhr.addEventListener(key, value);
});
forEach(uploadEventHandlers, function(value, key) {
xhr.upload.addEventListener(key, value);
});
if (withCredentials) {
xhr.withCredentials = true;
}
+86 -8
View File
@@ -150,7 +150,7 @@ Lexer.prototype = {
this.readString(ch);
} else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
this.readNumber();
} else if (this.isIdent(ch)) {
} else if (this.isIdentifierStart(this.peekMultichar())) {
this.readIdent();
} else if (this.is(ch, '(){}[].,;:?')) {
this.tokens.push({index: this.index, text: ch});
@@ -194,12 +194,49 @@ Lexer.prototype = {
ch === '\n' || ch === '\v' || ch === '\u00A0');
},
isIdent: function(ch) {
isIdentifierStart: function(ch) {
return this.options.isIdentifierStart ?
this.options.isIdentifierStart(ch, this.codePointAt(ch)) :
this.isValidIdentifierStart(ch);
},
isValidIdentifierStart: function(ch) {
return ('a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z' ||
'_' === ch || ch === '$');
},
isIdentifierContinue: function(ch) {
return this.options.isIdentifierContinue ?
this.options.isIdentifierContinue(ch, this.codePointAt(ch)) :
this.isValidIdentifierContinue(ch);
},
isValidIdentifierContinue: function(ch, cp) {
return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch);
},
codePointAt: function(ch) {
if (ch.length === 1) return ch.charCodeAt(0);
/*jshint bitwise: false*/
return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00;
/*jshint bitwise: true*/
},
peekMultichar: function() {
var ch = this.text.charAt(this.index);
var peek = this.peek();
if (!peek) {
return ch;
}
var cp1 = ch.charCodeAt(0);
var cp2 = peek.charCodeAt(0);
if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
return ch + peek;
}
return ch;
},
isExpOperator: function(ch) {
return (ch === '-' || ch === '+' || this.isNumber(ch));
},
@@ -248,12 +285,13 @@ Lexer.prototype = {
readIdent: function() {
var start = this.index;
this.index += this.peekMultichar().length;
while (this.index < this.text.length) {
var ch = this.text.charAt(this.index);
if (!(this.isIdent(ch) || this.isNumber(ch))) {
var ch = this.peekMultichar();
if (!this.isIdentifierContinue(ch)) {
break;
}
this.index++;
this.index += ch.length;
}
this.tokens.push({
index: start,
@@ -1183,7 +1221,13 @@ ASTCompiler.prototype = {
},
nonComputedMember: function(left, right) {
return left + '.' + right;
var SAFE_IDENTIFIER = /[$_a-zA-Z][$_a-zA-Z0-9]*/;
var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g;
if (SAFE_IDENTIFIER.test(right)) {
return left + '.' + right;
} else {
return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]';
}
},
computedMember: function(left, right) {
@@ -1746,6 +1790,7 @@ function $ParseProvider() {
'null': null,
'undefined': undefined
};
var identStart, identContinue;
/**
* @ngdoc method
@@ -1762,17 +1807,50 @@ function $ParseProvider() {
literals[literalName] = literalValue;
};
/**
* @ngdoc method
* @name $parseProvider#setIdentifierFns
* @description
*
* Allows defining the set of characters that are allowed in Angular expressions. The function
* `identifierStart` will get called to know if a given character is a valid character to be the
* first character for an identifier. The function `identifierContinue` will get called to know if
* a given character is a valid character to be a follow-up identifier character. The functions
* `identifierStart` and `identifierContinue` will receive as arguments the single character to be
* identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
* mind that the `string` parameter can be two characters long depending on the character
* representation. It is expected for the function to return `true` or `false`, whether that
* character is allowed or not.
*
* Since this function will be called extensivelly, keep the implementation of these functions fast,
* as the performance of these functions have a direct impact on the expressions parsing speed.
*
* @param {function=} identifierStart The function that will decide whether the given character is
* a valid identifier start character.
* @param {function=} identifierContinue The function that will decide whether the given character is
* a valid identifier continue character.
*/
this.setIdentifierFns = function(identifierStart, identifierContinue) {
identStart = identifierStart;
identContinue = identifierContinue;
return this;
};
this.$get = ['$filter', function($filter) {
var noUnsafeEval = csp().noUnsafeEval;
var $parseOptions = {
csp: noUnsafeEval,
expensiveChecks: false,
literals: copy(literals)
literals: copy(literals),
isIdentifierStart: isFunction(identStart) && identStart,
isIdentifierContinue: isFunction(identContinue) && identContinue
},
$parseOptionsExpensive = {
csp: noUnsafeEval,
expensiveChecks: true,
literals: copy(literals)
literals: copy(literals),
isIdentifierStart: isFunction(identStart) && identStart,
isIdentifierContinue: isFunction(identContinue) && identContinue
};
var runningChecksEnabled = false;
+1 -1
View File
@@ -6,7 +6,7 @@
// doesn't know about mocked locations and resolves URLs to the real document - which is
// exactly the behavior needed here. There is little value is mocking these out for this
// service.
var urlParsingNode = document.createElement("a");
var urlParsingNode = window.document.createElement("a");
var originUrl = urlResolve(window.location.href);
+2 -1
View File
@@ -2,8 +2,9 @@
"extends": "../../.jshintrc-base",
"maxlen": false, /* ngAnimate docs contain wide tables */
"newcap": false,
"browser": true,
"globals": {
"window": false,
"angular": false,
"noop": false,
+49 -17
View File
@@ -174,7 +174,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
var contains = Node.prototype.contains || function(arg) {
var contains = window.Node.prototype.contains || function(arg) {
// jshint bitwise: false
return this === arg || !!(this.compareDocumentPosition(arg) & 16);
// jshint bitwise: true
@@ -199,6 +199,23 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return matches;
}
function filterFromRegistry(list, matchContainer, matchCallback) {
var containerNode = extractElementNode(matchContainer);
return list.filter(function(entry) {
var isMatch = entry.node === containerNode &&
(!matchCallback || entry.callback === matchCallback);
return !isMatch;
});
}
function cleanupEventListeners(phase, element) {
if (phase === 'close' && !element[0].parentNode) {
// If the element is not attached to a parentNode, it has been removed by
// the domOperation, and we can safely remove the event callbacks
$animate.off(element);
}
}
var $animate = {
on: function(event, container, callback) {
var node = extractElementNode(container);
@@ -210,26 +227,33 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
// Remove the callback when the element is removed from the DOM
jqLite(container).on('$destroy', function() {
$animate.off(event, container, callback);
var animationDetails = activeAnimationsLookup.get(node);
if (!animationDetails) {
// If there's an animation ongoing, the callback calling code will remove
// the event listeners. If we'd remove here, the callbacks would be removed
// before the animation ends
$animate.off(event, container, callback);
}
});
},
off: function(event, container, callback) {
if (arguments.length === 1 && !angular.isString(arguments[0])) {
container = arguments[0];
for (var eventType in callbackRegistry) {
callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container);
}
return;
}
var entries = callbackRegistry[event];
if (!entries) return;
callbackRegistry[event] = arguments.length === 1
? null
: filterFromRegistry(entries, container, callback);
function filterFromRegistry(list, matchContainer, matchCallback) {
var containerNode = extractElementNode(matchContainer);
return list.filter(function(entry) {
var isMatch = entry.node === containerNode &&
(!matchCallback || entry.callback === matchCallback);
return !isMatch;
});
}
},
pin: function(element, parentElement) {
@@ -343,12 +367,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
var documentHidden = $document[0].hidden;
// this is a hard disable of all animations for the application or on
// the element itself, therefore there is no need to continue further
// past this point if not enabled
// Animations are also disabled if the document is currently hidden (page is not visible
// to the user), because browsers slow down or do not flush calls to requestAnimationFrame
var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node);
var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
var hasExistingAnimation = !!existingAnimation.state;
@@ -359,7 +385,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
if (skipAnimations) {
// Callbacks should fire even if the document is hidden (regression fix for issue #14120)
if (documentHidden) notifyProgress(runner, event, 'start');
close();
if (documentHidden) notifyProgress(runner, event, 'close');
return runner;
}
@@ -509,6 +538,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
markElementAnimationState(element, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options);
// this will update the runner's flow-control events based on
// the `realRunner` object.
runner.setHost(realRunner);
notifyProgress(runner, event, 'start', {});
realRunner.done(function(status) {
close(!status);
var animationDetails = activeAnimationsLookup.get(node);
@@ -517,11 +551,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
notifyProgress(runner, event, 'close', {});
});
// this will update the runner's flow-control events based on
// the `realRunner` object.
runner.setHost(realRunner);
notifyProgress(runner, event, 'start', {});
});
return runner;
@@ -538,7 +567,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
forEach(callbacks, function(callback) {
callback(element, phase, data);
});
cleanupEventListeners(phase, element);
});
} else {
cleanupEventListeners(phase, element);
}
});
runner.progress(event, phase, data);
+1 -1
View File
@@ -130,7 +130,7 @@
* <div ng-show="bool" class="fade">
* Show and hide me
* </div>
* <button ng-click="bool=true">Toggle</button>
* <button ng-click="bool=!bool">Toggle</button>
*
* <style>
* .fade.ng-hide {
+10 -3
View File
@@ -16,7 +16,7 @@
*
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
* directives are supported:
* `ngModel`, `ngChecked`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
* `ngDblClick`, and `ngMessages`.
*
* Below is a more detailed breakdown of the attributes handled by ngAria:
@@ -25,8 +25,9 @@
* |---------------------------------------------|----------------------------------------------------------------------------------------|
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
* | {@link ng.directive:ngRequired ngRequired} | aria-required |
* | {@link ng.directive:ngChecked ngChecked} | aria-checked |
* | {@link ng.directive:ngRequired ngRequired} | aria-required
* | {@link ng.directive:ngChecked ngChecked} | aria-checked
* | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly ||
* | {@link ng.directive:ngValue ngValue} | aria-checked |
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
@@ -91,6 +92,7 @@ function $AriaProvider() {
var config = {
ariaHidden: true,
ariaChecked: true,
ariaReadonly: true,
ariaDisabled: true,
ariaRequired: true,
ariaInvalid: true,
@@ -108,6 +110,7 @@ function $AriaProvider() {
*
* - **ariaHidden** `{boolean}` Enables/disables aria-hidden tags
* - **ariaChecked** `{boolean}` Enables/disables aria-checked tags
* - **ariaReadonly** `{boolean}` Enables/disables aria-readonly tags
* - **ariaDisabled** `{boolean}` Enables/disables aria-disabled tags
* - **ariaRequired** `{boolean}` Enables/disables aria-required tags
* - **ariaInvalid** `{boolean}` Enables/disables aria-invalid tags
@@ -170,6 +173,7 @@ function $AriaProvider() {
* The full list of directives that interface with ngAria:
* * **ngModel**
* * **ngChecked**
* * **ngReadonly**
* * **ngRequired**
* * **ngDisabled**
* * **ngValue**
@@ -209,6 +213,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
.directive('ngChecked', ['$aria', function($aria) {
return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false);
}])
.directive('ngReadonly', ['$aria', function($aria) {
return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false);
}])
.directive('ngRequired', ['$aria', function($aria) {
return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false);
}])
+31 -3
View File
@@ -1,8 +1,36 @@
/**
* @ngdoc module
* @name ngComponentRouter
* @installation
* ## Installation
*
* Currently, the **Component Router** module must be installed via `npm`, it is not yet available
* on Bower or the Google CDN.
*
* ```bash
* npm install @angular/router --save
* ```
*
* Include `angular_1_router.js` in your HTML:
* ```html
* <script src="/node_modules/@angular/router/angular1/angular_1_router.js"></script>
*```
*
* You also need to include ES6 shims to support running on Internet Explorer:
* ```html
* <!-- IE required polyfills, in this exact order -->
* <script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.33.3/es6-shim.min.js"></script>
* <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.20/system-polyfills.js"></script>
* <script src="https://npmcdn.com/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
* ```
*
* Then load the module in your application by adding it as a dependent module:
*
* ```js
* angular.module('app', ['ngComponentRouter']);
* ```
*
* @description
* The new Angular Router
*/
/**
@@ -41,7 +69,7 @@
*
* There is only one instance of this type in a Component Router application injectable as the
* {@link $rootRouter} service. This **Router** is associate with the **Top Level Component**
* ({@link $routerRootComponent}). It acts as the connection betweent he **Routers** and the **Location**.
* ({@link $routerRootComponent}). It acts as the connection between the **Routers** and the **Location**.
*/
/**
@@ -62,7 +90,7 @@
* @name RouteDefinition
* @description
*
* Each item in a the **RouteConfig** for a **Routing Component** is an instance of
* Each item in the **RouteConfig** for a **Routing Component** is an instance of
* this type. It can have the following properties:
*
* * `path` or (`regex` and `serializer) - defines how to recognize and generate this route
+3 -2
View File
@@ -1,7 +1,8 @@
{
"extends": "../../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
"angular": false
}
}
}
+3 -2
View File
@@ -2,9 +2,10 @@
"extends": "../../.jshintrc-base",
"bitwise": false, /* locale files use bitwise operators */
"maxlen": false, /* locale files are generated from a 3rd party library that has long lines */
"browser": true,
"globals": {
"window": false,
"angular": false
},
"-W041": false
}
}
+2 -1
View File
@@ -1,7 +1,8 @@
{
"extends": "../../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
"angular": false,
"goog": false // see src/module_closure.prefix
}
+3 -2
View File
@@ -1,9 +1,10 @@
{
"extends": "../../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
"angular": false,
"expect": false,
"jQuery": false
}
}
}
+50 -18
View File
@@ -1321,12 +1321,15 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
}
// TODO(vojta): change params to: method, url, data, headers, callback
function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType) {
function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
var xhr = new MockXhr(),
expectation = expectations[0],
wasExpected = false;
xhr.$$events = eventHandlers;
xhr.upload.$$events = uploadEventHandlers;
function prettyPrint(data) {
return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
? data
@@ -1416,12 +1419,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* order to change how a matched request is handled.
*
* - respond
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers, params)}`
* ```js
* {function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers, params)}
* ```
* The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
* headers (Object), and the text for the status (string). The respond method returns the
* `requestHandler` object for possible overrides.
* return an array containing response status (number), response data (Array|Object|string),
* response headers (Object), and the text for the status (string). The respond method returns
* the `requestHandler` object for possible overrides.
*/
$httpBackend.when = function(method, url, data, headers, keys) {
var definition = new MockHttpExpectation(method, url, data, headers, keys),
@@ -1606,12 +1611,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* order to change how a matched request is handled.
*
* - respond
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers, params)}`
* ```
* { function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers, params)}
* ```
* The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
* headers (Object), and the text for the status (string). The respond method returns the
* `requestHandler` object for possible overrides.
* return an array containing response status (number), response data (Array|Object|string),
* response headers (Object), and the text for the status (string). The respond method returns
* the `requestHandler` object for possible overrides.
*/
$httpBackend.expect = function(method, url, data, headers, keys) {
var expectation = new MockHttpExpectation(method, url, data, headers, keys),
@@ -2005,6 +2012,20 @@ function MockXhr() {
};
this.abort = angular.noop;
// This section simulates the events on a real XHR object (and the upload object)
// When we are testing $httpBackend (inside the angular project) we make partial use of this
// but store the events directly ourselves on `$$events`, instead of going through the `addEventListener`
this.$$events = {};
this.addEventListener = function(name, listener) {
if (angular.isUndefined(this.$$events[name])) this.$$events[name] = [];
this.$$events[name].push(listener);
};
this.upload = {
$$events: {},
addEventListener: this.addEventListener
};
}
@@ -2174,8 +2195,8 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
* A service that can be used to create instances of component controllers.
* <div class="alert alert-info">
* Be aware that the controller will be instantiated and attached to the scope as specified in
* the component definition object. That means that you must always provide a `$scope` object
* in the `locals` param.
* the component definition object. If you do not provide a `$scope` object in the `locals` param
* then the helper will create a new isolated scope as a child of `$rootScope`.
* </div>
* @param {string} componentName the name of the component whose controller we want to instantiate
* @param {Object} locals Injection locals for Controller.
@@ -2185,7 +2206,7 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
* @return {Object} Instance of requested controller.
*/
angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) {
this.$get = ['$controller','$injector', function($controller,$injector) {
this.$get = ['$controller','$injector', '$rootScope', function($controller, $injector, $rootScope) {
return function $componentController(componentName, locals, bindings, ident) {
// get all directives associated to the component name
var directives = $injector.get(componentName + 'Directive');
@@ -2203,6 +2224,9 @@ angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compi
}
// get the info of the component
var directiveInfo = candidateDirectives[0];
// create a scope if needed
locals = locals || {};
locals.$scope = locals.$scope || $rootScope.$new(true);
return $controller(directiveInfo.controller, locals, bindings, ident || directiveInfo.controllerAs);
};
}];
@@ -2326,11 +2350,13 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* `respond` or `passThrough` again in order to change how a matched request is handled.
*
* - respond
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers, params)}`
* ```
* { function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers, params)}
* ```
* The respond method takes a set of static data to be returned or a function that can return
* an array containing response status (number), response data (string), response headers
* (Object), and the text for the status (string).
* an array containing response status (number), response data (Array|Object|string), response
* headers (Object), and the text for the status (string).
* - passThrough `{function()}` Any request matching a backend definition with
* `passThrough` handler will be passed through to the real backend (an XHR request will be made
* to the server.)
@@ -2892,6 +2918,12 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
window.inject = angular.mock.inject = function() {
var blockFns = Array.prototype.slice.call(arguments, 0);
var errorForStack = new Error('Declaration Location');
// IE10+ and PhanthomJS do not set stack trace information, until the error is thrown
if (!errorForStack.stack) {
try {
throw errorForStack;
} catch (e) {}
}
return wasInjectorCreated() ? workFn.call(currentSpec) : workFn;
/////////////////////
function workFn() {
+45
View File
@@ -0,0 +1,45 @@
'use strict';
/**
* @ngdoc module
* @name ngParseExt
* @packageName angular-parse-ext
* @description
*
* # ngParseExt
*
* The `ngParseExt` module provides functionality to allow Unicode characters in
* identifiers inside Angular expressions.
*
*
* <div doc-module-components="ngParseExt"></div>
*
* This module allows the usage of any identifier that follows ES6 identifier naming convention
* to be used as an identifier in an Angular expression. ES6 delegates some of the identifier
* rules definition to Unicode, this module uses ES6 and Unicode 8.0 identifiers convention.
*
*/
/* global angularParseExtModule: true,
IDS_Y,
IDC_Y
*/
function isValidIdentifierStart(ch, cp) {
return ch === '$' ||
ch === '_' ||
IDS_Y(cp);
}
function isValidIdentifierContinue(ch, cp) {
return ch === '$' ||
ch === '_' ||
cp === 0x200C || // <ZWNJ>
cp === 0x200D || // <ZWJ>
IDC_Y(cp);
}
angular.module('ngParseExt', [])
.config(['$parseProvider', function($parseProvider) {
$parseProvider.setIdentifierFns(isValidIdentifierStart, isValidIdentifierContinue);
}]);
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,7 +1,8 @@
{
"extends": "../../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
"angular": false
}
}
}
+3 -2
View File
@@ -1,8 +1,9 @@
{
"extends": "../../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
"angular": false,
"ngRouteModule": false
}
}
}
+7
View File
@@ -26,6 +26,13 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
*
* The enter and leave animation occur concurrently.
*
* @knownIssue If `ngView` is contained in an asynchronously loaded template (e.g. in another
* directive's templateUrl or in a template loaded using `ngInclude`), then you need to
* make sure that `$route` is instantiated in time to capture the initial
* `$locationChangeStart` event and load the appropriate view. One way to achieve this
* is to have it as a dependency in a `.run` block:
* `myModule.run(['$route', function() {}]);`
*
* @scope
* @priority 400
* @param {string=} onload Expression to evaluate whenever the view updates.
+1 -5
View File
@@ -17,11 +17,7 @@
*/
/* global -ngRouteModule */
var ngRouteModule = angular.module('ngRoute', ['ng']).
provider('$route', $RouteProvider).
// Ensure `$route` will be instantiated in time to capture the initial
// `$locationChangeSuccess` event. This is necessary in case `ngView` is
// included in an asynchronously loaded template.
run(['$route', angular.noop]),
provider('$route', $RouteProvider),
$routeMinErr = angular.$$minErr('ngRoute');
/**
+3 -2
View File
@@ -1,8 +1,9 @@
{
"extends": "../../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
"angular": false,
"htmlSanitizeWriter": false
}
}
}
+2 -2
View File
@@ -344,7 +344,7 @@ function htmlParser(html, handler) {
mXSSAttempts--;
// strip custom-namespaced attributes on IE<=11
if (document.documentMode <= 11) {
if (window.document.documentMode) {
stripCustomNsAttrs(inertBodyElement);
}
html = inertBodyElement.innerHTML; //trigger mXSS
@@ -484,7 +484,7 @@ function htmlSanitizeWriter(buf, uriValidator) {
* @param node Root element to process
*/
function stripCustomNsAttrs(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.nodeType === window.Node.ELEMENT_NODE) {
var attrs = node.attributes;
for (var i = 0, l = attrs.length; i < l; i++) {
var attrNode = attrs[i];
+3 -2
View File
@@ -1,7 +1,8 @@
{
"extends": "../../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
"angular": false,
"includes": false,
"asyncForEach": false,
@@ -19,4 +20,4 @@
"$runner": false,
"callerFile": false
}
}
}
+1 -1
View File
@@ -104,7 +104,7 @@ angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
*/
angular.scenario.setUpAndRun = function(config) {
var href = window.location.href;
var body = _jQuery(document.body);
var body = _jQuery(window.document.body);
var output = [];
var objModel = new angular.scenario.ObjectModel($runner);
+5 -5
View File
@@ -5,7 +5,7 @@
(function(previousOnLoad) {
var prefix = (function() {
var filename = /(.*\/)angular-bootstrap.js(#(.*))?/;
var scripts = document.getElementsByTagName("script");
var scripts = window.document.getElementsByTagName("script");
for (var j = 0; j < scripts.length; j++) {
var src = scripts[j].src;
if (src && src.match(filename)) {
@@ -16,11 +16,11 @@
})();
function addScript(path) {
document.write('<script type="text/javascript" src="' + prefix + path + '"></script>');
window.document.write('<script type="text/javascript" src="' + prefix + path + '"></script>');
}
function addCSS(path) {
document.write('<link rel="stylesheet" type="text/css" href="' + prefix + path + '"/>');
window.document.write('<link rel="stylesheet" type="text/css" href="' + prefix + path + '"/>');
}
window.onload = function() {
@@ -32,7 +32,7 @@
addCSS("../../css/angular-scenario.css");
addScript("../../lib/jquery/jquery.js");
document.write(
window.document.write(
'<script type="text/javascript">' +
'var _jQuery = jQuery.noConflict(true);' +
'</script>'
@@ -54,7 +54,7 @@
addScript("output/Xml.js");
// Create the runner (which also sets up the global API)
document.write(
window.document.write(
'<script type="text/javascript">' +
' var $runner = new angular.scenario.Runner(window);' +
'</script>');
+1 -1
View File
@@ -3,5 +3,5 @@
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document){
(function(window){
var _jQuery = window.jQuery.noConflict(true);
+3 -3
View File
@@ -2,7 +2,7 @@ bindJQuery();
publishExternalAPI(angular);
var $runner = new angular.scenario.Runner(window),
scripts = document.getElementsByTagName('script'),
scripts = window.document.getElementsByTagName('script'),
script = scripts[scripts.length - 1],
config = {};
@@ -14,9 +14,9 @@ angular.forEach(script.attributes, function(attr) {
});
if (config.autotest) {
JQLite(document).ready(function() {
JQLite(window.document).ready(function() {
angular.scenario.setUpAndRun(config);
});
}
})(window, document);
})(window);
+8 -8
View File
@@ -61,7 +61,7 @@
evnt = new TransitionEvent(eventType, eventData);
}
catch (e) {
evnt = document.createEvent('TransitionEvent');
evnt = window.document.createEvent('TransitionEvent');
evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
@@ -74,14 +74,14 @@
evnt = new AnimationEvent(eventType, eventData);
}
catch (e) {
evnt = document.createEvent('AnimationEvent');
evnt = window.document.createEvent('AnimationEvent');
evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
} else if (/touch/.test(eventType) && supportsTouchEvents()) {
evnt = createTouchEvent(element, eventType, x, y);
} else {
evnt = document.createEvent('MouseEvents');
evnt = window.document.createEvent('MouseEvents');
x = x || 0;
y = y || 0;
evnt.initMouseEvent(eventType, true, true, window, 0, x, y, x, y, pressed('ctrl'),
@@ -120,12 +120,12 @@
if ('_cached' in supportsTouchEvents) {
return supportsTouchEvents._cached;
}
if (!document.createTouch || !document.createTouchList) {
if (!window.document.createTouch || !window.document.createTouchList) {
supportsTouchEvents._cached = false;
return false;
}
try {
document.createEvent('TouchEvent');
window.document.createEvent('TouchEvent');
} catch (e) {
supportsTouchEvents._cached = false;
return false;
@@ -135,12 +135,12 @@
}
function createTouchEvent(element, eventType, x, y) {
var evnt = new Event(eventType);
var evnt = new window.Event(eventType);
x = x || 0;
y = y || 0;
var touch = document.createTouch(window, element, Date.now(), x, y, x, y);
var touches = document.createTouchList(touch);
var touch = window.document.createTouch(window, element, Date.now(), x, y, x, y);
var touches = window.document.createTouchList(touch);
evnt.touches = touches;
+1 -1
View File
@@ -199,7 +199,7 @@ angular.scenario.dsl('binding', function() {
*/
angular.scenario.dsl('input', function() {
var chain = {};
var supportInputEvent = 'oninput' in document.createElement('div') && !(msie && msie <= 11);
var supportInputEvent = 'oninput' in window.document.createElement('div') && !msie;
chain.enter = function(value, event) {
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'",
+3 -2
View File
@@ -1,8 +1,9 @@
{
"extends": "../../.jshintrc-base",
"browser": true,
"globals": {
"window": false,
"angular": false,
"ngTouch": false
}
}
}
+6
View File
@@ -241,6 +241,12 @@ describe('injector', function() {
expect($f_n0.$inject).toEqual(['$a_']);
});
it('should handle functions with overridden toString', function() {
function fn(a) {}
fn.toString = function() { return 'fn'; };
expect(annotate(fn)).toEqual(['a']);
expect(fn.$inject).toEqual(['a']);
});
it('should throw on non function arg', function() {
expect(function() {
+58 -36
View File
@@ -131,25 +131,38 @@ beforeEach(function() {
return {
compare: function(actual) {
if (arguments.length > 1) {
throw new Error('toHaveBeenCalledOnce does not take arguments, use toHaveBeenCalledWith');
throw new Error('`toHaveBeenCalledOnce` does not take arguments, ' +
'use `toHaveBeenCalledOnceWith`');
}
if (!jasmine.isSpy(actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(actual) + '.');
}
var count = actual.calls.count();
var pass = count === 1;
var message = function() {
var msg = 'Expected spy ' + actual.identity() + ' to have been called once, but was ',
count = this.actual.calls.count();
return [
count === 0 ? msg + 'never called.' :
msg + 'called ' + count + ' times.',
msg.replace('to have', 'not to have') + 'called once.'
];
var msg = 'Expected spy ' + actual.and.identity() + (pass ? ' not ' : ' ') +
'to have been called once, but ';
switch (count) {
case 0:
msg += 'it was never called.';
break;
case 1:
msg += 'it was called once.';
break;
default:
msg += 'it was called ' + count + ' times.';
break;
}
return msg;
};
return {
pass: actual.calls.count() == 1,
pass: pass,
message: message
};
}
@@ -158,43 +171,52 @@ beforeEach(function() {
toHaveBeenCalledOnceWith: function(util, customEqualityTesters) {
return {
compare: function(actual) {
var expectedArgs = Array.prototype.slice.call(arguments, 1);
compare: generateCompare(false),
negativeCompare: generateCompare(true)
};
function generateCompare(isNot) {
return function(actual) {
if (!jasmine.isSpy(actual)) {
throw new Error('Expected a spy, but got ' + jasmine.pp(actual) + '.');
}
var message = function() {
if (actual.calls.count() != 1) {
if (actual.calls.count() === 0) {
return [
'Expected spy ' + actual.identity() + ' to have been called once with ' +
jasmine.pp(expectedArgs) + ' but it was never called.',
'Expected spy ' + actual.identity() + ' not to have been called with ' +
jasmine.pp(expectedArgs) + ' but it was.'
];
}
return [
'Expected spy ' + actual.identity() + ' to have been called once with ' +
jasmine.pp(expectedArgs) + ' but it was called ' + actual.calls.count() + ' times.',
'Expected spy ' + actual.identity() + ' not to have been called once with ' +
jasmine.pp(expectedArgs) + ' but it was.'
];
var expectedArgs = Array.prototype.slice.call(arguments, 1);
var actualCount = actual.calls.count();
var actualArgs = actualCount && actual.calls.argsFor(0);
var pass = (actualCount === 1) && util.equals(actualArgs, expectedArgs);
if (isNot) pass = !pass;
var message = function() {
var msg = 'Expected spy ' + actual.and.identity() + (isNot ? ' not ' : ' ') +
'to have been called once with ' + jasmine.pp(expectedArgs) + ', but ';
if (isNot) {
msg += 'it was.';
} else {
return [
'Expected spy ' + actual.identity() + ' to have been called once with ' +
jasmine.pp(expectedArgs) + ' but was called with ' + jasmine.pp(actual.calls.argsFor(0)),
'Expected spy ' + actual.identity() + ' not to have been called once with ' +
jasmine.pp(expectedArgs) + ' but was called with ' + jasmine.pp(actual.calls.argsFor(0))
];
switch (actualCount) {
case 0:
msg += 'it was never called.';
break;
case 1:
msg += 'it was called with ' + jasmine.pp(actualArgs) + '.';
break;
default:
msg += 'it was called ' + actualCount + ' times.';
break;
}
}
return msg;
};
return {
pass: actual.calls.count() === 1 && util.equals(actual.calls.argsFor(0), expectedArgs),
pass: pass,
message: message
};
}
};
};
}
},
toBeOneOf: function() {
+2 -2
View File
@@ -968,7 +968,7 @@ describe('jqLite', function() {
describe('text', function() {
it('should return null on empty', function() {
it('should return `""` on empty', function() {
expect(jqLite().length).toEqual(0);
expect(jqLite().text()).toEqual('');
});
@@ -1036,7 +1036,7 @@ describe('jqLite', function() {
describe('html', function() {
it('should return null on empty', function() {
it('should return `undefined` on empty', function() {
expect(jqLite().length).toEqual(0);
expect(jqLite().html()).toEqual(undefined);
});
+3 -2
View File
@@ -1,3 +1,4 @@
// When runnint the modules test, then the page should not be bootstrapped.
window.name = "NG_DEFER_BOOTSTRAP!";
'use strict';
// When running the modules test, then the page should not be bootstrapped.
window.name = "NG_DEFER_BOOTSTRAP!";
+6
View File
@@ -88,6 +88,9 @@ describe('$anchorScroll', function() {
return function($window) {
forEach(elmSpy, function(spy, id) {
var count = map[id] || 0;
// TODO(gkalpak): `toHaveBeenCalledTimes()` works correctly with 0 since
// https://github.com/jasmine/jasmine/commit/342f0eb9a38194ecb8559e7df872c72afc0fe52e
// Fix when we upgrade to a version that contains the fix.
if (count > 0) {
expect(spy).toHaveBeenCalledTimes(count);
} else {
@@ -386,6 +389,9 @@ describe('$anchorScroll', function() {
return function($rootScope, $window) {
inject(expectScrollingTo(identifierCountMap));
// TODO(gkalpak): `toHaveBeenCalledTimes()` works correctly with 0 since
// https://github.com/jasmine/jasmine/commit/342f0eb9a38194ecb8559e7df872c72afc0fe52e
// Fix when we upgrade to a version that contains the fix.
if (list.length > 0) {
expect($window.scrollBy).toHaveBeenCalledTimes(list.length);
} else {
+1 -1
View File
@@ -551,7 +551,7 @@ describe('browser', function() {
// `history.state` in contexts where `$sniffer.history` is false.
var historyStateAccessed = false;
var mockSniffer = {histroy: false};
var mockSniffer = {history: false};
var mockWindow = new MockWindow();
var _state = mockWindow.history.state;
+192 -24
View File
@@ -2711,6 +2711,25 @@ describe('$compile', function() {
expect(checkedVal).toEqual(true);
});
});
it('should handle updates to @ bindings on BOOLEAN attributes', function() {
var componentScope;
module(function($compileProvider) {
$compileProvider.directive('test', function() {
return {
scope: {checked: '@'},
link: function(scope, element, attrs) {
componentScope = scope;
attrs.$set('checked', true);
}
};
});
});
inject(function($compile, $rootScope) {
$compile('<test></test>')($rootScope);
expect(componentScope.checked).toBe(true);
});
});
});
@@ -3667,16 +3686,25 @@ describe('$compile', function() {
// Setup the directive with two bindings
element = $compile('<c1 prop1="val" prop2="val2" other="val3" attr="{{val4}}"></c1>')($rootScope);
// There should be no changes initially
expect(log).toEqual([]);
expect(log).toEqual([
{
prop1: jasmine.objectContaining({currentValue: undefined}),
prop2: jasmine.objectContaining({currentValue: undefined}),
attr: jasmine.objectContaining({currentValue: ''})
}
]);
// Clear the initial changes from the log
log = [];
// Update val to trigger the onChanges
$rootScope.$apply('val = 42');
// Now we should have a single changes entry in the log
expect(log).toEqual([
{
prop1: {previousValue: undefined, currentValue: 42},
prop2: {previousValue: undefined, currentValue: 84}
prop1: jasmine.objectContaining({currentValue: 42}),
prop2: jasmine.objectContaining({currentValue: 84})
}
]);
@@ -3688,8 +3716,8 @@ describe('$compile', function() {
// Now we should have a single changes entry in the log
expect(log).toEqual([
{
prop1: {previousValue: 42, currentValue: 17},
prop2: {previousValue: 84, currentValue: 34}
prop1: jasmine.objectContaining({previousValue: 42, currentValue: 17}),
prop2: jasmine.objectContaining({previousValue: 84, currentValue: 34})
}
]);
@@ -3706,13 +3734,38 @@ describe('$compile', function() {
// onChanges should not have been called
expect(log).toEqual([
{
attr: {previousValue: '', currentValue: '22'}
attr: jasmine.objectContaining({previousValue: '', currentValue: '22'})
}
]);
});
});
it('should trigger `$onChanges` even if the inner value already equals the new outer value', function() {
var log = [];
function TestController() { }
TestController.prototype.$onChanges = function(change) { log.push(change); };
angular.module('my', [])
.component('c1', {
controller: TestController,
bindings: { 'prop1': '<' }
});
module('my');
inject(function($compile, $rootScope) {
element = $compile('<c1 prop1="val"></c1>')($rootScope);
$rootScope.$apply('val = 1');
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: undefined, currentValue: 1})});
element.isolateScope().$ctrl.prop1 = 2;
$rootScope.$apply('val = 2');
expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: 1, currentValue: 2})});
});
});
it('should pass the original value as `previousValue` even if there were multiple changes in a single digest', function() {
var log = [];
function TestController() { }
@@ -3732,13 +3785,15 @@ describe('$compile', function() {
// therefore triggering the thing that this test is hoping to enfore
$rootScope.$watch('a', function(val) { $rootScope.b = val * 2; });
// There should be no changes initially
expect(log).toEqual([]);
expect(log).toEqual([{prop: jasmine.objectContaining({currentValue: undefined})}]);
// Clear the initial values from the log
log = [];
// Update val to trigger the onChanges
$rootScope.$apply('a = 42');
// Now the change should have the real previous value (undefined), not the intermediate one (42)
expect(log).toEqual([{prop: {previousValue: undefined, currentValue: 126}}]);
expect(log).toEqual([{prop: jasmine.objectContaining({currentValue: 126})}]);
// Clear the log
log = [];
@@ -3746,7 +3801,85 @@ describe('$compile', function() {
// Update val to trigger the onChanges
$rootScope.$apply('a = 7');
// Now the change should have the real previous value (126), not the intermediate one, (91)
expect(log).toEqual([{ prop: {previousValue: 126, currentValue: 21}}]);
expect(log).toEqual([{prop: jasmine.objectContaining({previousValue: 126, currentValue: 21})}]);
});
});
it('should trigger an initial onChanges call for each binding with the `isFirstChange()` returning true', function() {
var log = [];
function TestController() { }
TestController.prototype.$onChanges = function(change) { log.push(change); };
angular.module('my', [])
.component('c1', {
controller: TestController,
bindings: { 'prop': '<', attr: '@' }
});
module('my');
inject(function($compile, $rootScope) {
$rootScope.$apply('a = 7');
element = $compile('<c1 prop="a" attr="{{a}}"></c1>')($rootScope);
expect(log).toEqual([
{
prop: jasmine.objectContaining({currentValue: 7}),
attr: jasmine.objectContaining({currentValue: '7'})
}
]);
expect(log[0].prop.isFirstChange()).toEqual(true);
expect(log[0].attr.isFirstChange()).toEqual(true);
log = [];
$rootScope.$apply('a = 9');
expect(log).toEqual([
{
prop: jasmine.objectContaining({previousValue: 7, currentValue: 9}),
attr: jasmine.objectContaining({previousValue: '7', currentValue: '9'})
}
]);
expect(log[0].prop.isFirstChange()).toEqual(false);
expect(log[0].attr.isFirstChange()).toEqual(false);
});
});
it('should trigger an initial onChanges call for each binding even if the hook is defined in the constructor', function() {
var log = [];
function TestController() {
this.$onChanges = function(change) { log.push(change); };
}
angular.module('my', [])
.component('c1', {
controller: TestController,
bindings: { 'prop': '<', attr: '@' }
});
module('my');
inject(function($compile, $rootScope) {
$rootScope.$apply('a = 7');
element = $compile('<c1 prop="a" attr="{{a}}"></c1>')($rootScope);
expect(log).toEqual([
{
prop: jasmine.objectContaining({currentValue: 7}),
attr: jasmine.objectContaining({currentValue: '7'})
}
]);
expect(log[0].prop.isFirstChange()).toEqual(true);
expect(log[0].attr.isFirstChange()).toEqual(true);
log = [];
$rootScope.$apply('a = 10');
expect(log).toEqual([
{
prop: jasmine.objectContaining({previousValue: 7, currentValue: 10}),
attr: jasmine.objectContaining({previousValue: '7', currentValue: '10'})
}
]);
expect(log[0].prop.isFirstChange()).toEqual(false);
expect(log[0].attr.isFirstChange()).toEqual(false);
});
});
@@ -3778,15 +3911,15 @@ describe('$compile', function() {
// Setup two sibling components with bindings that will change
element = $compile('<div><c1 prop="val1"></c1><c2 prop="val2"></c2></div>')($rootScope);
// There should be no changes initially
expect(log).toEqual([]);
// Clear out initial changes
log = [];
// Update val to trigger the onChanges
$rootScope.$apply('val1 = 42; val2 = 17');
expect(log).toEqual([
['TestController1', {prop: {previousValue: undefined, currentValue: 42}}],
['TestController2', {prop: {previousValue: undefined, currentValue: 17}}]
['TestController1', {prop: jasmine.objectContaining({currentValue: 42})}],
['TestController2', {prop: jasmine.objectContaining({currentValue: 17})}]
]);
// A single apply should only trigger three turns of the digest loop
expect(watchCount).toEqual(3);
@@ -3796,11 +3929,13 @@ describe('$compile', function() {
it('should cope with changes occuring inside `$onChanges()` hooks', function() {
var log = [];
function OuterController() { }
function OuterController() {
this.prop1 = 0;
}
OuterController.prototype.$onChanges = function(change) {
log.push(['OuterController', change]);
// Make a change to the inner component
this.b = 72;
this.b = this.prop1 * 2;
};
function InnerController() { }
@@ -3823,15 +3958,15 @@ describe('$compile', function() {
// Setup the directive with two bindings
element = $compile('<outer prop1="a"></outer>')($rootScope);
// There should be no changes initially
expect(log).toEqual([]);
// Clear out initial changes
log = [];
// Update val to trigger the onChanges
$rootScope.$apply('a = 42');
expect(log).toEqual([
['OuterController', {prop1: {previousValue: undefined, currentValue: 42}}],
['InnerController', {prop2: {previousValue: undefined, currentValue: 72}}]
['OuterController', {prop1: jasmine.objectContaining({currentValue: 42})}],
['InnerController', {prop2: jasmine.objectContaining({currentValue: 84})}]
]);
});
});
@@ -10370,15 +10505,15 @@ describe('$compile', function() {
}));
});
it('should add additional annotations to the controller constructor', function() {
var myModule = angular.module('my', []).component('myComponent', {
it('should expose additional annotations on the directive definition object', function() {
angular.module('my', []).component('myComponent', {
$canActivate: 'canActivate',
$routeConfig: 'routeConfig',
$customAnnotation: 'XXX'
});
module('my');
inject(function(myComponentDirective) {
expect(myComponentDirective[0].controller).toEqual(jasmine.objectContaining({
expect(myComponentDirective[0]).toEqual(jasmine.objectContaining({
$canActivate: 'canActivate',
$routeConfig: 'routeConfig',
$customAnnotation: 'XXX'
@@ -10386,6 +10521,39 @@ describe('$compile', function() {
});
});
it('should support custom annotations if the controller is named', function() {
angular.module('my', []).component('myComponent', {
$customAnnotation: 'XXX',
controller: 'SomeNamedController'
});
module('my');
inject(function(myComponentDirective) {
expect(myComponentDirective[0]).toEqual(jasmine.objectContaining({
$customAnnotation: 'XXX'
}));
});
});
it('should provide a new empty controller if none is specified', function() {
angular.
module('my', []).
component('myComponent1', {$customAnnotation1: 'XXX'}).
component('myComponent2', {$customAnnotation2: 'YYY'});
module('my');
inject(function(myComponent1Directive, myComponent2Directive) {
var ctrl1 = myComponent1Directive[0].controller;
var ctrl2 = myComponent2Directive[0].controller;
expect(ctrl1).not.toBe(ctrl2);
expect(ctrl1.$customAnnotation1).toBe('XXX');
expect(ctrl1.$customAnnotation2).toBeUndefined();
expect(ctrl2.$customAnnotation1).toBeUndefined();
expect(ctrl2.$customAnnotation2).toBe('YYY');
});
});
it('should return ddo with reasonable defaults', function() {
angular.module('my', []).component('myComponent', {});
module('my');
+16
View File
@@ -409,6 +409,22 @@ describe('ngClass', function() {
expect(e2.hasClass('even')).toBeTruthy();
expect(e2.hasClass('odd')).toBeFalsy();
}));
it('should support mixed array/object variable with a mutating object',
inject(function($rootScope, $compile) {
element = $compile('<div ng-class="classVar"></div>')($rootScope);
$rootScope.classVar = [{orange: true}];
$rootScope.$digest();
expect(element).toHaveClass('orange');
$rootScope.classVar[0].orange = false;
$rootScope.$digest();
expect(element).not.toHaveClass('orange');
})
);
});
describe('ngClass animations', function() {
+21 -44
View File
@@ -771,7 +771,7 @@ describe('ngOptions', function() {
});
it('should not select disabled options when model changes', function() {
it('should select disabled options when model changes', function() {
scope.options = [
{ name: 'white', value: '#FFFFFF' },
{ name: 'one', value: 1, unavailable: true },
@@ -792,11 +792,14 @@ describe('ngOptions', function() {
scope.$apply('selected = 1');
options = element.find('option');
expect(element.val()).toEqualUnknownValue('?');
expect(options.length).toEqual(5);
expect(options.eq(0).prop('selected')).toEqual(true);
// jQuery returns null for val() when the option is disabled, see
// https://bugs.jquery.com/ticket/13097
expect(element[0].value).toBe('number:1');
expect(options.length).toEqual(4);
expect(options.eq(0).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(true);
expect(options.eq(2).prop('selected')).toEqual(false);
expect(options.eq(4).prop('selected')).toEqual(false);
expect(options.eq(3).prop('selected')).toEqual(false);
});
@@ -816,11 +819,14 @@ describe('ngOptions', function() {
scope.$apply('selected = 1');
var options = element.find('option');
expect(element.val()).toEqualUnknownValue('?');
expect(options.length).toEqual(5);
expect(options.eq(0).prop('selected')).toEqual(true);
// jQuery returns null for val() when the option is disabled, see
// https://bugs.jquery.com/ticket/13097
expect(element[0].value).toBe('number:1');
expect(options.length).toEqual(4);
expect(options.eq(0).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(true);
expect(options.eq(2).prop('selected')).toEqual(false);
expect(options.eq(4).prop('selected')).toEqual(false);
expect(options.eq(3).prop('selected')).toEqual(false);
// Now enable that option
scope.$apply(function() {
@@ -861,7 +867,7 @@ describe('ngOptions', function() {
});
it('should not select disabled options when model changes', function() {
it('should select disabled options when model changes', function() {
scope.options = [
{ name: 'a', value: 0 },
{ name: 'b', value: 1, unavailable: true },
@@ -886,14 +892,14 @@ describe('ngOptions', function() {
scope.$apply('selected = [1,3]');
options = element.find('option');
expect(options.eq(0).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(true);
expect(options.eq(2).prop('selected')).toEqual(false);
expect(options.eq(3).prop('selected')).toEqual(true);
// Now only select the disabled option
scope.$apply('selected = [1]');
expect(options.eq(0).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(true);
expect(options.eq(2).prop('selected')).toEqual(false);
expect(options.eq(3).prop('selected')).toEqual(false);
});
@@ -917,7 +923,7 @@ describe('ngOptions', function() {
var options = element.find('option');
expect(options.eq(0).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(true);
expect(options.eq(2).prop('selected')).toEqual(false);
expect(options.eq(3).prop('selected')).toEqual(false);
@@ -1940,6 +1946,8 @@ describe('ngOptions', function() {
scope.options[1].unavailable = false;
});
options = element.find('option');
expect(scope.options[1].unavailable).toEqual(false);
expect(options.eq(1).prop('disabled')).toEqual(false);
});
@@ -2535,37 +2543,6 @@ describe('ngOptions', function() {
expect(element.find('option')[1].selected).toBeTruthy();
});
it('should not write disabled selections from model', function() {
scope.selected = [30];
scope.options = [
{ name: 'white', value: '#FFFFFF' },
{ name: 'one', value: 1, unavailable: true },
{ name: 'notTrue', value: false },
{ name: 'thirty', value: 30, unavailable: false }
];
createSelect({
'ng-options': 'o.value as o.name disable when o.unavailable for o in options',
'ng-model': 'selected',
'multiple': true
});
var options = element.find('option');
expect(options.eq(0).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(false);
expect(options.eq(2).prop('selected')).toEqual(false);
expect(options.eq(3).prop('selected')).toEqual(true);
scope.$apply(function() {
scope.selected.push(1);
});
expect(options.eq(0).prop('selected')).toEqual(false);
expect(options.eq(1).prop('selected')).toEqual(false);
expect(options.eq(2).prop('selected')).toEqual(false);
expect(options.eq(3).prop('selected')).toEqual(true);
});
it('should update model on change', function() {
createMultiSelect();
+11
View File
@@ -241,6 +241,17 @@ describe('$httpBackend', function() {
});
it('should set up event listeners', function() {
var progressFn = function() {};
var uploadProgressFn = function() {};
$backend('GET', '/url', null, callback, {}, null, null, null,
{progress: progressFn}, {progress: uploadProgressFn});
xhr = MockXhr.$$lastInstance;
expect(xhr.$$events.progress[0]).toBe(progressFn);
expect(xhr.upload.$$events.progress[0]).toBe(uploadProgressFn);
});
describe('responseType', function() {
it('should set responseType and return xhr.response', function() {
+30 -1
View File
@@ -1,5 +1,7 @@
'use strict';
/* global MockXhr: false */
describe('$http', function() {
var callback, mockedCookies;
@@ -1019,7 +1021,7 @@ describe('$http', function() {
});
describe('scope.$apply', function() {
describe('callbacks', function() {
it('should $apply after success callback', function() {
$httpBackend.when('GET').respond(200);
@@ -1047,6 +1049,33 @@ describe('$http', function() {
$exceptionHandler.errors = [];
}));
it('should pass the event handlers through to the backend', function() {
var progressFn = jasmine.createSpy('progressFn');
var uploadProgressFn = jasmine.createSpy('uploadProgressFn');
$httpBackend.when('GET').respond(200);
$http({
method: 'GET',
url: '/some',
eventHandlers: {progress: progressFn},
uploadEventHandlers: {progress: uploadProgressFn}
});
$rootScope.$apply();
var mockXHR = MockXhr.$$lastInstance;
expect(mockXHR.$$events.progress).toEqual(jasmine.any(Function));
expect(mockXHR.upload.$$events.progress).toEqual(jasmine.any(Function));
spyOn($rootScope, '$digest');
mockXHR.$$events.progress();
expect(progressFn).toHaveBeenCalledOnce();
expect($rootScope.$digest).toHaveBeenCalledTimes(1);
mockXHR.upload.$$events.progress();
expect(uploadProgressFn).toHaveBeenCalledOnce();
expect($rootScope.$digest).toHaveBeenCalledTimes(2);
});
});
+31
View File
@@ -95,6 +95,37 @@ describe('parser', function() {
expect(spaces).toEqual(noSpaces);
});
it('should use callback functions to know when an identifier is valid', function() {
function getText(t) { return t.text; }
var isIdentifierStart = jasmine.createSpy('start');
var isIdentifierContinue = jasmine.createSpy('continue');
isIdentifierStart.and.returnValue(true);
var lex = new Lexer({csp: false, isIdentifierStart: isIdentifierStart, isIdentifierContinue: isIdentifierContinue});
isIdentifierContinue.and.returnValue(true);
var tokens = lex.lex('πΣε').map(getText);
expect(tokens).toEqual(['πΣε']);
isIdentifierContinue.and.returnValue(false);
tokens = lex.lex('πΣε').map(getText);
expect(tokens).toEqual(['π', 'Σ', 'ε']);
});
it('should send the unicode characters and code points', function() {
function getText(t) { return t.text; }
var isIdentifierStart = jasmine.createSpy('start');
var isIdentifierContinue = jasmine.createSpy('continue');
isIdentifierStart.and.returnValue(true);
isIdentifierContinue.and.returnValue(true);
var lex = new Lexer({csp: false, isIdentifierStart: isIdentifierStart, isIdentifierContinue: isIdentifierContinue});
var tokens = lex.lex('\uD801\uDC37\uD852\uDF62\uDBFF\uDFFF');
expect(isIdentifierStart).toHaveBeenCalledTimes(1);
expect(isIdentifierStart.calls.argsFor(0)).toEqual(['\uD801\uDC37', 0x10437]);
expect(isIdentifierContinue).toHaveBeenCalledTimes(2);
expect(isIdentifierContinue.calls.argsFor(0)).toEqual(['\uD852\uDF62', 0x24B62]);
expect(isIdentifierContinue.calls.argsFor(1)).toEqual(['\uDBFF\uDFFF', 0x10FFFF]);
});
it('should tokenize undefined', function() {
var tokens = lex("undefined");
var i = 0;
+456 -114
View File
@@ -1282,7 +1282,7 @@ describe("animations", function() {
expect(element).not.toHaveClass('green');
}));
it('should automatically cancel out class-based animations if the element already contains or doesn\' contain the applied classes',
it('should automatically cancel out class-based animations if the element already contains or doesn\'t contain the applied classes',
inject(function($animate, $rootScope) {
parent.append(element);
@@ -1896,6 +1896,68 @@ describe("animations", function() {
expect(count).toBe(3);
}));
it('should remove all event listeners for an element when $animate.off(element) is called',
inject(function($animate, $rootScope, $rootElement, $document, $$rAF) {
element = jqLite('<div></div>');
var otherElement = jqLite('<div></div>');
$rootElement.append(otherElement);
var count = 0;
var runner;
$animate.on('enter', element, counter);
$animate.on('leave', element, counter);
$animate.on('addClass', element, counter);
$animate.on('addClass', otherElement, counter);
function counter(element, phase) {
count++;
}
runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
$animate.flush();
runner.end();
runner = $animate.addClass(element, 'blue');
$rootScope.$digest();
$animate.flush();
runner.end();
$$rAF.flush();
expect(count).toBe(4);
$animate.off(element);
runner = $animate.enter(element, $rootElement);
$animate.flush();
expect(capturedAnimation[1]).toBe('enter');
runner.end();
runner = $animate.addClass(element, 'red');
$animate.flush();
expect(capturedAnimation[1]).toBe('addClass');
runner.end();
runner = $animate.leave(element);
$animate.flush();
expect(capturedAnimation[1]).toBe('leave');
runner.end();
// Try to flush all remaining callbacks
expect(function() {
$$rAF.flush();
}).toThrowError('No rAF callbacks present');
expect(count).toBe(4);
// Check that other elements' event listeners are not affected
$animate.addClass(otherElement, 'green');
$animate.flush();
expect(count).toBe(5);
}));
it('should fire a `start` callback when the animation starts with the matching element',
inject(function($animate, $rootScope, $rootElement, $document) {
@@ -1937,63 +1999,96 @@ describe("animations", function() {
expect(capturedElement).toBe(element);
}));
it('should remove all event listeners when the element is removed',
inject(function($animate, $rootScope, $rootElement) {
element = jqLite('<div></div>');
they('should remove all event listeners when the element is removed via $prop',
['leave()', 'remove()'], function(method) {
inject(function($animate, $rootScope, $rootElement, $$rAF) {
var count = 0;
var runner;
element = jqLite('<div></div>');
$animate.on('enter', element, counter);
$animate.on('addClass', element[0], counter);
var count = 0;
var enterSpy = jasmine.createSpy();
var addClassSpy = jasmine.createSpy();
var runner;
function counter(element, phase) {
if (phase === 'start') {
count++;
$animate.on('enter', element, enterSpy);
$animate.on('addClass', element[0], addClassSpy);
function counter(element, phase) {
if (phase === 'start') {
count++;
}
}
}
runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
$animate.flush();
runner.end(); // Otherwise the class animation won't run because enter is still in progress
expect(count).toBe(1);
$animate.flush();
expect(enterSpy.calls.count()).toBe(1);
expect(enterSpy.calls.mostRecent().args[1]).toBe('start');
capturedAnimation = null;
runner.end(); // Otherwise the class animation won't run because enter is still in progress
$$rAF.flush();
expect(enterSpy.calls.count()).toBe(2);
expect(enterSpy.calls.mostRecent().args[1]).toBe('close');
$animate.addClass(element, 'blue');
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
enterSpy.calls.reset();
capturedAnimation = null;
$animate.flush();
expect(count).toBe(2);
runner = $animate.addClass(element, 'blue');
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
capturedAnimation = null;
$animate.flush();
expect(addClassSpy.calls.count()).toBe(1);
expect(addClassSpy.calls.mostRecent().args[1]).toBe('start');
element.remove();
runner.end();
$$rAF.flush();
expect(addClassSpy.calls.count()).toBe(2);
expect(addClassSpy.calls.mostRecent().args[1]).toBe('close');
runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
addClassSpy.calls.reset();
capturedAnimation = null;
expect(capturedAnimation).toBeTruthy();
if (method === 'leave()') {
runner = $animate.leave(element);
$animate.flush();
runner.end();
} else if (method === 'remove()') {
element.remove();
}
$animate.flush();
runner.end(); // Otherwise the class animation won't run because enter is still in progress
runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
expect(count).toBe(2);
$animate.flush();
expect(enterSpy.calls.count()).toBe(0);
capturedAnimation = null;
runner.end(); // Otherwise the class animation won't run because enter is still in progress
expect(function() {
$$rAF.flush();
}).toThrowError('No rAF callbacks present'); // Try to flush any callbacks
expect(enterSpy.calls.count()).toBe(0);
$animate.addClass(element, 'red');
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
capturedAnimation = null;
$animate.flush();
expect(count).toBe(2);
}));
$animate.addClass(element, 'red');
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
$animate.flush();
expect(addClassSpy.calls.count()).toBe(0);
runner.end();
expect(function() {
$$rAF.flush();
}).toThrowError('No rAF callbacks present'); // Try to flush any callbacks
expect(addClassSpy.calls.count()).toBe(0);
expect(enterSpy.calls.count()).toBe(0);
});
});
it('should always detect registered callbacks after one postDigest has fired',
inject(function($animate, $rootScope, $rootElement) {
@@ -2058,104 +2153,187 @@ describe("animations", function() {
}
}));
it('leave: should remove the element even if another animation is called after',
inject(function($animate, $rootScope, $rootElement) {
describe('for leave', function() {
var outerContainer = jqLite('<div></div>');
element = jqLite('<div></div>');
outerContainer.append(element);
$rootElement.append(outerContainer);
it('should remove the element even if another animation is called afterwards',
inject(function($animate, $rootScope, $rootElement) {
var runner = $animate.leave(element, $rootElement);
$animate.removeClass(element,'rclass');
$rootScope.$digest();
runner.end();
$animate.flush();
var outerContainer = jqLite('<div></div>');
element = jqLite('<div></div>');
outerContainer.append(element);
$rootElement.append(outerContainer);
var isElementRemoved = !outerContainer[0].contains(element[0]);
expect(isElementRemoved).toBe(true);
}));
var runner = $animate.leave(element, $rootElement);
$animate.removeClass(element,'rclass');
$rootScope.$digest();
runner.end();
$animate.flush();
it('leave : should trigger a callback for an leave animation',
inject(function($animate, $rootScope, $$rAF, $rootElement, $document) {
var isElementRemoved = !outerContainer[0].contains(element[0]);
expect(isElementRemoved).toBe(true);
}));
var callbackTriggered = false;
$animate.on('leave', jqLite($document[0].body), function() {
callbackTriggered = true;
});
they('should trigger callbacks when the listener is on the $prop element', ['same', 'parent'],
function(elementRelation) {
inject(function($animate, $rootScope, $$rAF, $rootElement, $document) {
var listenerElement, callbackSpy = jasmine.createSpy();
element = jqLite('<div></div>');
$rootElement.append(element);
$animate.leave(element, $rootElement);
$rootScope.$digest();
element = jqLite('<div></div>');
listenerElement = elementRelation === 'same' ? element : jqLite($document[0].body);
$animate.on('leave', listenerElement, callbackSpy);
$rootElement.append(element);
var runner = $animate.leave(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
$$rAF.flush();
expect(callbackTriggered).toBe(true);
}));
expect(callbackSpy.calls.count()).toBe(1);
expect(callbackSpy.calls.mostRecent().args[1]).toBe('start');
callbackSpy.calls.reset();
it('leave : should not fire a callback if the element is outside of the given container',
inject(function($animate, $rootScope, $$rAF, $rootElement) {
runner.end();
$$rAF.flush();
var callbackTriggered = false;
var innerContainer = jqLite('<div></div>');
$rootElement.append(innerContainer);
expect(callbackSpy.calls.count()).toBe(1);
expect(callbackSpy.calls.mostRecent().args[1]).toBe('close');
});
}
);
$animate.on('leave', innerContainer,
function(element, phase, data) {
callbackTriggered = true;
});
it('should trigger callbacks for a leave animation',
inject(function($animate, $rootScope, $$rAF, $rootElement, $document) {
element = jqLite('<div></div>');
$rootElement.append(element);
$animate.leave(element, $rootElement);
$rootScope.$digest();
var callbackSpy = jasmine.createSpy();
$animate.on('leave', jqLite($document[0].body), callbackSpy);
expect(callbackTriggered).toBe(false);
}));
element = jqLite('<div></div>');
$rootElement.append(element);
$animate.leave(element, $rootElement);
$rootScope.$digest();
it('leave : should fire a `start` callback when the animation starts with the matching element',
inject(function($animate, $rootScope, $$rAF, $rootElement, $document) {
$$rAF.flush();
element = jqLite('<div></div>');
expect(callbackSpy).toHaveBeenCalled();
expect(callbackSpy.calls.count()).toBe(1);
}));
var capturedState;
var capturedElement;
$animate.on('leave', jqLite($document[0].body), function(element, phase) {
capturedState = phase;
capturedElement = element;
});
it('should trigger a callback for an leave animation (same element)',
inject(function($animate, $rootScope, $$rAF, $rootElement, $document) {
$rootElement.append(element);
$animate.leave(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
var callbackSpy = jasmine.createSpy();
expect(capturedState).toBe('start');
expect(capturedElement).toBe(element);
}));
element = jqLite('<div></div>');
$animate.on('leave', element, callbackSpy);
$rootElement.append(element);
var runner = $animate.leave(element, $rootElement);
$rootScope.$digest();
it('leave : should fire a `close` callback when the animation ends with the matching element',
inject(function($animate, $rootScope, $$rAF, $rootElement, $document) {
$$rAF.flush();
element = jqLite('<div></div>');
expect(callbackSpy.calls.count()).toBe(1);
expect(callbackSpy.calls.mostRecent().args[1]).toBe('start');
callbackSpy.calls.reset();
var capturedState;
var capturedElement;
$animate.on('leave', jqLite($document[0].body), function(element, phase) {
capturedState = phase;
capturedElement = element;
});
runner.end();
$$rAF.flush();
$rootElement.append(element);
var runner = $animate.leave(element, $rootElement);
$rootScope.$digest();
runner.end();
$$rAF.flush();
expect(callbackSpy.calls.count()).toBe(1);
expect(callbackSpy.calls.mostRecent().args[1]).toBe('close');
}));
expect(capturedState).toBe('close');
expect(capturedElement).toBe(element);
}));
it('should not fire a callback if the element is outside of the given container',
inject(function($animate, $rootScope, $$rAF, $rootElement) {
var callbackTriggered = false;
var innerContainer = jqLite('<div></div>');
$rootElement.append(innerContainer);
$animate.on('leave', innerContainer,
function(element, phase, data) {
callbackTriggered = true;
});
element = jqLite('<div></div>');
$rootElement.append(element);
$animate.leave(element, $rootElement);
$rootScope.$digest();
expect(callbackTriggered).toBe(false);
}));
it('should fire a `start` callback when the animation starts',
inject(function($animate, $rootScope, $$rAF, $rootElement, $document) {
element = jqLite('<div></div>');
var capturedState;
var capturedElement;
$animate.on('leave', jqLite($document[0].body), function(element, phase) {
capturedState = phase;
capturedElement = element;
});
$rootElement.append(element);
$animate.leave(element, $rootElement);
$rootScope.$digest();
$$rAF.flush();
expect(capturedState).toBe('start');
expect(capturedElement).toBe(element);
}));
it('should fire a `close` callback when the animation ends',
inject(function($animate, $rootScope, $$rAF, $rootElement, $document) {
element = jqLite('<div></div>');
var capturedState;
var capturedElement;
$animate.on('leave', jqLite($document[0].body), function(element, phase) {
capturedState = phase;
capturedElement = element;
});
$rootElement.append(element);
var runner = $animate.leave(element, $rootElement);
$rootScope.$digest();
runner.end();
$$rAF.flush();
expect(capturedState).toBe('close');
expect(capturedElement).toBe(element);
}));
it('should remove all event listeners after all callbacks for the "leave:close" phase have been called',
inject(function($animate, $rootScope, $rootElement, $$rAF) {
var leaveSpy = jasmine.createSpy();
var addClassSpy = jasmine.createSpy();
element = jqLite('<div></div>');
$animate.on('leave', element, leaveSpy);
$animate.on('addClass', element, addClassSpy);
$rootElement.append(element);
var runner = $animate.leave(element, $rootElement);
$animate.flush();
runner.end();
$$rAF.flush();
expect(leaveSpy.calls.mostRecent().args[1]).toBe('close');
$animate.addClass(element, 'blue');
$animate.flush();
runner.end();
expect(function() {
$$rAF.flush();
}).toThrowError('No rAF callbacks present');
expect(addClassSpy.calls.count()).toBe(0);
}));
});
they('should trigger a callback for a $prop animation if the listener is on the document',
['enter', 'leave'], function($event) {
@@ -2191,5 +2369,169 @@ describe("animations", function() {
});
});
describe('when animations are skipped, disabled, or invalid', function() {
var overriddenAnimationRunner;
var capturedAnimation;
var capturedAnimationHistory;
var defaultFakeAnimationRunner;
var parent;
var parent2;
beforeEach(module(function($provide) {
overriddenAnimationRunner = null;
capturedAnimation = null;
capturedAnimationHistory = [];
$provide.value('$$animation', function() {
capturedAnimationHistory.push(capturedAnimation = arguments);
return overriddenAnimationRunner || defaultFakeAnimationRunner;
});
return function($rootElement, $q, $animate, $$AnimateRunner, $document) {
defaultFakeAnimationRunner = new $$AnimateRunner();
$animate.enabled(true);
element = jqLite('<div class="element">element</div>');
parent = jqLite('<div class="parent1">parent</div>');
parent2 = jqLite('<div class="parent2">parent</div>');
$rootElement.append(parent);
$rootElement.append(parent2);
jqLite($document[0].body).append($rootElement);
};
}));
it('should trigger all callbacks if a follow-up structural animation takes over a running animation',
inject(function($animate, $rootScope) {
parent.append(element);
var moveSpy = jasmine.createSpy();
var leaveSpy = jasmine.createSpy();
$animate.on('move', parent2, moveSpy);
$animate.on('leave', parent2, leaveSpy);
$animate.move(element, parent2);
$rootScope.$digest();
$animate.flush();
expect(moveSpy.calls.count()).toBe(1);
expect(moveSpy.calls.mostRecent().args[1]).toBe('start');
$animate.leave(element);
$rootScope.$digest();
$animate.flush();
expect(moveSpy.calls.count()).toBe(2);
expect(moveSpy.calls.mostRecent().args[1]).toBe('close');
expect(leaveSpy.calls.count()).toBe(2);
expect(leaveSpy.calls.argsFor(0)[1]).toBe('start');
expect(leaveSpy.calls.argsFor(1)[1]).toBe('close');
}));
it('should not trigger callbacks for the previous structural animation if a follow-up structural animation takes over before the postDigest',
inject(function($animate, $rootScope) {
var enterDone = jasmine.createSpy('enter animation done');
var enterSpy = jasmine.createSpy();
var leaveSpy = jasmine.createSpy();
$animate.on('enter', parent, enterSpy);
$animate.on('leave', parent, leaveSpy);
$animate.enter(element, parent).done(enterDone);
expect(enterDone).not.toHaveBeenCalled();
var runner = $animate.leave(element);
$animate.flush();
expect(enterDone).toHaveBeenCalled();
expect(enterSpy).not.toHaveBeenCalled();
expect(leaveSpy.calls.count()).toBe(1);
expect(leaveSpy.calls.mostRecent().args[1]).toBe('start');
leaveSpy.calls.reset();
runner.end();
$animate.flush();
expect(enterSpy).not.toHaveBeenCalled();
expect(leaveSpy.calls.count()).toBe(1);
expect(leaveSpy.calls.mostRecent().args[1]).toBe('close');
}));
it('should not trigger the callback if animations are disabled on the element',
inject(function($animate, $rootScope, $rootElement, $document) {
var callbackTriggered = false;
var spy = jasmine.createSpy('enter');
$animate.on('enter', jqLite($document[0].body), spy);
element = jqLite('<div></div>');
$animate.enabled(element, false);
var runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
$animate.flush(); // Flushes the animation frames for the callbacks
expect(spy).not.toHaveBeenCalled();
}));
it('should not trigger the callbacks if the animation is skipped because there are no class-based animations and no structural animation',
inject(function($animate, $rootScope) {
parent.append(element);
var classSpy = jasmine.createSpy('classChange');
$animate.on('addClass', element, classSpy);
$animate.on('removeClass', element, classSpy);
element.addClass('one three');
$animate.addClass(element, 'one');
$animate.removeClass(element, 'four');
$rootScope.$digest();
$animate.flush();
expect(classSpy).not.toHaveBeenCalled();
}));
describe('because the document is hidden', function() {
beforeEach(module(function($provide) {
var doc = jqLite({
body: document.body,
hidden: true
});
$provide.value('$document', doc);
}));
it('should trigger callbacks for an enter animation',
inject(function($animate, $rootScope, $rootElement, $document) {
var callbackTriggered = false;
var spy = jasmine.createSpy();
$animate.on('enter', jqLite($document[0].body), spy);
element = jqLite('<div></div>');
var runner = $animate.enter(element, $rootElement);
$rootScope.$digest();
$animate.flush(); // Flushes the animation frames for the callbacks
expect(spy.calls.count()).toBe(2);
expect(spy.calls.argsFor(0)[1]).toBe('start');
expect(spy.calls.argsFor(1)[1]).toBe('close');
}));
});
});
});
});
+60
View File
@@ -396,6 +396,66 @@ describe('$aria', function() {
});
});
describe('aria-readonly', function() {
beforeEach(injectScopeAndCompiler);
they('should not attach itself to native $prop controls', {
input: '<input ng-readonly="val">',
textarea: '<textarea ng-readonly="val"></textarea>',
select: '<select ng-readonly="val"></select>',
button: '<button ng-readonly="val"></button>'
}, function(tmpl) {
var element = $compile(tmpl)(scope);
scope.$apply('val = true');
expect(element.attr('readonly')).toBeDefined();
expect(element.attr('aria-readonly')).toBeUndefined();
});
it('should attach itself to custom controls', function() {
compileElement('<div ng-readonly="val"></div>');
expect(element.attr('aria-readonly')).toBe('false');
scope.$apply('val = true');
expect(element.attr('aria-readonly')).toBe('true');
});
it('should not attach itself if an aria-readonly attribute is already present', function() {
compileElement('<div ng-readonly="val" aria-readonly="userSetValue"></div>');
expect(element.attr('aria-readonly')).toBe('userSetValue');
});
it('should always set aria-readonly to a boolean value', function() {
compileElement('<div ng-readonly="val"></div>');
scope.$apply('val = "test angular"');
expect(element.attr('aria-readonly')).toBe('true');
scope.$apply('val = null');
expect(element.attr('aria-readonly')).toBe('false');
scope.$apply('val = {}');
expect(element.attr('aria-readonly')).toBe('true');
});
});
describe('aria-readonly when disabled', function() {
beforeEach(configAriaProvider({
ariaReadonly: false
}));
beforeEach(injectScopeAndCompiler);
it('should not add the aria-readonly attribute', function() {
compileElement("<input ng-model='val' readonly>");
expect(element.attr('aria-readonly')).toBeUndefined();
compileElement("<div ng-model='val' ng-readonly='true'></div>");
expect(element.attr('aria-readonly')).toBeUndefined();
});
});
describe('aria-required', function() {
beforeEach(injectScopeAndCompiler);
+66
View File
@@ -914,6 +914,51 @@ describe('ngMock', function() {
});
}).toThrow('test message');
}));
describe('error stack trace when called outside of spec context', function() {
// - Chrome, Firefox, Edge, Opera give us the stack trace as soon as an Error is created
// - IE10+, PhantomJS give us the stack trace only once the error is thrown
// - IE9 does not provide stack traces
var stackTraceSupported = (function() {
var error = new Error();
if (!error.stack) {
try {
throw error;
} catch (e) {}
}
return !!error.stack;
})();
function testCaller() {
return inject(function() {
throw new Error();
});
}
var throwErrorFromInjectCallback = testCaller();
if (stackTraceSupported) {
describe('on browsers supporting stack traces', function() {
it('should update thrown Error stack trace with inject call location', function() {
try {
throwErrorFromInjectCallback();
} catch (e) {
expect(e.stack).toMatch('testCaller');
}
});
});
} else {
describe('on browsers not supporting stack traces', function() {
it('should not add stack trace information to thrown Error', function() {
try {
throwErrorFromInjectCallback();
} catch (e) {
expect(e.stack).toBeUndefined();
}
});
});
}
});
});
});
@@ -2044,6 +2089,27 @@ describe('ngMock', function() {
}).toThrowError('Too many components found');
});
});
it('should create an isolated child of $rootScope, if no `$scope` local is provided', function() {
function TestController($scope) {
this.$scope = $scope;
}
module(function($compileProvider) {
$compileProvider.component('test', {
controller: TestController
});
});
inject(function($componentController, $rootScope) {
var $ctrl = $componentController('test');
expect($ctrl.$scope).toBeDefined();
expect($ctrl.$scope.$parent).toBe($rootScope);
// check it is isolated
$rootScope.a = 17;
expect($ctrl.$scope.a).toBeUndefined();
$ctrl.$scope.a = 42;
expect($rootScope.a).toEqual(17);
});
});
});
});
-31
View File
@@ -1027,34 +1027,3 @@ describe('ngView animations', function() {
));
});
});
describe('ngView in async template', function() {
beforeEach(module('ngRoute'));
beforeEach(module(function($compileProvider, $provide, $routeProvider) {
$compileProvider.directive('asyncView', function() {
return {templateUrl: 'async-view.html'};
});
$provide.decorator('$templateRequest', function($timeout) {
return function() {
return $timeout(angular.identity, 500, false, '<ng-view></ng-view>');
};
});
$routeProvider.when('/', {template: 'Hello, world !'});
}));
it('should work correctly upon initial page load',
// Injecting `$location` here is necessary, so that it gets instantiated early
inject(function($compile, $location, $rootScope, $timeout) {
var elem = $compile('<async-view></async-view>')($rootScope);
$rootScope.$digest();
$timeout.flush(500);
expect(elem.text()).toBe('Hello, world !');
dealoc(elem);
})
);
});
-15
View File
@@ -23,21 +23,6 @@ describe('$route', function() {
dealoc(element);
});
it('should be loaded upon initial load (even if `ngView` is loaded async)', function() {
module(function($routeProvider) {
$routeProvider.when('/', {template: 'Hello, world !'});
});
inject(function($location, $rootScope) {
$location.path('/');
$rootScope.$digest();
});
inject(function($route) {
expect($route.current).toBeDefined();
});
});
it('should allow cancellation via $locationChangeStart via $routeChangeStart', function() {
module(function($routeProvider) {
$routeProvider.when('/Edit', {