Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e305948e4 | |||
| ed99821e4d | |||
| e057a9aa39 | |||
| e676d642f5 | |||
| 288b531626 | |||
| 7a4df50480 | |||
| 6550198003 | |||
| c6909ed144 | |||
| 187e43185d | |||
| 91834bcf37 | |||
| 841c090755 | |||
| da960544f1 | |||
| 74981c9f20 | |||
| dc4b06559f | |||
| 56138bdd63 | |||
| 0ccc4fcd89 | |||
| b7e2b01bb5 | |||
| 0c19482d03 | |||
| 0f7bcfdf93 | |||
| 9a26ab5870 | |||
| d906ed3100 | |||
| 0b16d10d2d | |||
| e0198c1cda | |||
| ba731ab850 | |||
| e69c1806a8 | |||
| b4770582f8 | |||
| 2a2fd14c08 | |||
| e4eb382f2d |
@@ -13,6 +13,7 @@ angular.js.tmproj
|
||||
bower_components/
|
||||
angular.xcodeproj
|
||||
.idea
|
||||
*.iml
|
||||
.agignore
|
||||
.lvimrc
|
||||
libpeerconnection.log
|
||||
|
||||
@@ -1,3 +1,59 @@
|
||||
|
||||
<a name="1.3.1"></a>
|
||||
# 1.3.1 spectral-lobster (2014-10-31)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** returning null when an optional controller is not found
|
||||
([2cd5b4ec](https://github.com/angular/angular.js/commit/2cd5b4ec4409a818ccd33a6fbdeb99a3443a1809),
|
||||
[#9404](https://github.com/angular/angular.js/issues/9404), [#9392](https://github.com/angular/angular.js/issues/9392))
|
||||
- **$observe:** check if the attribute is undefined
|
||||
([531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
|
||||
[#9707](https://github.com/angular/angular.js/issues/9707), [#9720](https://github.com/angular/angular.js/issues/9720))
|
||||
- **$parse:** support dirty-checking objects with null prototype
|
||||
([28661d1a](https://github.com/angular/angular.js/commit/28661d1a8cc3a8454bad7ae531e027b1256476c9),
|
||||
[#9568](https://github.com/angular/angular.js/issues/9568))
|
||||
- **$sce:** use msie instead of $document[0].documentMode
|
||||
([45252c3a](https://github.com/angular/angular.js/commit/45252c3a545336a0bac93be6ee28cde6afaa3cb4),
|
||||
[#9661](https://github.com/angular/angular.js/issues/9661))
|
||||
- **$templateRequest:** ignore JSON Content-Type header and content
|
||||
([1bd473eb](https://github.com/angular/angular.js/commit/1bd473eb4587900086e0b6b308dcf1dcfe9760d9),
|
||||
[#5756](https://github.com/angular/angular.js/issues/5756), [#9619](https://github.com/angular/angular.js/issues/9619))
|
||||
- **i18n:** rename datetimeSymbols to be camelCase
|
||||
([94f5a285](https://github.com/angular/angular.js/commit/94f5a285bfcf04d800afc462a7a37a3469d77f1a))
|
||||
- **loader:** fix double spaces
|
||||
([8b2f1a47](https://github.com/angular/angular.js/commit/8b2f1a47b584ceb98689f48538a2af73cd65dfd8),
|
||||
[#9630](https://github.com/angular/angular.js/issues/9630))
|
||||
- **ngMock:** $httpBackend should match data containing Date objects correctly
|
||||
([1025f6eb](https://github.com/angular/angular.js/commit/1025f6ebf4e5933a12920889be00cd8ac8a106fa),
|
||||
[#5127](https://github.com/angular/angular.js/issues/5127))
|
||||
- **ngSanitize:** attribute name: xmlns:href -> xlink:href
|
||||
([4cccf0f2](https://github.com/angular/angular.js/commit/4cccf0f2a89b002d63cb443e1e7b15f76dcef425),
|
||||
[#9769](https://github.com/angular/angular.js/issues/9769))
|
||||
- **select:** assign result of track exp to element value
|
||||
([4b4098bf](https://github.com/angular/angular.js/commit/4b4098bfcae64f69c70a22393de1f3d9a0d3dc46),
|
||||
[#9718](https://github.com/angular/angular.js/issues/9718), [#9592](https://github.com/angular/angular.js/issues/9592))
|
||||
- **templateRequest:** allow empty html template
|
||||
([52ceec22](https://github.com/angular/angular.js/commit/52ceec2229dc132b76da4e022c91474344f2d906),
|
||||
[#9581](https://github.com/angular/angular.js/issues/9581))
|
||||
- **testability:** escape regex chars in `findBindings` if using `exactMatch`
|
||||
([02aa4f4b](https://github.com/angular/angular.js/commit/02aa4f4b85ee15922a1f2de8ba78f562c18518d0),
|
||||
[#9595](https://github.com/angular/angular.js/issues/9595), [#9600](https://github.com/angular/angular.js/issues/9600))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** allow $watchCollection to be used in bi-directional bindings
|
||||
([40bbc981](https://github.com/angular/angular.js/commit/40bbc9817845bf75581daee5d0ec30980affb0f5),
|
||||
[#9725](https://github.com/angular/angular.js/issues/9725))
|
||||
- **ngSanitize:** accept SVG elements and attributes
|
||||
([a54b25d7](https://github.com/angular/angular.js/commit/a54b25d77999a85701dfc5396fef78e586a99667),
|
||||
[#9578](https://github.com/angular/angular.js/issues/9578), [#9751](https://github.com/angular/angular.js/issues/9751))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.0"></a>
|
||||
# 1.3.0 superluminal-nudge (2014-10-13)
|
||||
|
||||
@@ -1452,6 +1508,8 @@ Closes #8230
|
||||
|
||||
- **jQuery:** due to [9e7cb3c3](https://github.com/angular/angular.js/commit/9e7cb3c37543008e6236bb5a2c4536df2e1e43a9),
|
||||
Angular no longer supports jQuery versions below 2.1.1.
|
||||
- **$q:** due to [23bc92b1](https://github.com/angular/angular.js/commit/23bc92b17df882a907fb326320f0622717fefe7b),
|
||||
Promises methods are no longer enumerated when using for-loops with `hasOwnProperty` check. E.g. `angular.extends`
|
||||
|
||||
|
||||
<a name="1.2.22"></a>
|
||||
|
||||
@@ -71,7 +71,8 @@ app.controller('DataController', function($scope, $rootScope) {
|
||||
date2: new Date(Math.random()*Date.now()),
|
||||
func: function(){ return star; },
|
||||
obj: data[i-1],
|
||||
keys: data[i-1] && (data[i-1].keys || Object.keys(data[i-1]))
|
||||
keys: data[i-1] && (data[i-1].keys || Object.keys(data[i-1])),
|
||||
constructor: data[i-1]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
<label for="complexPath">Complex Paths</label>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="radio" ng-model="expressionType" value="constructorPath" id="constructorPath">
|
||||
<label for="constructorPath">Constructor Paths</label>
|
||||
($parse special cases "constructor" for security)
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="radio" ng-model="expressionType" value="fieldAccess" id="fieldAccess">
|
||||
<label for="fieldAccess">Field Accessors</label>
|
||||
@@ -77,6 +83,17 @@
|
||||
<span bm-pe-watch="row.keys"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="constructorPath" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch="row.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
|
||||
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
|
||||
</li>
|
||||
|
||||
<li ng-switch-when="complexPath" ng-repeat="(rowIdx, row) in ::data">
|
||||
<span bm-pe-watch="row.index"></span>
|
||||
<span bm-pe-watch="row.num0"></span>
|
||||
|
||||
@@ -6,56 +6,165 @@
|
||||
|
||||
# Accessibility with ngAria
|
||||
|
||||
You can use the `ngAria` module to have common ARIA attributes automatically applied when you
|
||||
use certain directives. To enable `ngAria`, just require the module into your application and
|
||||
the code will hook into your ng-show/ng-hide, input, textarea, button, select and
|
||||
ng-required directives and add the appropriate ARIA states and properties.
|
||||
The goal of ngAria is to improve Angular's default accessibility by enabling common
|
||||
[ARIA](http://www.w3.org/TR/wai-aria/) attributes that convey state or semantic information for
|
||||
assistive technologies used by persons with disabilities.
|
||||
|
||||
Currently, the following attributes are implemented:
|
||||
* aria-hidden
|
||||
* aria-checked
|
||||
* aria-disabled
|
||||
* aria-required
|
||||
* aria-invalid
|
||||
* aria-multiline
|
||||
* aria-valuenow
|
||||
* aria-valuemin
|
||||
* aria-valuemax
|
||||
* tabindex
|
||||
##Including ngAria
|
||||
|
||||
You can disable individual attributes by using the `{@link ngAria.$ariaProvider#config config}` method.
|
||||
|
||||
###Example
|
||||
Using 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
|
||||
at runtime.
|
||||
|
||||
```js
|
||||
angular.module('myApp', ['ngAria'])...
|
||||
```
|
||||
|
||||
Elements using `ng-model` with `required` or `ngRequired` directives will automatically have
|
||||
`aria-required` attributes with the proper corresponding values.
|
||||
Currently, ngAria interfaces with the following directives:
|
||||
|
||||
* <a href="#ngmodel">ngModel</a>
|
||||
* <a href="#ngdisabled">ngDisabled</a>
|
||||
* <a href="#ngshow">ngShow</a>
|
||||
* <a href="#nghide">ngHide</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngClick</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngDblClick</a>
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
Most of ngAria's heavy lifting happens in the [ngModel](https://docs.angularjs.org/api/ng/directive/ngModel)
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
has a a role or type of `checkbox`, `radio`, `range` or `textbox`.
|
||||
|
||||
For those elements using ngModel, ngAria will dynamically bind and update the following ARIA
|
||||
attributes (if they have not been explicitly specified by the developer):
|
||||
|
||||
* aria-checked
|
||||
* aria-valuemin
|
||||
* aria-valuemax
|
||||
* aria-valuenow
|
||||
* aria-invalid
|
||||
* aria-required
|
||||
|
||||
###Example
|
||||
|
||||
```html
|
||||
<material-input ng-model="val" required>
|
||||
<md-checkbox ng-model="val" required>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<material-input ng-model="val" required aria-required="true">
|
||||
<md-checkbox ng-model="val" required aria-required="true" tabIndex="0">
|
||||
```
|
||||
|
||||
ngAria is just a starting point. You'll have to manually choose how to implement some
|
||||
accessibility features.
|
||||
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
|
||||
the keyboard. It is still up to **you** as a developer to **ensure custom controls will be
|
||||
operable** from the keybard. Think of `ng-click` on a `<div>` or `<md-checkbox>`: you still need
|
||||
to bind `ng-keypress` to make it fully operable from the keyboard. As a rule, any time you create
|
||||
a widget involving user interaction, be sure to test it with your keyboard and at least one mobile
|
||||
and desktop screen reader (preferably more).
|
||||
|
||||
For instance, you may want to add `ng-keypress` bindings alongside `ng-click` to make keyboard
|
||||
navigation easier.
|
||||
<h2 id="ngdisabled">ngDisabled</h2>
|
||||
|
||||
The `disabled` attribute is only valid for certain elements such as `button`, `input` and
|
||||
`textarea`. To properly disable custom element directives such as `<md-checkbox>` or `<taco-tab>`,
|
||||
using ngAria with [ngDisabled](https://docs.angularjs.org/api/ng/directive/ngDisabled) will also
|
||||
add `aria-disabled`. This tells assistive technologies when a non-native input is disabled, helping
|
||||
custom controls to be more accessible.
|
||||
|
||||
## Additional Resources
|
||||
###Example
|
||||
|
||||
```html
|
||||
<md-checkbox ng-disabled="disabled">
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<md-checkbox disabled aria-disabled="true">
|
||||
```
|
||||
|
||||
>You can check whether a control is legitimately disabled for a screen reader by visiting
|
||||
[chrome://accessibility](chrome://accessibility).
|
||||
|
||||
<h2 id="ngshow">ngShow</h2>
|
||||
|
||||
>The [ngShow](https://docs.angularjs.org/api/ng/directive/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.
|
||||
|
||||
In its default setup, ngAria for `ngShow` is actually redundant. It toggles `aria-hidden` on the
|
||||
directive when it is hidden or shown. However, the default CSS of `display: none !important`,
|
||||
already hides child elements from a screen reader. It becomes more useful when the default
|
||||
CSS is overridden with properties that don’t affect assistive technologies, such as `opacity`
|
||||
or `transform`. By toggling `aria-hidden` dynamically with ngAria, we can ensure content visually
|
||||
hidden with this technique will not be read aloud in a screen reader.
|
||||
|
||||
One caveat with this combination of CSS and `aria-hidden`: you must also remove links and other
|
||||
interactive child elements from the tab order using `tabIndex=“-1”` on each control. This ensures
|
||||
screen reader users won't accidentally focus on "mystery elements". Managing tab index on every
|
||||
child control can be complex and affect performance, so it’s 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).
|
||||
|
||||
<h2 id="nghide">ngHide</h2>
|
||||
|
||||
>The [ngHide](https://docs.angularjs.org/api/ng/directive/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.
|
||||
|
||||
The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redundant. It toggles
|
||||
`aria-hidden` on the directive when it is hidden or shown, but the content is already hidden with
|
||||
`display: none`. See explanation for <a href="#ngshow">ngShow</a> when overriding the default CSS.
|
||||
|
||||
<h2 id="ngclick">ngClick and ngDblClick</h2>
|
||||
If `ngClick` or `ngDblClick` is encountered, ngAria will add `tabIndex` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as
|
||||
`<div>` or `<taco-button>` to enable keyboard access. I have recommended this also bind
|
||||
`ng-keypress` to be more useful; the conversation is [currently ongoing](https://github.com/angular/angular.js/issues/9254).
|
||||
|
||||
* * *
|
||||
|
||||
##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:
|
||||
|
||||
```
|
||||
angular.module('myApp', ['ngAria'], function config($ariaProvider) {
|
||||
$ariaProvider.config({
|
||||
tabindex: false
|
||||
});
|
||||
});
|
||||
```
|
||||
* * *
|
||||
|
||||
##Common Accessibility Patterns
|
||||
|
||||
Accessibility best practices that apply to web apps in general also apply to Angular.
|
||||
|
||||
* [A11Y Project](http://a11yproject.com/)
|
||||
* [WebAim](http://webaim.org/)
|
||||
* [Using WAI-ARIA in HTML](http://www.w3.org/TR/2014/WD-aria-in-html-20140626/)
|
||||
* [Apps For All: Coding Accessible Web Applications](https://shop.smashingmagazine.com/apps-for-all-coding-accessible-web-applications.html)
|
||||
* **Text alternatives**: Add alternate text content to make visual information accessible using
|
||||
[these W3C guidelines](http://www.w3.org/TR/html-alt-techniques/). The appropriate technique
|
||||
depends on the specific markup but can be accomplished using offscreen spans, `aria-label` or
|
||||
label elements, image `alt` attributes, `figure`/`figcaption` elements and more.
|
||||
* **HTML Semantics**: If you're creating custom element directives, Web Components or HTML in
|
||||
general, use native elements wherever possible to utilize built-in events and properties.
|
||||
Alternatively, use ARIA to communicate semantic meaning. See [notes on ARIA use](http://www.w3.org/TR/aria-in-html/#notes-on-aria-use-in-html).
|
||||
* **Focus management**: Guide the user around the app as views are appended/removed.
|
||||
Focus should *never* be lost, as this causes unexpected behavior and much confusion (referred to
|
||||
as "freak-out mode").
|
||||
* **Announcing changes**: When filtering or other UI messaging happens away from the user's focus,
|
||||
notify with [ARIA Live Regions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions).
|
||||
* **Color contrast and scale**: Make sure content is legible and interactive controls are usable
|
||||
at all screen sizes. Consider configurable UI themes for people with color blindness, low vision
|
||||
or other visual impairments.
|
||||
* **Progressive enhancement**: Some users do not browse with JavaScript enabled or do not have
|
||||
the latest browser. An accessible message about site requirements can inform users and improve
|
||||
the experience.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
* [Using ARIA in HTML](http://www.w3.org/TR/aria-in-html/)
|
||||
* [AngularJS Accessibility at ngEurope](https://www.youtube.com/watch?v=dmYDggEgU-s&list=UUEGUP3TJJfMsEM_1y8iviSQ)
|
||||
* [Chrome Accessibility Developer Tools](https://chrome.google.com/webstore/detail/accessibility-developer-t/fpkknkljclfencbdbgkenhalefipecmb?hl=en)
|
||||
* [W3C Accessibility Testing](http://www.w3.org/wiki/Accessibility_testing)
|
||||
* [WebAIM](http://webaim.org)
|
||||
* [A11y Project](http://a11yproject.com)
|
||||
|
||||
@@ -56,10 +56,10 @@ Try out the Live Preview above, and then let's walk through the example and desc
|
||||
|
||||
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
|
||||
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
|
||||
processes this new markup from the template using the so called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
processes this new markup from the template using the so-called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
|
||||
|
||||
The first kind of new markup are the so called <a name="directive">"{@link directive directives}"</a>.
|
||||
The first kind of new markup are the so-called <a name="directive">"{@link directive directives}"</a>.
|
||||
They apply special behavior to attributes or elements in the HTML. In the example above we use the
|
||||
{@link ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
|
||||
initializes our application. Angular also defines a directive for the {@link ng.directive:input `input`}
|
||||
@@ -89,7 +89,7 @@ A filter formats the value of an expression for display to the user.
|
||||
In the example above, the filter {@link ng.filter:currency `currency`} formats a number
|
||||
into an output that looks like money.
|
||||
|
||||
The important thing in the example is that angular provides _live_ bindings:
|
||||
The important thing in the example is that Angular provides _live_ bindings:
|
||||
Whenever the input values change, the value of the expressions are automatically
|
||||
recalculated and the DOM is updated with their values.
|
||||
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
|
||||
@@ -150,13 +150,13 @@ different currencies and also pay the invoice.
|
||||
|
||||
What changed?
|
||||
|
||||
First, there is a new JavaScript file that contains a so called <a name="controller">"{@link controller controller}"</a>.
|
||||
First, there is a new JavaScript file that contains a so-called <a name="controller">"{@link controller controller}"</a>.
|
||||
More exactly, the file contains a constructor function that creates the actual controller instance.
|
||||
The purpose of controllers is to expose variables and functionality to expressions and directives.
|
||||
|
||||
Besides the new file that contains the controller code we also added a
|
||||
{@link ng.directive:ngController `ng-controller`} directive to the HTML.
|
||||
This directive tells angular that the new `InvoiceController` is responsible for the element with the directive
|
||||
This directive tells Angular that the new `InvoiceController` is responsible for the element with the directive
|
||||
and all of the element's children.
|
||||
The syntax `InvoiceController as invoice` tells Angular to instantiate the controller
|
||||
and save it in the variable `invoice` in the current scope.
|
||||
@@ -263,7 +263,7 @@ services, ...) is created and wired using dependency injection. Within Angular,
|
||||
the DI container is called the <a name="injector">"{@link di injector}"</a>.
|
||||
|
||||
To use DI, there needs to be a place where all the things that should work together are registered.
|
||||
In Angular, this is the purpose of the so called <a name="module">"{@link module modules}"</a>.
|
||||
In Angular, this is the purpose of the so-called <a name="module">"{@link module modules}"</a>.
|
||||
When Angular starts, it will use the configuration of the module with the name defined by the `ng-app` directive,
|
||||
including the configuration of all modules that this module depends on.
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests
|
||||
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
|
||||
to control browsers and simulate user actions.
|
||||
|
||||
For more information on Protractor, view [getting started](https://github.com/angular/protractor/blob/master/docs/getting-started.md)
|
||||
or the [api docs](https://github.com/angular/protractor/blob/master/docs/api.md).
|
||||
For more information on Protractor, view [getting started](http://angular.github.io/protractor/#/getting-started)
|
||||
or the [api docs](http://angular.github.io/protractor/#/api).
|
||||
|
||||
Protractor uses [Jasmine](http://jasmine.github.io/1.3/introduction.html) for its test syntax.
|
||||
As in unit testing, a test file is comprised of one or
|
||||
|
||||
@@ -56,6 +56,9 @@ for other directives to augment its behavior.
|
||||
|
||||
Note that `novalidate` is used to disable browser's native form validation.
|
||||
|
||||
The value of `ngModel` won't be set unless it passes validation for the input field.
|
||||
For example: inputs of type `email` must have a value in the form of `user@domain`.
|
||||
|
||||
|
||||
|
||||
# Using CSS classes
|
||||
|
||||
@@ -44,6 +44,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
* **Security:** {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
|
||||
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
|
||||
* **Mobile:** {@link ngTouch Touch events}
|
||||
* **Accessibility:** {@link guide/accessibility ngAria}
|
||||
|
||||
### Testing
|
||||
|
||||
|
||||
@@ -41,3 +41,35 @@ The page should reload and the debug information should now be available.
|
||||
|
||||
For more see the docs pages on {@link ng.$compileProvider#debugInfoEnabled `$compileProvider`}
|
||||
and {@link angular.reloadWithDebugInfo `angular.reloadWithDebugInfo`}.
|
||||
|
||||
## Strict DI Mode
|
||||
|
||||
Using strict di mode in your production application will throw errors when a injectable
|
||||
function is not
|
||||
{@link di#dependency-annotation annotated explicitly}. Strict di mode is intended to help
|
||||
you make sure that your code will work when minified. However, it also will force you to
|
||||
make sure that your injectable functions are explicitly annotated which will improve
|
||||
angular's performance when injecting dependencies in your injectable functions because it
|
||||
doesn't have to dynamically discover a function's dependencies. It is recommended to
|
||||
automate the explicit annotation via a tool like
|
||||
[ng-annotate](https://github.com/olov/ng-annotate) when you deploy to production (and enable
|
||||
strict di mode)
|
||||
|
||||
To enable strict di mode, you have two options:
|
||||
|
||||
```html
|
||||
<div ng-app="myApp" ng-strict-di>
|
||||
<!-- your app here -->
|
||||
</div>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
angular.bootstrap(document, ['myApp'], {
|
||||
strictDi: true
|
||||
});
|
||||
```
|
||||
|
||||
For more information, see the
|
||||
{@link di#using-strict-dependency-injection DI Guide}.
|
||||
|
||||
@@ -339,6 +339,18 @@ the dirty checking function must be efficient. Care should be taken that the dir
|
||||
function does not do any DOM access, as DOM access is orders of magnitude slower than property
|
||||
access on JavaScript object.
|
||||
|
||||
### Scope `$watch` Depths
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-scope-watch-strategies.png">
|
||||
|
||||
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
|
||||
|
||||
- Watching *by reference* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
|
||||
- Watching *collection contents* ({@link
|
||||
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
|
||||
- Watching *by value* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener, true)`) detects any change in an arbitrarily nested data structure. It is the most powerful change detection strategy, but also the most expensive. A full traversal of the nested data structure is needed on each digest, and a full copy of it needs to be held in memory.
|
||||
|
||||
## Integration with the browser event loop
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-runtime.png">
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ really is that easy to set up any functional, readable, end-to-end test.
|
||||
### Running End to End Tests with Protractor
|
||||
Even though the syntax of this test looks very much like our controller unit test written with
|
||||
Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
|
||||
about the Protractor APIs at https://github.com/angular/protractor/blob/master/docs/api.md.
|
||||
about the Protractor APIs at http://angular.github.io/protractor/#/api.
|
||||
|
||||
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
|
||||
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
|
||||
|
||||
@@ -236,7 +236,9 @@ the response is received:
|
||||
```
|
||||
|
||||
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
|
||||
promise returned by the `$http` service to be resolved with the trained response.
|
||||
promise returned by the `$http` service to be resolved with the trained response. See
|
||||
'Flushing HTTP requests' in the {@link ngMock.$httpBackend mock $httpBackend} documentation for
|
||||
a full explanation of why this is necessary.
|
||||
|
||||
* We make the assertions, verifying that the phone model now exists on the scope.
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ You can now rerun `npm run protractor` to see the tests run.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Using the [Protractor API](https://github.com/angular/protractor/blob/master/docs/api.md),
|
||||
* Using the [Protractor API](http://angular.github.io/protractor/#/api),
|
||||
write a test that verifies that we display 4 thumbnail images on the Nexus S details page.
|
||||
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript
|
||||
animations on top of the template code we created before.
|
||||
|
||||
* Used the `ngAnimate` to enable animations throughout the application.
|
||||
* Common `ng` directives automatically trigger hooks for animations to tap into.
|
||||
* We now use the `ngAnimate` module to enable animations throughout the application.
|
||||
* We also use common `ng` directives to automatically trigger hooks for animations to tap into.
|
||||
* When an animation is found then the animation will run in between the standard DOM operation that
|
||||
is being issued on the element at the given time (e.g. inserting and removing nodes on
|
||||
{@link ngRepeat `ngRepeat`} or adding and removing classes on
|
||||
@@ -369,7 +369,8 @@ occur whenever the CSS class itself changes.
|
||||
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added
|
||||
to the matching profile image and the animation plays.
|
||||
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first:
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first. Notice that we
|
||||
have changed the way we display our large image:
|
||||
|
||||
__`app/partials/phone-detail.html`.__
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
Generated
+6
-6
@@ -4810,7 +4810,7 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"dependencies": {
|
||||
"request": {
|
||||
"version": "2.36.0",
|
||||
@@ -4834,7 +4834,7 @@
|
||||
"version": "0.12.1",
|
||||
"dependencies": {
|
||||
"punycode": {
|
||||
"version": "1.3.1"
|
||||
"version": "1.3.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4842,7 +4842,7 @@
|
||||
"version": "0.1.4",
|
||||
"dependencies": {
|
||||
"combined-stream": {
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.7",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "0.0.5"
|
||||
@@ -4897,7 +4897,7 @@
|
||||
}
|
||||
},
|
||||
"selenium-webdriver": {
|
||||
"version": "2.43.5",
|
||||
"version": "2.44.0",
|
||||
"dependencies": {
|
||||
"tmp": {
|
||||
"version": "0.0.24"
|
||||
@@ -4906,7 +4906,7 @@
|
||||
"version": "0.4.4",
|
||||
"dependencies": {
|
||||
"sax": {
|
||||
"version": "0.6.0"
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
@@ -4966,7 +4966,7 @@
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.1.32",
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "1.3.1",
|
||||
"protractor": "1.4.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
|
||||
+49
-5
@@ -622,10 +622,17 @@
|
||||
*
|
||||
*
|
||||
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
|
||||
* @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives.
|
||||
* @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
|
||||
*
|
||||
* <div class="alert alert-error">
|
||||
* **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
|
||||
* e.g. will not use the right outer scope. Please pass the transclude function as a
|
||||
* `parentBoundTranscludeFn` to the link function instead.
|
||||
* </div>
|
||||
*
|
||||
* @param {number} maxPriority only apply directives lower than given priority (Only effects the
|
||||
* root element(s), not their children)
|
||||
* @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template
|
||||
* @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template
|
||||
* (a DOM element/tree) to a scope. Where:
|
||||
*
|
||||
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
|
||||
@@ -637,6 +644,19 @@
|
||||
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
|
||||
* * `scope` - is the current scope with which the linking function is working with.
|
||||
*
|
||||
* * `options` - An optional object hash with linking options. If `options` is provided, then the following
|
||||
* keys may be used to control linking behavior:
|
||||
*
|
||||
* * `parentBoundTranscludeFn` - the transclude function made available to
|
||||
* directives; if given, it will be passed through to the link functions of
|
||||
* directives found in `element` during compilation.
|
||||
* * `transcludeControllers` - an object hash with keys that map controller names
|
||||
* to controller instances; if given, it will make the controllers
|
||||
* available to directives.
|
||||
* * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
|
||||
* the cloned elements; only needed for transcludes that are allowed to contain non html
|
||||
* elements (e.g. SVG elements). See also the directive.controller property.
|
||||
*
|
||||
* Calling the linking function returns the element of the template. It is either the original
|
||||
* element passed in, or the clone of the element if the `cloneAttachFn` is provided.
|
||||
*
|
||||
@@ -1155,8 +1175,22 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
maxPriority, ignoreDirective, previousCompileContext);
|
||||
compile.$$addScopeClass($compileNodes);
|
||||
var namespace = null;
|
||||
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement) {
|
||||
return function publicLinkFn(scope, cloneConnectFn, options) {
|
||||
assertArg(scope, 'scope');
|
||||
|
||||
options = options || {};
|
||||
var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
|
||||
transcludeControllers = options.transcludeControllers,
|
||||
futureParentElement = options.futureParentElement;
|
||||
|
||||
// When `parentBoundTranscludeFn` is passed, it is a
|
||||
// `controllersBoundTransclude` function (it was previously passed
|
||||
// as `transclude` to directive.link) so we must unwrap it to get
|
||||
// its `boundTranscludeFn`
|
||||
if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
|
||||
parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
|
||||
}
|
||||
|
||||
if (!namespace) {
|
||||
namespace = detectNamespaceForChildElements(futureParentElement);
|
||||
}
|
||||
@@ -1326,7 +1360,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
transcludedScope.$$transcluded = true;
|
||||
}
|
||||
|
||||
return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
|
||||
return transcludeFn(transcludedScope, cloneFn, {
|
||||
parentBoundTranscludeFn: previousBoundTranscludeFn,
|
||||
transcludeControllers: controllers,
|
||||
futureParentElement: futureParentElement
|
||||
});
|
||||
};
|
||||
|
||||
return boundTranscludeFn;
|
||||
@@ -1794,7 +1832,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
isolateScope = scope.$new(true);
|
||||
}
|
||||
|
||||
transcludeFn = boundTranscludeFn && controllersBoundTransclude;
|
||||
if (boundTranscludeFn) {
|
||||
// track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
|
||||
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
|
||||
transcludeFn = controllersBoundTransclude;
|
||||
transcludeFn.$$boundTransclude = boundTranscludeFn;
|
||||
}
|
||||
|
||||
if (controllerDirectives) {
|
||||
// TODO: merge `controllers` and `elementControllers` into single object.
|
||||
controllers = {};
|
||||
|
||||
@@ -200,7 +200,7 @@ var inputType = {
|
||||
|
||||
/**
|
||||
* @ngdoc input
|
||||
* @name input[dateTimeLocal]
|
||||
* @name input[datetime-local]
|
||||
*
|
||||
* @description
|
||||
* Input with datetime validation and transformation. In browsers that do not yet support
|
||||
@@ -1576,9 +1576,6 @@ var VALID_CLASS = 'ng-valid',
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param {string} name The name of the validator.
|
||||
* @param {Function} validationFn The validation function that will be run.
|
||||
*
|
||||
* @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
|
||||
* view value has changed. It is called with no arguments, and its return value is ignored.
|
||||
* This can be used in place of additional $watches against the model value.
|
||||
@@ -1592,6 +1589,7 @@ var VALID_CLASS = 'ng-valid',
|
||||
* @property {boolean} $dirty True if user has already interacted with the control.
|
||||
* @property {boolean} $valid True if there is no error.
|
||||
* @property {boolean} $invalid True if at least one error on the control.
|
||||
* @property {string} $name The name attribute of the control.
|
||||
*
|
||||
* @description
|
||||
*
|
||||
@@ -2292,7 +2290,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* - {@link input[email] email}
|
||||
* - {@link input[url] url}
|
||||
* - {@link input[date] date}
|
||||
* - {@link input[dateTimeLocal] dateTimeLocal}
|
||||
* - {@link input[datetime-local] datetime-local}
|
||||
* - {@link input[time] time}
|
||||
* - {@link input[month] month}
|
||||
* - {@link input[week] week}
|
||||
@@ -2772,12 +2770,17 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
|
||||
* @name ngValue
|
||||
*
|
||||
* @description
|
||||
* Binds the given expression to the value of `option` or `input[radio]`, so
|
||||
* that when the element is selected, the `ngModel` of that element is set to
|
||||
* Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`},
|
||||
* so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to
|
||||
* the bound value.
|
||||
*
|
||||
* `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as
|
||||
* shown below.
|
||||
* `ngValue` is useful when dynamically generating lists of radio buttons using
|
||||
* {@link ngRepeat `ngRepeat`}, as shown below.
|
||||
*
|
||||
* Likewise, `ngValue` can be used to generate `<option>` elements for
|
||||
* the {@link select `select`} element. In that case however, only strings are supported
|
||||
* for the `value `attribute, so the resulting `ngModel` will always be a string.
|
||||
* Support for `select` models with non-string values is available via `ngOptions`.
|
||||
*
|
||||
* @element input
|
||||
* @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
|
||||
|
||||
@@ -53,7 +53,11 @@ forEach(
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function($element, attr) {
|
||||
var fn = $parse(attr[directiveName]);
|
||||
// We expose the powerful $event object on the scope that provides access to the Window,
|
||||
// etc. that isn't protected by the fast paths in $parse. We explicitly request better
|
||||
// checks at the cost of speed since event handler expressions are not executed as
|
||||
// frequently as regular change detection.
|
||||
var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
return function ngEventHandler(scope, element) {
|
||||
element.on(eventName, function(event) {
|
||||
var callback = function() {
|
||||
|
||||
@@ -284,7 +284,7 @@ var ngIncludeFillContentDirective = ['$compile',
|
||||
$compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
|
||||
function namespaceAdaptedClone(clone) {
|
||||
$element.append(clone);
|
||||
}, undefined, undefined, $element);
|
||||
}, {futureParentElement: $element});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -483,7 +483,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
if (multiple) {
|
||||
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
|
||||
} else {
|
||||
return viewValue == callExpression(compareValueFn, key, value);
|
||||
return viewValue === callExpression(compareValueFn, key, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+64
-25
@@ -852,64 +852,85 @@ function setter(obj, path, setValue, fullExp) {
|
||||
return setValue;
|
||||
}
|
||||
|
||||
var getterFnCache = createMap();
|
||||
var getterFnCacheDefault = createMap();
|
||||
var getterFnCacheExpensive = createMap();
|
||||
|
||||
function isPossiblyDangerousMemberName(name) {
|
||||
return name == 'constructor';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the "Black Hole" variant from:
|
||||
* - http://jsperf.com/angularjs-parse-getter/4
|
||||
* - http://jsperf.com/path-evaluation-simplified/7
|
||||
*/
|
||||
function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) {
|
||||
function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) {
|
||||
ensureSafeMemberName(key0, fullExp);
|
||||
ensureSafeMemberName(key1, fullExp);
|
||||
ensureSafeMemberName(key2, fullExp);
|
||||
ensureSafeMemberName(key3, fullExp);
|
||||
ensureSafeMemberName(key4, fullExp);
|
||||
var eso = function(o) {
|
||||
return ensureSafeObject(o, fullExp);
|
||||
};
|
||||
var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity;
|
||||
var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity;
|
||||
var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity;
|
||||
var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity;
|
||||
var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity;
|
||||
|
||||
return function cspSafeGetter(scope, locals) {
|
||||
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
|
||||
|
||||
if (pathVal == null) return pathVal;
|
||||
pathVal = pathVal[key0];
|
||||
pathVal = eso0(pathVal[key0]);
|
||||
|
||||
if (!key1) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key1];
|
||||
pathVal = eso1(pathVal[key1]);
|
||||
|
||||
if (!key2) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key2];
|
||||
pathVal = eso2(pathVal[key2]);
|
||||
|
||||
if (!key3) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key3];
|
||||
pathVal = eso3(pathVal[key3]);
|
||||
|
||||
if (!key4) return pathVal;
|
||||
if (pathVal == null) return undefined;
|
||||
pathVal = pathVal[key4];
|
||||
pathVal = eso4(pathVal[key4]);
|
||||
|
||||
return pathVal;
|
||||
};
|
||||
}
|
||||
|
||||
function getterFn(path, options, fullExp) {
|
||||
var fn = getterFnCache[path];
|
||||
function getterFnWithEnsureSafeObject(fn, fullExpression) {
|
||||
return function(s, l) {
|
||||
return fn(s, l, ensureSafeObject, fullExpression);
|
||||
};
|
||||
}
|
||||
|
||||
function getterFn(path, options, fullExp) {
|
||||
var expensiveChecks = options.expensiveChecks;
|
||||
var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault);
|
||||
var fn = getterFnCache[path];
|
||||
if (fn) return fn;
|
||||
|
||||
|
||||
var pathKeys = path.split('.'),
|
||||
pathKeysLength = pathKeys.length;
|
||||
|
||||
// http://jsperf.com/angularjs-parse-getter/6
|
||||
if (options.csp) {
|
||||
if (pathKeysLength < 6) {
|
||||
fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp);
|
||||
fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks);
|
||||
} else {
|
||||
fn = function cspSafeGetter(scope, locals) {
|
||||
var i = 0, val;
|
||||
do {
|
||||
val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++],
|
||||
pathKeys[i++], fullExp)(scope, locals);
|
||||
pathKeys[i++], fullExp, expensiveChecks)(scope, locals);
|
||||
|
||||
locals = undefined; // clear after first iteration
|
||||
scope = val;
|
||||
@@ -919,22 +940,33 @@ function getterFn(path, options, fullExp) {
|
||||
}
|
||||
} else {
|
||||
var code = '';
|
||||
if (expensiveChecks) {
|
||||
code += 's = eso(s, fe);\nl = eso(l, fe);\n';
|
||||
}
|
||||
var needsEnsureSafeObject = expensiveChecks;
|
||||
forEach(pathKeys, function(key, index) {
|
||||
ensureSafeMemberName(key, fullExp);
|
||||
code += 'if(s == null) return undefined;\n' +
|
||||
's='+ (index
|
||||
var lookupJs = (index
|
||||
// we simply dereference 's' on any .dot notation
|
||||
? 's'
|
||||
// but if we are first then we check locals first, and if so read it first
|
||||
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n';
|
||||
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
|
||||
if (expensiveChecks || isPossiblyDangerousMemberName(key)) {
|
||||
lookupJs = 'eso(' + lookupJs + ', fe)';
|
||||
needsEnsureSafeObject = true;
|
||||
}
|
||||
code += 'if(s == null) return undefined;\n' +
|
||||
's=' + lookupJs + ';\n';
|
||||
});
|
||||
code += 'return s;';
|
||||
|
||||
/* jshint -W054 */
|
||||
var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals
|
||||
var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject
|
||||
/* jshint +W054 */
|
||||
evaledFnGetter.toString = valueFn(code);
|
||||
|
||||
if (needsEnsureSafeObject) {
|
||||
evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp);
|
||||
}
|
||||
fn = evaledFnGetter;
|
||||
}
|
||||
|
||||
@@ -1004,15 +1036,20 @@ function getValueOf(value) {
|
||||
* service.
|
||||
*/
|
||||
function $ParseProvider() {
|
||||
var cache = createMap();
|
||||
var cacheDefault = createMap();
|
||||
var cacheExpensive = createMap();
|
||||
|
||||
var $parseOptions = {
|
||||
csp: false
|
||||
};
|
||||
|
||||
|
||||
this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
|
||||
$parseOptions.csp = $sniffer.csp;
|
||||
var $parseOptions = {
|
||||
csp: $sniffer.csp,
|
||||
expensiveChecks: false
|
||||
},
|
||||
$parseOptionsExpensive = {
|
||||
csp: $sniffer.csp,
|
||||
expensiveChecks: true
|
||||
};
|
||||
|
||||
function wrapSharedExpression(exp) {
|
||||
var wrapped = exp;
|
||||
@@ -1029,13 +1066,14 @@ function $ParseProvider() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
return function $parse(exp, interceptorFn) {
|
||||
return function $parse(exp, interceptorFn, expensiveChecks) {
|
||||
var parsedExpression, oneTime, cacheKey;
|
||||
|
||||
switch (typeof exp) {
|
||||
case 'string':
|
||||
cacheKey = exp = exp.trim();
|
||||
|
||||
var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
|
||||
parsedExpression = cache[cacheKey];
|
||||
|
||||
if (!parsedExpression) {
|
||||
@@ -1044,8 +1082,9 @@ function $ParseProvider() {
|
||||
exp = exp.substring(2);
|
||||
}
|
||||
|
||||
var lexer = new Lexer($parseOptions);
|
||||
var parser = new Parser(lexer, $filter, $parseOptions);
|
||||
var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
|
||||
var lexer = new Lexer(parseOptions);
|
||||
var parser = new Parser(lexer, $filter, parseOptions);
|
||||
parsedExpression = parser.parse(exp);
|
||||
|
||||
if (parsedExpression.constant) {
|
||||
@@ -1218,7 +1257,7 @@ function $ParseProvider() {
|
||||
var result = interceptorFn(value, scope, locals);
|
||||
// we only return the interceptor's result if the
|
||||
// initial value is defined (for bind-once)
|
||||
return isDefined(value) ? result : value;
|
||||
return isDefined(value) || interceptorFn.$stateful ? result : value;
|
||||
};
|
||||
|
||||
// Propagate $$watchDelegates other then inputsWatchDelegate
|
||||
|
||||
@@ -113,6 +113,10 @@ function $RootScopeProvider() {
|
||||
expect(parent.salutation).toEqual('Hello');
|
||||
* ```
|
||||
*
|
||||
* When interacting with `Scope` in tests, additional helper methods are available on the
|
||||
* instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
|
||||
* details.
|
||||
*
|
||||
*
|
||||
* @param {Object.<string, function()>=} providers Map of service factory which need to be
|
||||
* provided for the current scope. Defaults to {@link ng}.
|
||||
@@ -551,6 +555,9 @@ function $RootScopeProvider() {
|
||||
newValue = _value;
|
||||
var newLength, key, bothNaN, newItem, oldItem;
|
||||
|
||||
// If the new value is undefined, then return undefined as the watch may be a one-time watch
|
||||
if (isUndefined(newValue)) return;
|
||||
|
||||
if (!isObject(newValue)) { // if primitive
|
||||
if (oldValue !== newValue) {
|
||||
oldValue = newValue;
|
||||
|
||||
@@ -238,5 +238,16 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
.directive('ngDisabled', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
|
||||
}])
|
||||
.directive('ngMessages', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngMessages',
|
||||
link: function(scope, elem, attr, ngMessages) {
|
||||
if (!elem.attr('aria-live')) {
|
||||
elem.attr('aria-live', 'assertive');
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('ngClick', ngAriaTabindex)
|
||||
.directive('ngDblclick', ngAriaTabindex);
|
||||
|
||||
Vendored
+87
@@ -1823,6 +1823,7 @@ angular.module('ngMock', ['ng']).provider({
|
||||
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
|
||||
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
|
||||
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
|
||||
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
|
||||
}]);
|
||||
|
||||
/**
|
||||
@@ -2031,6 +2032,92 @@ angular.mock.e2e.$httpBackendDecorator =
|
||||
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name $rootScope.Scope
|
||||
* @module ngMock
|
||||
* @description
|
||||
* {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These
|
||||
* methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when
|
||||
* `ngMock` module is loaded.
|
||||
*
|
||||
* In addition to all the regular `Scope` methods, the following helper methods are available:
|
||||
*/
|
||||
angular.mock.$RootScopeDecorator = function($delegate) {
|
||||
|
||||
var $rootScopePrototype = Object.getPrototypeOf($delegate);
|
||||
|
||||
$rootScopePrototype.$countChildScopes = countChildScopes;
|
||||
$rootScopePrototype.$countWatchers = countWatchers;
|
||||
|
||||
return $delegate;
|
||||
|
||||
// ------------------------------------------------------------------------------------------ //
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $rootScope.Scope#$countChildScopes
|
||||
* @module ngMock
|
||||
* @description
|
||||
* Counts all the direct and indirect child scopes of the current scope.
|
||||
*
|
||||
* The current scope is excluded from the count. The count includes all isolate child scopes.
|
||||
*
|
||||
* @returns {number} Total number of child scopes.
|
||||
*/
|
||||
function countChildScopes() {
|
||||
// jshint validthis: true
|
||||
var count = 0; // exclude the current scope
|
||||
var pendingChildHeads = [this.$$childHead];
|
||||
var currentScope;
|
||||
|
||||
while (pendingChildHeads.length) {
|
||||
currentScope = pendingChildHeads.shift();
|
||||
|
||||
while (currentScope) {
|
||||
count += 1;
|
||||
pendingChildHeads.push(currentScope.$$childHead);
|
||||
currentScope = currentScope.$$nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $rootScope.Scope#$countWatchers
|
||||
* @module ngMock
|
||||
* @description
|
||||
* Counts all the watchers of direct and indirect child scopes of the current scope.
|
||||
*
|
||||
* The watchers of the current scope are included in the count and so are all the watchers of
|
||||
* isolate child scopes.
|
||||
*
|
||||
* @returns {number} Total number of watchers.
|
||||
*/
|
||||
function countWatchers() {
|
||||
// jshint validthis: true
|
||||
var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope
|
||||
var pendingChildHeads = [this.$$childHead];
|
||||
var currentScope;
|
||||
|
||||
while (pendingChildHeads.length) {
|
||||
currentScope = pendingChildHeads.shift();
|
||||
|
||||
while (currentScope) {
|
||||
count += currentScope.$$watchers ? currentScope.$$watchers.length : 0;
|
||||
pendingChildHeads.push(currentScope.$$childHead);
|
||||
currentScope = currentScope.$$nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (window.jasmine || window.mocha) {
|
||||
|
||||
var currentSpec = null,
|
||||
|
||||
@@ -141,10 +141,14 @@ function $RouteProvider() {
|
||||
* Adds a new route definition to the `$route` service.
|
||||
*/
|
||||
this.when = function(path, route) {
|
||||
//copy original route object to preserve params inherited from proto chain
|
||||
var routeCopy = angular.copy(route);
|
||||
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
|
||||
routeCopy.reloadOnSearch = true;
|
||||
}
|
||||
routes[path] = angular.extend(
|
||||
{reloadOnSearch: true},
|
||||
route,
|
||||
path && pathRegExp(path, route)
|
||||
routeCopy,
|
||||
path && pathRegExp(path, routeCopy)
|
||||
);
|
||||
|
||||
// create redirection for trailing slashes
|
||||
@@ -155,7 +159,7 @@ function $RouteProvider() {
|
||||
|
||||
routes[redirectPath] = angular.extend(
|
||||
{redirectTo: path},
|
||||
pathRegExp(redirectPath, route)
|
||||
pathRegExp(redirectPath, routeCopy)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -437,7 +441,7 @@ function $RouteProvider() {
|
||||
* {@link ng.$location $location} hasn't changed.
|
||||
*
|
||||
* As a result of that, {@link ngRoute.directive:ngView ngView}
|
||||
* creates new scope, reinstantiates the controller.
|
||||
* creates new scope and reinstantiates the controller.
|
||||
*/
|
||||
reload: function() {
|
||||
forceReload = true;
|
||||
|
||||
+146
-33
@@ -26,6 +26,17 @@ describe('$compile', function() {
|
||||
return !!d.toString().match(/SVGForeignObject/);
|
||||
}
|
||||
|
||||
function getChildScopes(scope) {
|
||||
var children = [];
|
||||
if (!scope.$$childHead) { return children; }
|
||||
var childScope = scope.$$childHead;
|
||||
do {
|
||||
children.push(childScope);
|
||||
children = children.concat(getChildScopes(childScope));
|
||||
} while ((childScope = childScope.$$nextSibling));
|
||||
return children;
|
||||
}
|
||||
|
||||
var element, directive, $compile, $rootScope;
|
||||
|
||||
beforeEach(module(provideLog, function($provide, $compileProvider) {
|
||||
@@ -3356,6 +3367,31 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should continue with a digets cycle when there is a two-way binding from the child to the parent', function() {
|
||||
module(function() {
|
||||
directive('hello', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: { greeting: '=' },
|
||||
template: '<button ng-click="setGreeting()">Say hi!</button>',
|
||||
link: function(scope) {
|
||||
scope.setGreeting = function() { scope.greeting = 'Hello!'; };
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($rootScope) {
|
||||
compile('<div>' +
|
||||
'<p>{{greeting}}</p>' +
|
||||
'<div><hello greeting="greeting"></hello></div>' +
|
||||
'</div>');
|
||||
$rootScope.$digest();
|
||||
browserTrigger(element.find('button'), 'click');
|
||||
expect(element.find('p').text()).toBe('Hello!');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -5209,26 +5245,103 @@ describe('$compile', function() {
|
||||
});
|
||||
|
||||
|
||||
// see issue https://github.com/angular/angular.js/issues/9413
|
||||
describe('passing a parent bound transclude function to the link ' +
|
||||
'function returned from `$compile`', function() {
|
||||
|
||||
beforeEach(module(function() {
|
||||
directive('lazyCompile', function($compile) {
|
||||
return {
|
||||
compile: function(tElement, tAttrs) {
|
||||
var content = tElement.contents();
|
||||
tElement.empty();
|
||||
return function(scope, element, attrs, ctrls, transcludeFn) {
|
||||
element.append(content);
|
||||
$compile(content)(scope, undefined, {
|
||||
parentBoundTranscludeFn: transcludeFn
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
directive('toggle', valueFn({
|
||||
scope: {t: '=toggle'},
|
||||
transclude: true,
|
||||
template: '<div ng-if="t"><lazy-compile><div ng-transclude></div></lazy-compile></div>'
|
||||
}));
|
||||
}));
|
||||
|
||||
it('should preserve the bound scope', function() {
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<div ng-init="outer=true"></div>' +
|
||||
'<div toggle="t">' +
|
||||
'<span ng-if="outer">Success</span><span ng-if="!outer">Error</span>' +
|
||||
'</div>' +
|
||||
'</div>')($rootScope);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect($rootScope.$countChildScopes()).toBe(4);
|
||||
expect(element.text()).toBe('Success');
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect($rootScope.$countChildScopes()).toBe(4);
|
||||
expect(element.text()).toBe('Success');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should preserve the bound scope when using recursive transclusion', function() {
|
||||
|
||||
directive('recursiveTransclude', valueFn({
|
||||
transclude: true,
|
||||
template: '<div><lazy-compile><div ng-transclude></div></lazy-compile></div>'
|
||||
}));
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
'<div ng-init="outer=true"></div>' +
|
||||
'<div toggle="t">' +
|
||||
'<div recursive-transclude>' +
|
||||
'<span ng-if="outer">Success</span><span ng-if="!outer">Error</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>')($rootScope);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect($rootScope.$countChildScopes()).toBe(4);
|
||||
expect(element.text()).toBe('Success');
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect($rootScope.$countChildScopes()).toBe(4);
|
||||
expect(element.text()).toBe('Success');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// see issue https://github.com/angular/angular.js/issues/9095
|
||||
describe('removing a transcluded element', function() {
|
||||
|
||||
function countScopes($rootScope) {
|
||||
return [$rootScope].concat(
|
||||
getChildScopes($rootScope)
|
||||
).length;
|
||||
}
|
||||
|
||||
function getChildScopes(scope) {
|
||||
var children = [];
|
||||
if (!scope.$$childHead) { return children; }
|
||||
var childScope = scope.$$childHead;
|
||||
do {
|
||||
children.push(childScope);
|
||||
children = children.concat(getChildScopes(childScope));
|
||||
} while ((childScope = childScope.$$nextSibling));
|
||||
return children;
|
||||
}
|
||||
|
||||
beforeEach(module(function() {
|
||||
directive('toggle', function() {
|
||||
return {
|
||||
@@ -5251,22 +5364,22 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.text()).toContain('msg-1');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
|
||||
expect(countScopes($rootScope)).toEqual(4);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.text()).not.toContain('msg-1');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.text()).toContain('msg-1');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
|
||||
expect(countScopes($rootScope)).toEqual(4);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.text()).not.toContain('msg-1');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
@@ -5283,22 +5396,22 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.text()).toContain('msg-1msg-1');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
|
||||
expect(countScopes($rootScope)).toEqual(4);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.text()).not.toContain('msg-1msg-1');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.text()).toContain('msg-1msg-1');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion, ngRepeat
|
||||
expect(countScopes($rootScope)).toEqual(4);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.text()).not.toContain('msg-1msg-1');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
@@ -5314,22 +5427,22 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.html()).toContain('some comment');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion
|
||||
expect(countScopes($rootScope)).toEqual(3);
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.html()).not.toContain('some comment');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.html()).toContain('some comment');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion
|
||||
expect(countScopes($rootScope)).toEqual(3);
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.html()).not.toContain('some comment');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
it('should not leak the transclude scope if the transcluded contains only text nodes',
|
||||
@@ -5344,22 +5457,22 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.html()).toContain('some text');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion
|
||||
expect(countScopes($rootScope)).toEqual(3);
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.html()).not.toContain('some text');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
$rootScope.$apply('t = true');
|
||||
expect(element.html()).toContain('some text');
|
||||
// Expected scopes: $rootScope, ngIf, transclusion
|
||||
expect(countScopes($rootScope)).toEqual(3);
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
$rootScope.$apply('t = false');
|
||||
expect(element.html()).not.toContain('some text');
|
||||
// Expected scopes: $rootScope
|
||||
expect(countScopes($rootScope)).toEqual(1);
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
it('should mark as destroyed all sub scopes of the scope being destroyed',
|
||||
|
||||
@@ -90,6 +90,25 @@ describe('event directives', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('security', function() {
|
||||
it('should allow access to the $event object', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event">BTN</button>')(scope);
|
||||
element.triggerHandler('click');
|
||||
expect(scope.e.target).toBe(element[0]);
|
||||
}));
|
||||
|
||||
it('should block access to DOM nodes (e.g. exposed via $event)', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event.target">BTN</button>')(scope);
|
||||
expect(function() {
|
||||
element.triggerHandler('click');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! ' +
|
||||
'Expression: e = $event.target');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('blur', function() {
|
||||
|
||||
describe('call the listener asynchronously during $apply', function() {
|
||||
|
||||
@@ -1629,6 +1629,20 @@ describe('select', function() {
|
||||
expect(element.val()).toEqual('?');
|
||||
expect(element.find('option').eq(0).attr('selected')).toEqual('selected');
|
||||
});
|
||||
|
||||
|
||||
it('should select the correct option for selectAs and falsy values', function() {
|
||||
scope.values = [{value: 0, label: 'zero'}, {value: 1, label: 'one'}];
|
||||
scope.selected = '';
|
||||
createSelect({
|
||||
'ng-model': 'selected',
|
||||
'ng-options': 'option.value as option.label for option in values'
|
||||
});
|
||||
|
||||
var option = element.find('option').eq(0);
|
||||
expect(option.val()).toBe('?');
|
||||
expect(option.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
+39
-3
@@ -3,9 +3,11 @@
|
||||
describe('parser', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
/* global getterFnCache: true */
|
||||
// clear cache
|
||||
getterFnCache = createMap();
|
||||
/* global getterFnCacheDefault: true */
|
||||
/* global getterFnCacheExpensive: true */
|
||||
// clear caches
|
||||
getterFnCacheDefault = createMap();
|
||||
getterFnCacheExpensive = createMap();
|
||||
});
|
||||
|
||||
|
||||
@@ -672,6 +674,24 @@ describe('parser', function() {
|
||||
|
||||
describe('sandboxing', function() {
|
||||
describe('Function constructor', function() {
|
||||
it('should not tranverse the Function constructor in the getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor');
|
||||
|
||||
});
|
||||
|
||||
it('should not allow access to the Function prototype in the getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('toString.constructor.prototype');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: toString.constructor.prototype');
|
||||
|
||||
});
|
||||
|
||||
it('should NOT allow access to Function constructor in getter', function() {
|
||||
|
||||
expect(function() {
|
||||
@@ -765,6 +785,22 @@ describe('parser', function() {
|
||||
'Expression: foo["bar"]');
|
||||
|
||||
});
|
||||
|
||||
describe('expensiveChecks', function() {
|
||||
it('should block access to window object even when aliased', inject(function($parse, $window) {
|
||||
scope.foo = {w: $window};
|
||||
// This isn't blocked for performance.
|
||||
expect(scope.$eval($parse('foo.w'))).toBe($window);
|
||||
// Event handlers use the more expensive path for better protection since they expose
|
||||
// the $event object on the scope.
|
||||
expect(function() {
|
||||
scope.$eval($parse('foo.w', null, true));
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.w');
|
||||
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Function prototype functions', function() {
|
||||
|
||||
@@ -400,6 +400,16 @@ describe('$aria', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('announcing ngMessages', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should attach aria-live', function() {
|
||||
var element = [
|
||||
$compile('<div ng-messages="myForm.myName.$error">')(scope)
|
||||
];
|
||||
expectAriaAttrOnEachElement(element, 'aria-live', "assertive");
|
||||
});
|
||||
});
|
||||
|
||||
describe('aria-value when disabled', function() {
|
||||
beforeEach(configAriaProvider({
|
||||
|
||||
Vendored
+168
@@ -1507,6 +1507,174 @@ describe('ngMock', function() {
|
||||
expect($rootElement.text()).toEqual('');
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('$rootScopeDecorator', function() {
|
||||
|
||||
describe('$countChildScopes', function() {
|
||||
|
||||
it('should return 0 when no child scopes', inject(function($rootScope) {
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
var childScope = $rootScope.$new();
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
expect(childScope.$countChildScopes()).toBe(0);
|
||||
|
||||
var grandChildScope = childScope.$new();
|
||||
expect(childScope.$countChildScopes()).toBe(1);
|
||||
expect(grandChildScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
it('should correctly navigate complex scope tree', inject(function($rootScope) {
|
||||
var child;
|
||||
|
||||
$rootScope.$new();
|
||||
$rootScope.$new().$new().$new();
|
||||
child = $rootScope.$new().$new();
|
||||
child.$new();
|
||||
child.$new();
|
||||
child.$new().$new().$new();
|
||||
|
||||
expect($rootScope.$countChildScopes()).toBe(11);
|
||||
}));
|
||||
|
||||
|
||||
it('should provide the current count even after child destructions', inject(function($rootScope) {
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
|
||||
var childScope1 = $rootScope.$new();
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
|
||||
var childScope2 = $rootScope.$new();
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
|
||||
childScope1.$destroy();
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
|
||||
childScope2.$destroy();
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
it('should work with isolate scopes', inject(function($rootScope) {
|
||||
/*
|
||||
RS
|
||||
|
|
||||
CIS
|
||||
/ \
|
||||
GCS GCIS
|
||||
*/
|
||||
|
||||
var childIsolateScope = $rootScope.$new(true);
|
||||
expect($rootScope.$countChildScopes()).toBe(1);
|
||||
|
||||
var grandChildScope = childIsolateScope.$new();
|
||||
expect($rootScope.$countChildScopes()).toBe(2);
|
||||
expect(childIsolateScope.$countChildScopes()).toBe(1);
|
||||
|
||||
var grandChildIsolateScope = childIsolateScope.$new(true);
|
||||
expect($rootScope.$countChildScopes()).toBe(3);
|
||||
expect(childIsolateScope.$countChildScopes()).toBe(2);
|
||||
|
||||
childIsolateScope.$destroy();
|
||||
expect($rootScope.$countChildScopes()).toBe(0);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('$countWatchers', function() {
|
||||
|
||||
it('should return the sum of watchers for the current scope and all of its children', inject(
|
||||
function($rootScope) {
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(0);
|
||||
|
||||
var childScope = $rootScope.$new();
|
||||
expect($rootScope.$countWatchers()).toBe(0);
|
||||
|
||||
childScope.$watch('foo');
|
||||
expect($rootScope.$countWatchers()).toBe(1);
|
||||
expect(childScope.$countWatchers()).toBe(1);
|
||||
|
||||
$rootScope.$watch('bar');
|
||||
childScope.$watch('baz');
|
||||
expect($rootScope.$countWatchers()).toBe(3);
|
||||
expect(childScope.$countWatchers()).toBe(2);
|
||||
}));
|
||||
|
||||
|
||||
it('should correctly navigate complex scope tree', inject(function($rootScope) {
|
||||
var child;
|
||||
|
||||
$rootScope.$watch('foo1');
|
||||
|
||||
$rootScope.$new();
|
||||
$rootScope.$new().$new().$new();
|
||||
|
||||
child = $rootScope.$new().$new();
|
||||
child.$watch('foo2');
|
||||
child.$new();
|
||||
child.$new();
|
||||
child = child.$new().$new().$new();
|
||||
child.$watch('foo3');
|
||||
child.$watch('foo4');
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(4);
|
||||
}));
|
||||
|
||||
|
||||
it('should provide the current count even after child destruction and watch deregistration',
|
||||
inject(function($rootScope) {
|
||||
|
||||
var deregisterWatch1 = $rootScope.$watch('exp1');
|
||||
|
||||
var childScope = $rootScope.$new();
|
||||
childScope.$watch('exp2');
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(2);
|
||||
|
||||
childScope.$destroy();
|
||||
expect($rootScope.$countWatchers()).toBe(1);
|
||||
|
||||
deregisterWatch1();
|
||||
expect($rootScope.$countWatchers()).toBe(0);
|
||||
}));
|
||||
|
||||
|
||||
it('should work with isolate scopes', inject(function($rootScope) {
|
||||
/*
|
||||
RS=1
|
||||
|
|
||||
CIS=1
|
||||
/ \
|
||||
GCS=1 GCIS=1
|
||||
*/
|
||||
|
||||
$rootScope.$watch('exp1');
|
||||
expect($rootScope.$countWatchers()).toBe(1);
|
||||
|
||||
var childIsolateScope = $rootScope.$new(true);
|
||||
childIsolateScope.$watch('exp2');
|
||||
expect($rootScope.$countWatchers()).toBe(2);
|
||||
expect(childIsolateScope.$countWatchers()).toBe(1);
|
||||
|
||||
var grandChildScope = childIsolateScope.$new();
|
||||
grandChildScope.$watch('exp3');
|
||||
|
||||
var grandChildIsolateScope = childIsolateScope.$new(true);
|
||||
grandChildIsolateScope.$watch('exp4');
|
||||
|
||||
expect($rootScope.$countWatchers()).toBe(4);
|
||||
expect(childIsolateScope.$countWatchers()).toBe(3);
|
||||
expect(grandChildScope.$countWatchers()).toBe(1);
|
||||
expect(grandChildIsolateScope.$countWatchers()).toBe(1);
|
||||
|
||||
childIsolateScope.$destroy();
|
||||
expect($rootScope.$countWatchers()).toBe(1);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -294,6 +294,21 @@ describe('$route', function() {
|
||||
$rootScope.$digest();
|
||||
expect($route.current).toBeDefined();
|
||||
}));
|
||||
|
||||
it("should use route params inherited from prototype chain", function() {
|
||||
function BaseRoute() {}
|
||||
BaseRoute.prototype.templateUrl = 'foo.html';
|
||||
|
||||
module(function($routeProvider) {
|
||||
$routeProvider.when('/foo', new BaseRoute());
|
||||
});
|
||||
|
||||
inject(function($route, $location, $rootScope) {
|
||||
$location.path('/foo');
|
||||
$rootScope.$digest();
|
||||
expect($route.current.templateUrl).toBe('foo.html');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user