Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78ba429e6a | |||
| dbf8c3c745 | |||
| 3602c9785b | |||
| acaac21fd1 | |||
| c98ef94706 | |||
| b0972a2e75 | |||
| 2dbb6f9a54 | |||
| 785a5fd7c1 | |||
| a55c1e79cf | |||
| d070450cd2 | |||
| 09648e4888 | |||
| 2adbcf189b | |||
| 39c5ffb2a6 | |||
| 04a570d31c | |||
| 958d3d56b1 | |||
| 0e50810c53 | |||
| 21e48abbc1 | |||
| b6d5439343 | |||
| 93901bdde4 | |||
| d802ed1b36 | |||
| e8f4305e9d | |||
| b38a2287f2 | |||
| 1e7675ad4c | |||
| 280b5ce3c0 | |||
| fbc5cf514b | |||
| f01087f802 | |||
| 4ac6424e87 | |||
| d3c486dd6d | |||
| 2d0f6ccba8 | |||
| 9a81b8668a | |||
| 9481d69d1c | |||
| 7615723547 | |||
| 338f949259 | |||
| d0192b31a3 | |||
| 6127528b50 | |||
| 0410572322 | |||
| fd2371cfc2 | |||
| 267fcc999c | |||
| 84187b6d94 | |||
| 5d6482bb3b | |||
| 023765c593 | |||
| 4a401bbcf3 | |||
| 7401c70718 | |||
| bb36bc7edf | |||
| bf1972dc1e | |||
| 689dfb1679 | |||
| 1169b54456 | |||
| 81b81856ee | |||
| fd4b99936e | |||
| 09271a8ab9 | |||
| 5a8d9acacb | |||
| 04d5a5072f | |||
| 55c30e1be6 | |||
| 97fc84c151 | |||
| 4ee0687f3f | |||
| ddff347b91 | |||
| 05ef1bd853 | |||
| d0f8bd30a6 | |||
| 1a8d3c8b3a | |||
| 753687e5c2 | |||
| 1a15c01b64 | |||
| 7f33e1ca89 | |||
| 28d00945ba | |||
| 6f40c88f47 | |||
| 68dd621082 | |||
| 3abfb4ef51 | |||
| 1014e52349 | |||
| cda061f723 | |||
| 1497c6c1fb | |||
| e41e445b51 | |||
| 7ab73190b7 | |||
| 450b3a5460 |
+2
-2
@@ -13,15 +13,15 @@ env:
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./lib/browser-stack/start-tunnel.sh
|
||||
- ./lib/sauce/sauce_connect_setup.sh
|
||||
- npm install -g grunt-cli
|
||||
- grunt bower
|
||||
- grunt bower
|
||||
- grunt package-without-bower
|
||||
- grunt ci-checks
|
||||
- ./lib/sauce/sauce_connect_block.sh
|
||||
|
||||
script:
|
||||
- grunt ci-checks
|
||||
- ./travis_build.sh
|
||||
|
||||
after_script:
|
||||
|
||||
@@ -1,3 +1,60 @@
|
||||
<a name="1.2.4"></a>
|
||||
# 1.2.4 wormhole-blaster (2013-12-06)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- ensure animations work with directives that share a transclusion
|
||||
([958d3d56](https://github.com/angular/angular.js/commit/958d3d56b1899a2cfc7b18c0292e5a1d8c64d0a5),
|
||||
[#4716](https://github.com/angular/angular.js/issues/4716), [#4871](https://github.com/angular/angular.js/issues/4871), [#5021](https://github.com/angular/angular.js/issues/5021), [#5278](https://github.com/angular/angular.js/issues/5278))
|
||||
- ensure ms durations are properly rounded
|
||||
([93901bdd](https://github.com/angular/angular.js/commit/93901bdde4bb9f0ba114ebb33b8885808e1823e1),
|
||||
[#5113](https://github.com/angular/angular.js/issues/5113), [#5162](https://github.com/angular/angular.js/issues/5162))
|
||||
- **$compile:**
|
||||
- update cloned elements if the template arrives after the cloning
|
||||
([b0972a2e](https://github.com/angular/angular.js/commit/b0972a2e75909e41dbac6e4413ada7df2d51df3a))
|
||||
- ensure the isolated local watch `lastValue` is always in sync
|
||||
([2d0f6ccb](https://github.com/angular/angular.js/commit/2d0f6ccba896fe34141d6d4f59eef6fba580c5c2),
|
||||
[#5182](https://github.com/angular/angular.js/issues/5182))
|
||||
- **$rootScope:**
|
||||
- ensure that when the $destroy event is broadcast on $rootScope that it does something
|
||||
([d802ed1b](https://github.com/angular/angular.js/commit/d802ed1b3680cfc1751777fac465b92ee29944dc),
|
||||
[#5169](https://github.com/angular/angular.js/issues/5169))
|
||||
- ensure the phase is cleared within a digest if an exception is raised by a watcher
|
||||
([d3c486dd](https://github.com/angular/angular.js/commit/d3c486dd6dfa8d5dca32a3e28aa685fb7260c878))
|
||||
- **$sanitize:** don't rely on YARR regex engine executing immediately in order to prevent object mutation
|
||||
([81b81856](https://github.com/angular/angular.js/commit/81b81856ee43d2876927c4e1f774affa87e99707),
|
||||
[#5193](https://github.com/angular/angular.js/issues/5193), [#5192](https://github.com/angular/angular.js/issues/5192))
|
||||
- **closure:** closure compiler shouldn't rename .defaults.transformRequest
|
||||
([f01087f8](https://github.com/angular/angular.js/commit/f01087f802839637843115cbcf99702e09d866f6))
|
||||
- **input:** ensure ngModelWatch() triggers second digest pass when appropriate
|
||||
([b6d54393](https://github.com/angular/angular.js/commit/b6d5439343b9801f7f2a009d0de09cba9aa21a1d),
|
||||
[#5258](https://github.com/angular/angular.js/issues/5258), [#5282](https://github.com/angular/angular.js/issues/5282))
|
||||
- **isElement:** return boolean value rather than `truthy` value.
|
||||
([2dbb6f9a](https://github.com/angular/angular.js/commit/2dbb6f9a54eb5ff5847eed11c85ac4cf119eb41c),
|
||||
[#4519](https://github.com/angular/angular.js/issues/4519), [#4534](https://github.com/angular/angular.js/issues/4534))
|
||||
- **jqLite:** ignore incompatible nodes on find()
|
||||
([1169b544](https://github.com/angular/angular.js/commit/1169b5445691e1495354d235a3badf05240e3904),
|
||||
[#4120](https://github.com/angular/angular.js/issues/4120))
|
||||
- **ngInit:** evaluate ngInit before ngInclude
|
||||
([0e50810c](https://github.com/angular/angular.js/commit/0e50810c53428f4c1f5bfdba9599df54cb7a6c6e),
|
||||
[#5167](https://github.com/angular/angular.js/issues/5167), [#5208](https://github.com/angular/angular.js/issues/5208))
|
||||
- **ngSanitize:** prefer textContent to innerText to avoid layout trashing
|
||||
([bf1972dc](https://github.com/angular/angular.js/commit/bf1972dc1e8ffbeaddfa53df1d49bc5a2177f09c))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$parse:** micro-optimization for ensureSafeObject function
|
||||
([689dfb16](https://github.com/angular/angular.js/commit/689dfb167924a61aef444ce7587fb987d8080990),
|
||||
[#5246](https://github.com/angular/angular.js/issues/5246))
|
||||
- **Scope:** short-circuit after dirty-checking last dirty watcher
|
||||
([d070450c](https://github.com/angular/angular.js/commit/d070450cd2b3b3a3aa34b69d3fa1f4cc3be025dd),
|
||||
[#5272](https://github.com/angular/angular.js/issues/5272), [#5287](https://github.com/angular/angular.js/issues/5287))
|
||||
|
||||
|
||||
|
||||
<a name="1.2.3"></a>
|
||||
# 1.2.3 unicorn-zapper (2013-11-27)
|
||||
|
||||
|
||||
@@ -29,6 +29,19 @@ duplication of work, and help you to craft the change so that it is successfully
|
||||
project.
|
||||
* **Small Changes** can be crafted and submitted to [GitHub Repository][github] as a Pull Request.
|
||||
|
||||
|
||||
## Want a Doc Fix?
|
||||
If you want to help improve the docs, it's a good idea to let others know what you're working on to
|
||||
minimize duplication of effort. Before starting, check out the issue queue for [Milestone:Docs Only](https://github.com/angular/angular.js/issues?milestone=24&state=open).
|
||||
Comment on an issue to let others know what you're working on, or create a new issue if your work
|
||||
doesn't fit within the scope of any of the existing doc fix projects.
|
||||
|
||||
For large fixes, please build and test the documentation before submitting the PR to be sure you haven't
|
||||
accidentally introduced any layout or formatting issues.You should also make sure that your commit message
|
||||
is labeled "docs:" and follows the **Git Commit Guidelines** outlined below.
|
||||
|
||||
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly.
|
||||
|
||||
## Submission Guidelines
|
||||
|
||||
### Submitting an Issue
|
||||
|
||||
+3
-10
@@ -106,45 +106,38 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: true,
|
||||
},
|
||||
ng: {
|
||||
files: { src: files['angularSrc'] },
|
||||
options: { jshintrc: 'src/.jshintrc' }
|
||||
},
|
||||
ngAnimate: {
|
||||
files: { src: 'src/ngAnimate/**/*.js' },
|
||||
options: { jshintrc: 'src/ngAnimate/.jshintrc' }
|
||||
},
|
||||
ngCookies: {
|
||||
files: { src: 'src/ngCookies/**/*.js' },
|
||||
options: { jshintrc: 'src/ngCookies/.jshintrc' }
|
||||
},
|
||||
ngLocale: {
|
||||
files: { src: 'src/ngLocale/**/*.js' },
|
||||
options: { jshintrc: 'src/ngLocale/.jshintrc' }
|
||||
},
|
||||
ngMock: {
|
||||
files: { src: 'src/ngMock/**/*.js' },
|
||||
options: { jshintrc: 'src/ngMock/.jshintrc' }
|
||||
},
|
||||
ngResource: {
|
||||
files: { src: 'src/ngResource/**/*.js' },
|
||||
options: { jshintrc: 'src/ngResource/.jshintrc' }
|
||||
},
|
||||
ngRoute: {
|
||||
files: { src: 'src/ngRoute/**/*.js' },
|
||||
options: { jshintrc: 'src/ngRoute/.jshintrc' }
|
||||
},
|
||||
ngSanitize: {
|
||||
files: { src: 'src/ngSanitize/**/*.js' },
|
||||
options: { jshintrc: 'src/ngSanitize/.jshintrc' }
|
||||
},
|
||||
ngScenario: {
|
||||
files: { src: 'src/ngScenario/**/*.js' },
|
||||
options: { jshintrc: 'src/ngScenario/.jshintrc' }
|
||||
},
|
||||
ngTouch: {
|
||||
files: { src: 'src/ngTouch/**/*.js' },
|
||||
options: { jshintrc: 'src/ngTouch/.jshintrc' }
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Triage new issues/PRs on github
|
||||
|
||||
This document shows the steps the Angular team is using to triage issues.
|
||||
The labels are used later on for planning releases.
|
||||
|
||||
## Tips ##
|
||||
|
||||
* install [github pr helper extension](https://github.com/petebacondarwin/github-pr-helper) and become 356% more productive
|
||||
* Label "resolution:*"
|
||||
* these tags can be used for labeling a closed issue/PR with a reason why it was closed. (we can add reasons as we need them, right there are only a few rejection reasons. it doesn't make sense to label issues that were fixed or prs that were merged)
|
||||
|
||||
|
||||
## Process ##
|
||||
|
||||
1. Open list of [non triaged issues](https://github.com/angular/angular.js/issues?direction=desc&milestone=none&page=1&sort=created&state=open)
|
||||
1. Assign yourself: Pick an issue that is not assigned to anyone and assign it to you
|
||||
1. Assign milestone:
|
||||
* "Docs only" milestone - for documentation PR -> **Done**.
|
||||
* Current/next milestone - regressions
|
||||
* 1.2.x - everything else
|
||||
1. Label "GH: *" (to be automated via Mary Poppins)
|
||||
* PR - issue is a PR
|
||||
* issue - otherwise
|
||||
1. Bugs:
|
||||
* Label "Type: Bug"
|
||||
* Label "Type: Regression" - if the bug is a regression
|
||||
* Duplicate? - Check if there are comments pointing out that this is a dupe, if they do exist verify that this is indeed a dupe and close it and go to the last step
|
||||
* Reproducible? - Steps to reproduce the bug are clear, if not ask for clarification (ideally plunker or fiddle)
|
||||
* Reproducible on master? - http://code.angularjs.org/snapshot/
|
||||
|
||||
1. Non bugs:
|
||||
* Label "Type: Feature" or "Type: Chore"
|
||||
* Label "needs: breaking change" - if needed
|
||||
* Understandable? - verify if the description of the request is clear. if not ask for clarification
|
||||
* Goals of angular core? - Often new features should be implemented as a third-party module rather than an addition to the core.
|
||||
|
||||
1. Label "component: *"
|
||||
* In rare cases, it's ok to have multiple components.
|
||||
1. Label "impact: *"
|
||||
* small - obscure issue affecting one or handful of developers
|
||||
* medium - impacts some usage patterns
|
||||
* large - impacts most or all of angular apps
|
||||
1. Label "complexity: *"
|
||||
* small - trivial change
|
||||
* medium - non-trivial but straightforward change
|
||||
* large - changes to many components in angular or any changes to $compile, ngRepeat or other "fun" components
|
||||
1. Label "PRs welcome" for "GH: issue"
|
||||
* if complexity is small or medium and the problem as well as solution are well captured in the issue
|
||||
1. Label "cla: yes" for "GH: PR":
|
||||
* otherwise prompt the contributor to sign the CLA
|
||||
1. Label "origin: google" for issues from Google
|
||||
1. Label "high priority" for security issues, major performance regressions or memory leaks
|
||||
|
||||
1. Unassign yourself from the issue
|
||||
|
||||
Vendored
+14
-2
@@ -1053,6 +1053,10 @@ angular.$http;
|
||||
*/
|
||||
angular.$http.Config;
|
||||
|
||||
angular.$http.Config.transformRequest;
|
||||
|
||||
angular.$http.Config.transformResponse;
|
||||
|
||||
// /**
|
||||
// * This extern is currently incomplete as delete is a reserved word.
|
||||
// * To use delete, index $http.
|
||||
@@ -1159,6 +1163,13 @@ angular.$http.HttpPromise.error = function(callback) {};
|
||||
*/
|
||||
angular.$http.Response;
|
||||
|
||||
angular.$HttpProvider;
|
||||
|
||||
/**
|
||||
* @type {angular.$http.Config}
|
||||
*/
|
||||
angular.$HttpProvider.defaults;
|
||||
|
||||
/******************************************************************************
|
||||
* $injector Service
|
||||
*****************************************************************************/
|
||||
@@ -1698,7 +1709,8 @@ angular.$routeProvider.when = function(path, route) {};
|
||||
* resolve: (Object.<string, (
|
||||
* string|Function|Array.<string|Function>|angular.$q.Promise
|
||||
* )>|undefined),
|
||||
* redirectTo: (string|function()|undefined),
|
||||
* redirectTo: (
|
||||
* string|function(Object.<string>, string, Object): string|undefined),
|
||||
* reloadOnSearch: (boolean|undefined)
|
||||
* }}
|
||||
*/
|
||||
@@ -1721,7 +1733,7 @@ angular.$routeProvider.Params.templateUrl;
|
||||
*/
|
||||
angular.$routeProvider.Params.resolve;
|
||||
|
||||
/** @type {string|function()} */
|
||||
/** @type {string|function(Object.<string>, string, Object): string} */
|
||||
angular.$routeProvider.Params.redirectTo;
|
||||
|
||||
/** @type {boolean} */
|
||||
|
||||
@@ -190,6 +190,7 @@ This should help give you an idea of what Angular does internally.
|
||||
<pre>
|
||||
var $compile = ...; // injected into your code
|
||||
var scope = ...;
|
||||
var parent = ...; // DOM element where the compiled template can be appended
|
||||
|
||||
var html = '<div ng-bind="exp"></div>';
|
||||
|
||||
@@ -200,7 +201,10 @@ This should help give you an idea of what Angular does internally.
|
||||
var linkFn = $compile(template);
|
||||
|
||||
// Step 3: link the compiled template with the scope.
|
||||
linkFn(scope);
|
||||
var element = linkFn(scope);
|
||||
|
||||
// Step 4: Append to DOM (optional)
|
||||
parent.appendChild(element);
|
||||
</pre>
|
||||
|
||||
### The difference between Compile and Link
|
||||
|
||||
@@ -270,7 +270,7 @@ When Angular starts, it will use the configuration of the module with the name d
|
||||
including the configuration of all modules that this module depends on.
|
||||
|
||||
In the example above:
|
||||
The template contains the directive `ng-app="invoice"`. This tells Angular
|
||||
The template contains the directive `ng-app="invoice2"`. This tells Angular
|
||||
to use the `invoice` module as the main module for the application.
|
||||
The code snippet `angular.module('invoice', ['finance'])` specifies that the `invoice` module depends on the
|
||||
`finance` module. By this, Angular uses the `InvoiceController` as well as the `currencyConverter` service.
|
||||
|
||||
@@ -146,7 +146,7 @@ of service names to inject.
|
||||
var MyController = function(renamed$scope, renamedGreeter) {
|
||||
...
|
||||
}
|
||||
MyController.$inject = ['$scope', 'greeter'];
|
||||
MyController['$inject'] = ['$scope', 'greeter'];
|
||||
</pre>
|
||||
|
||||
In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject.
|
||||
|
||||
@@ -590,6 +590,10 @@ See [79223eae](https://github.com/angular/angular.js/commit/79223eae502283889334
|
||||
|
||||
## Underscore-prefixed/suffixed properties are non-bindable
|
||||
|
||||
<div class="alert alert-info">
|
||||
<p>**Reverted**: This breaking change has been reverted in 1.2.1, and so can be ignored if you're using **version 1.2.1 or higher**</p>
|
||||
</div>
|
||||
|
||||
This change introduces the notion of "private" properties (properties
|
||||
whose names begin and/or end with an underscore) on the scope chain.
|
||||
These properties will not be available to Angular expressions (i.e. {{
|
||||
|
||||
@@ -110,7 +110,7 @@ myApp.factory('clientId', function clientIdFactory() {
|
||||
But given that the token is just a string literal, sticking with the Value recipe is still more
|
||||
appropriate as it makes the code easier to follow.
|
||||
|
||||
Let's say, however, that we would also like create a service that computes a token used for
|
||||
Let's say, however, that we would also like to create a service that computes a token used for
|
||||
authentication against a remote API. This token will be called 'apiToken' and will be computed
|
||||
based on the `clientId` value and a secret stored in browser's local storage:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ angular-seed, and run the application in the browser.
|
||||
<ol>
|
||||
<li>In a <i>separate</i> terminal tab or window, run <code>node ./scripts/web-server.js</code> to start the web server.</li>
|
||||
<li>Open a browser window for the app and navigate to <a
|
||||
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
|
||||
href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>For other http servers:</b>
|
||||
@@ -54,7 +54,7 @@ angular-seed, and run the application in the browser.
|
||||
<li><b>For node.js users:</b>
|
||||
<ol>
|
||||
<li>In a <i>separate</i> terminal tab or window, run <code>node scripts\web-server.js</code> to start the web server.</li>
|
||||
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
|
||||
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>For other http servers:</b>
|
||||
|
||||
@@ -184,7 +184,11 @@ http://pivotal.github.com/jasmine/ Jasmine home page} and at the {@link
|
||||
http://pivotal.github.io/jasmine/ Jasmine docs}.
|
||||
|
||||
The angular-seed project is pre-configured to run all unit tests using {@link
|
||||
http://karma-runner.github.io/ Karma}. To run the test, do the following:
|
||||
http://karma-runner.github.io/ Karma}. Ensure that the necessary karma plugins are installed.
|
||||
You can do this by issuing `npm install` into your terminal.
|
||||
|
||||
|
||||
To run the test, do the following:
|
||||
|
||||
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
|
||||
`./scripts/test.sh` to start the Karma server (the config file necessary to start the server
|
||||
|
||||
@@ -127,8 +127,8 @@ end-to-end tests! Use `./scripts/e2e-test.sh` script for that. End-to-end tests
|
||||
with unit tests, Karma will exit after the test run and will not automatically rerun the test
|
||||
suite on every file change. To rerun the test suite, execute the `e2e-test.sh` script again.
|
||||
|
||||
Note: You must ensure you've installed karma-ng-scenario prior to running the `e2e-test.sh` script.
|
||||
You can do this by issuing `npm install karma-ng-scenario` into your terminal.
|
||||
Note: You must ensure you've installed the karma-ng-scenario framework plugin prior to running the
|
||||
`e2e-test.sh` script. You can do this by issuing `npm install` into your terminal.
|
||||
|
||||
This test verifies that the search box and the repeater are correctly wired together. Notice how
|
||||
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
|
||||
@@ -154,7 +154,7 @@ really is that easy to set up any functional, readable, end-to-end test.
|
||||
`ngController` declaration to the HTML element because it is the common parent of both the body
|
||||
and title elements:
|
||||
|
||||
<html ng-app ng-controller="PhoneListCtrl">
|
||||
<html ng-app="phonecatApp" ng-controller="PhoneListCtrl">
|
||||
|
||||
Be sure to __remove__ the `ng-controller` declaration from the body element.
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ We also added phone images next to each record using an image tag with the {@lin
|
||||
api/ng.directive:ngSrc ngSrc} directive. That directive prevents the
|
||||
browser from treating the angular `{{ expression }}` markup literally, and initiating a request to
|
||||
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
specified an attribute binding in a regular `src` attribute (`<img class="diagram" src="{{phone.imageUrl}}">`).
|
||||
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
|
||||
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ Angular's server}.
|
||||
|
||||
<button ng-click="hello('Elmo')">Hello</button>
|
||||
|
||||
to the `phone-details.html` template.
|
||||
to the `phone-detail.html` template.
|
||||
|
||||
<div style="display: none">
|
||||
TODO!
|
||||
|
||||
@@ -43,7 +43,7 @@ __`app/index.html`.__
|
||||
<pre>
|
||||
...
|
||||
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
|
||||
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
|
||||
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
|
||||
<!-- required module to enable animation support in AngularJS -->
|
||||
<script src="lib/angular/angular-animate.js"></script>
|
||||
@@ -56,6 +56,10 @@ __`app/index.html`.__
|
||||
...
|
||||
</pre>
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
|
||||
</div>
|
||||
|
||||
Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`).
|
||||
But before we start, let's create a new module which uses the ngAnimate module as a dependency just like we did before
|
||||
with `ngResource`.
|
||||
@@ -153,7 +157,7 @@ __`app/css/animations.css`__
|
||||
</pre>
|
||||
|
||||
As you can see our `phone-listing` CSS class is combined together with the animation hooks that occur when items are
|
||||
inserted info and removed from the list:
|
||||
inserted into and removed from the list:
|
||||
|
||||
* The `ng-enter` class is applied to the element when a new phone is added to the list and rendered on the page.
|
||||
* The `ng-move` class is applied when items are moved around in the list.
|
||||
@@ -383,10 +387,6 @@ isn't required to do JavaScript animations with AngularJS, but we're going to us
|
||||
your own JavaScript animation library is beyond the scope of this tutorial. For more on
|
||||
`jQuery.animate`, see the {@link http://api.jquery.com/animate/ jQuery documentation}.
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
|
||||
</div>
|
||||
|
||||
The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed
|
||||
on the element that contains the class we registered, which is in this case `.phone`. When the `.active`
|
||||
class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will
|
||||
|
||||
@@ -364,7 +364,7 @@
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p class="pull-right"><a href="#">Back to top</a></p>
|
||||
<p class="pull-right"><a back-to-top href="#">Back to top</a></p>
|
||||
|
||||
<p>
|
||||
Super-powered by Google ©2010-2012
|
||||
|
||||
@@ -372,6 +372,21 @@ docsApp.directive.errorDisplay = ['$location', 'errorLinkFilter', function ($loc
|
||||
}];
|
||||
|
||||
|
||||
/**
|
||||
* backToTop Directive
|
||||
* @param {Function} $anchorScroll
|
||||
*
|
||||
* @description Ensure that the browser scrolls when the anchor is clicked
|
||||
*/
|
||||
docsApp.directive.backToTop = ['$anchorScroll', function($anchorScroll) {
|
||||
return function link(scope, element) {
|
||||
element.on('click', function(event) {
|
||||
scope.$apply($anchorScroll);
|
||||
});
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
docsApp.serviceFactory.angularUrls = function($document) {
|
||||
var urls = {};
|
||||
|
||||
|
||||
+3
-1
@@ -20,6 +20,8 @@ module.exports = function(config) {
|
||||
junitReporter: {
|
||||
outputFile: 'test_out/e2e.xml',
|
||||
suite: 'E2E'
|
||||
}
|
||||
},
|
||||
|
||||
browserNoActivityTimeout: 90000
|
||||
});
|
||||
};
|
||||
|
||||
+70
-8
@@ -6,21 +6,22 @@ module.exports = function(config, specificOptions) {
|
||||
logColors: true,
|
||||
browsers: ['Chrome'],
|
||||
browserDisconnectTimeout: 10000,
|
||||
browserDisconnectTolerance: 2,
|
||||
browserNoActivityTimeout: 20000,
|
||||
|
||||
|
||||
// config for Travis CI
|
||||
// SauceLabs config for local development.
|
||||
sauceLabs: {
|
||||
testName: specificOptions.testName || 'AngularJS',
|
||||
startConnect: false,
|
||||
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
|
||||
startConnect: true
|
||||
},
|
||||
|
||||
// BrowserStack config for Travis CI
|
||||
// BrowserStack config for local development.
|
||||
browserStack: {
|
||||
startTunnel: false,
|
||||
project: 'AngularJS',
|
||||
name: specificOptions.testName,
|
||||
build: process.env.TRAVIS_BUILD_NUMBER
|
||||
startTunnel: true,
|
||||
timeout: 600 // 10min
|
||||
},
|
||||
|
||||
// For more browsers on Sauce Labs see:
|
||||
@@ -58,6 +59,12 @@ module.exports = function(config, specificOptions) {
|
||||
platform: 'Windows 2012',
|
||||
version: '10'
|
||||
},
|
||||
'SL_IE_11': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
platform: 'Windows 8.1',
|
||||
version: '11'
|
||||
},
|
||||
|
||||
'BS_Chrome': {
|
||||
base: 'BrowserStack',
|
||||
@@ -110,11 +117,66 @@ module.exports = function(config, specificOptions) {
|
||||
|
||||
|
||||
if (process.env.TRAVIS) {
|
||||
var buildLabel = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
|
||||
|
||||
config.logLevel = config.LOG_DEBUG;
|
||||
config.transports = ['websocket', 'xhr-polling'];
|
||||
|
||||
config.browserStack.build = buildLabel;
|
||||
config.browserStack.startTunnel = false;
|
||||
|
||||
config.sauceLabs.build = buildLabel;
|
||||
config.sauceLabs.startConnect = false;
|
||||
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
|
||||
// TODO(vojta): remove once SauceLabs supports websockets.
|
||||
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
|
||||
config.transports = ['xhr-polling'];
|
||||
|
||||
// Debug logging into a file, that we print out at the end of the build.
|
||||
config.loggers.push({
|
||||
type: 'file',
|
||||
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log'),
|
||||
level: config.LOG_DEBUG
|
||||
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log')
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Terrible hack to workaround inflexibility of log4js:
|
||||
// - ignore web-server's 404 warnings,
|
||||
// - ignore DEBUG logs (on Travis), we log them into a file instead.
|
||||
var IGNORED_404 = [
|
||||
'/favicon.ico',
|
||||
'/%7B%7BtestUrl%7D%7D',
|
||||
'/someSanitizedUrl',
|
||||
'/{{testUrl}}'
|
||||
];
|
||||
var log4js = require('./node_modules/karma/node_modules/log4js');
|
||||
var layouts = require('./node_modules/karma/node_modules/log4js/lib/layouts');
|
||||
var originalConfigure = log4js.configure;
|
||||
log4js.configure = function(log4jsConfig) {
|
||||
var consoleAppender = log4jsConfig.appenders.shift();
|
||||
var originalResult = originalConfigure.call(log4js, log4jsConfig);
|
||||
var layout = layouts.layout(consoleAppender.layout.type, consoleAppender.layout);
|
||||
|
||||
|
||||
|
||||
log4js.addAppender(function(log) {
|
||||
var msg = log.data[0];
|
||||
|
||||
// ignore web-server's 404s
|
||||
if (log.categoryName === 'web-server' && log.level.levelStr === config.LOG_WARN &&
|
||||
IGNORED_404.some(function(ignoredLog) {return msg.indexOf(ignoredLog) !== -1})) {
|
||||
return;
|
||||
}
|
||||
|
||||
// on Travis, ignore DEBUG statements
|
||||
if (process.env.TRAVIS && log.level.levelStr === config.LOG_DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(layout(log));
|
||||
});
|
||||
|
||||
return originalResult;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ var http = require('http');
|
||||
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
|
||||
|
||||
var HOSTNAME = 'localhost';
|
||||
var PORTS = [9090, 9876];
|
||||
var PORTS = require('../grunt/utils').availablePorts;
|
||||
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
|
||||
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
|
||||
|
||||
@@ -25,9 +25,8 @@ var tunnel = new BrowserStackTunnel({
|
||||
hosts: hosts
|
||||
});
|
||||
|
||||
|
||||
console.log('Starting tunnel on ports', PORTS.join(', '));
|
||||
tunnel.start(function(error) {
|
||||
console.log('** callback **')
|
||||
if (error) {
|
||||
console.error('Can not establish the tunnel', error);
|
||||
} else {
|
||||
|
||||
+19
-1
@@ -5,6 +5,22 @@ var spawn = require('child_process').spawn;
|
||||
var version;
|
||||
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
|
||||
|
||||
var PORT_MIN = 8000;
|
||||
var PORT_MAX = 9999;
|
||||
var TRAVIS_BUILD_NUMBER = parseInt(process.env.TRAVIS_BUILD_NUMBER, 10);
|
||||
var getRandomPorts = function() {
|
||||
if (!process.env.TRAVIS) {
|
||||
return [9876, 9877];
|
||||
}
|
||||
|
||||
// Generate two numbers between PORT_MIN and PORT_MAX, based on TRAVIS_BUILD_NUMBER.
|
||||
return [
|
||||
PORT_MIN + (TRAVIS_BUILD_NUMBER % (PORT_MAX - PORT_MIN)),
|
||||
PORT_MIN + ((TRAVIS_BUILD_NUMBER + 100) % (PORT_MAX - PORT_MIN))
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
init: function() {
|
||||
@@ -295,5 +311,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
// see http://saucelabs.com/docs/connect#localhost
|
||||
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876]
|
||||
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876],
|
||||
// pseudo-random port numbers for BrowserStack
|
||||
availablePorts: getRandomPorts()
|
||||
};
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"version": "1.2.3",
|
||||
"cdnVersion": "1.2.2",
|
||||
"codename": "unicorn-zapper",
|
||||
"version": "1.2.4",
|
||||
"cdnVersion": "1.2.3",
|
||||
"codename": "wormhole-baster",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -24,7 +24,7 @@
|
||||
"karma-chrome-launcher": "~0.1.0",
|
||||
"karma-firefox-launcher": "~0.1.0",
|
||||
"karma-ng-scenario": "~0.1.0",
|
||||
"karma-junit-reporter": "git://github.com/karma-runner/karma-junit-reporter#karma-0.11",
|
||||
"karma-junit-reporter": "~0.2.1",
|
||||
"karma-sauce-launcher": "~0.1.1",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"yaml-js": "~0.0.8",
|
||||
@@ -38,7 +38,7 @@
|
||||
"grunt-shell": "~0.4.0",
|
||||
"semver": "~2.1.0",
|
||||
"lodash": "~2.1.0",
|
||||
"karma-browserstack-launcher": "~0.0.4",
|
||||
"karma-browserstack-launcher": "git://github.com/karma-runner/karma-browserstack-launcher.git#master",
|
||||
"browserstacktunnel-wrapper": "~1.1.1"
|
||||
},
|
||||
"licenses": [
|
||||
@@ -48,6 +48,6 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"grunt-contrib-jshint": "~0.6.4"
|
||||
"grunt-contrib-jshint": "~0.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -565,9 +565,9 @@ var trim = (function() {
|
||||
* @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
|
||||
*/
|
||||
function isElement(node) {
|
||||
return node &&
|
||||
return !!(node &&
|
||||
(node.nodeName // we are a direct element
|
||||
|| (node.on && node.find)); // we have an on and find method part of jQuery API
|
||||
|| (node.on && node.find))); // we have an on and find method part of jQuery API
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -768,7 +768,7 @@ function shallowCopy(src, dst) {
|
||||
|
||||
for(var key in src) {
|
||||
// shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
|
||||
// so we don't need to worry hasOwnProperty here
|
||||
// so we don't need to worry about using our custom hasOwnProperty here
|
||||
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
|
||||
dst[key] = src[key];
|
||||
}
|
||||
@@ -1330,23 +1330,25 @@ function getter(obj, path, bindFnToScope) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the siblings between `startNode` and `endNode`, inclusive
|
||||
* @param {Object} object with `startNode` and `endNode` properties
|
||||
* Return the DOM siblings between the first and last node in the given array.
|
||||
* @param {Array} array like object
|
||||
* @returns jQlite object containing the elements
|
||||
*/
|
||||
function getBlockElements(block) {
|
||||
if (block.startNode === block.endNode) {
|
||||
return jqLite(block.startNode);
|
||||
function getBlockElements(nodes) {
|
||||
var startNode = nodes[0],
|
||||
endNode = nodes[nodes.length - 1];
|
||||
if (startNode === endNode) {
|
||||
return jqLite(startNode);
|
||||
}
|
||||
|
||||
var element = block.startNode;
|
||||
var element = startNode;
|
||||
var elements = [element];
|
||||
|
||||
do {
|
||||
element = element.nextSibling;
|
||||
if (!element) break;
|
||||
elements.push(element);
|
||||
} while (element !== block.endNode);
|
||||
} while (element !== endNode);
|
||||
|
||||
return jqLite(elements);
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ function annotate(fn) {
|
||||
* // ...
|
||||
* }
|
||||
* // Define function dependencies
|
||||
* MyController.$inject = ['$scope', '$route'];
|
||||
* MyController['$inject'] = ['$scope', '$route'];
|
||||
*
|
||||
* // Then
|
||||
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
|
||||
|
||||
+5
-1
@@ -822,7 +822,11 @@ forEach({
|
||||
},
|
||||
|
||||
find: function(element, selector) {
|
||||
return element.getElementsByTagName(selector);
|
||||
if (element.getElementsByTagName) {
|
||||
return element.getElementsByTagName(selector);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
clone: jqLiteClone,
|
||||
|
||||
+5
-5
@@ -192,7 +192,7 @@
|
||||
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
|
||||
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
|
||||
* * `^` - Locate the required controller by searching the element's parents. Throw an error if not found.
|
||||
* * `?^` - Attempt to locate the required controller by searching the element's parentsor pass `null` to the
|
||||
* * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the
|
||||
* `link` fn if not found.
|
||||
*
|
||||
*
|
||||
@@ -931,7 +931,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn)
|
||||
);
|
||||
} else {
|
||||
nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
|
||||
}
|
||||
} else if (childLinkFn) {
|
||||
childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
|
||||
@@ -1438,13 +1438,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// we are out of sync and need to copy
|
||||
if (parentValue !== lastValue) {
|
||||
// parent changed and it has precedence
|
||||
lastValue = isolateScope[scopeName] = parentValue;
|
||||
isolateScope[scopeName] = parentValue;
|
||||
} else {
|
||||
// if the parent can be assigned then do so
|
||||
parentSet(scope, parentValue = lastValue = isolateScope[scopeName]);
|
||||
parentSet(scope, parentValue = isolateScope[scopeName]);
|
||||
}
|
||||
}
|
||||
return parentValue;
|
||||
return lastValue = parentValue;
|
||||
});
|
||||
break;
|
||||
|
||||
|
||||
@@ -21,9 +21,22 @@ var nullFormCtrl = {
|
||||
* @property {Object} $error Is an object hash, containing references to all invalid controls or
|
||||
* forms, where:
|
||||
*
|
||||
* - keys are validation tokens (error names) — such as `required`, `url` or `email`,
|
||||
* - values are arrays of controls or forms that are invalid with given error.
|
||||
* - keys are validation tokens (error names),
|
||||
* - values are arrays of controls or forms that are invalid for given error name.
|
||||
*
|
||||
*
|
||||
* Built-in validation tokens:
|
||||
*
|
||||
* - `email`
|
||||
* - `max`
|
||||
* - `maxlength`
|
||||
* - `min`
|
||||
* - `minlength`
|
||||
* - `number`
|
||||
* - `pattern`
|
||||
* - `required`
|
||||
* - `url`
|
||||
*
|
||||
* @description
|
||||
* `FormController` keeps track of all its controls and nested forms as well as state of them,
|
||||
* such as being valid/invalid or dirty/pristine.
|
||||
|
||||
@@ -951,39 +951,6 @@ var VALID_CLASS = 'ng-valid',
|
||||
</file>
|
||||
* </example>
|
||||
*
|
||||
* ## Isolated Scope Pitfall
|
||||
*
|
||||
* Note that if you have a directive with an isolated scope, you cannot require `ngModel`
|
||||
* since the model value will be looked up on the isolated scope rather than the outer scope.
|
||||
* When the directive updates the model value, calling `ngModel.$setViewValue()` the property
|
||||
* on the outer scope will not be updated. However you can get around this by using $parent.
|
||||
*
|
||||
* Here is an example of this situation. You'll notice that the first div is not updating the input.
|
||||
* However the second div can update the input properly.
|
||||
*
|
||||
* <example module="badIsolatedDirective">
|
||||
<file name="script.js">
|
||||
angular.module('badIsolatedDirective', []).directive('isolate', function() {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
scope: { },
|
||||
template: '<input ng-model="innerModel">',
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
scope.$watch('innerModel', function(value) {
|
||||
console.log(value);
|
||||
ngModel.$setViewValue(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<input ng-model="someModel"/>
|
||||
<div isolate ng-model="someModel"></div>
|
||||
<div isolate ng-model="$parent.someModel"></div>
|
||||
</file>
|
||||
* </example>
|
||||
*
|
||||
*
|
||||
*/
|
||||
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
|
||||
@@ -1130,7 +1097,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* It will update the $viewValue, then pass this value through each of the functions in `$parsers`,
|
||||
* which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to
|
||||
* `$modelValue` and the **expression** specified in the `ng-model` attribute.
|
||||
*
|
||||
*
|
||||
* Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
|
||||
*
|
||||
* Note that calling this function does not trigger a `$digest`.
|
||||
@@ -1187,6 +1154,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
ctrl.$render();
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}];
|
||||
|
||||
|
||||
@@ -94,9 +94,12 @@ var ngIfDirective = ['$animate', function($animate) {
|
||||
if (!childScope) {
|
||||
childScope = $scope.$new();
|
||||
$transclude(childScope, function (clone) {
|
||||
clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
|
||||
// Note: We only need the first/last node of the cloned nodes.
|
||||
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
|
||||
// by a directive with templateUrl when it's template arrives.
|
||||
block = {
|
||||
startNode: clone[0],
|
||||
endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ')
|
||||
clone: clone
|
||||
};
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
@@ -109,7 +112,7 @@ var ngIfDirective = ['$animate', function($animate) {
|
||||
}
|
||||
|
||||
if (block) {
|
||||
$animate.leave(getBlockElements(block));
|
||||
$animate.leave(getBlockElements(block.clone));
|
||||
block = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
* to initialize values on a scope.
|
||||
* </div>
|
||||
*
|
||||
* @priority 450
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngInit {@link guide/expression Expression} to eval.
|
||||
*
|
||||
@@ -47,6 +49,7 @@
|
||||
</doc:example>
|
||||
*/
|
||||
var ngInitDirective = ngDirective({
|
||||
priority: 450,
|
||||
compile: function() {
|
||||
return {
|
||||
pre: function(scope, element, attrs) {
|
||||
|
||||
@@ -301,7 +301,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
} else if (nextBlockMap.hasOwnProperty(trackById)) {
|
||||
// restore lastBlockMap
|
||||
forEach(nextBlockOrder, function(block) {
|
||||
if (block && block.startNode) lastBlockMap[block.id] = block;
|
||||
if (block && block.scope) lastBlockMap[block.id] = block;
|
||||
});
|
||||
// This is a duplicate and we need to throw an error
|
||||
throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
|
||||
@@ -318,7 +318,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
// lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn
|
||||
if (lastBlockMap.hasOwnProperty(key)) {
|
||||
block = lastBlockMap[key];
|
||||
elementsToRemove = getBlockElements(block);
|
||||
elementsToRemove = getBlockElements(block.clone);
|
||||
$animate.leave(elementsToRemove);
|
||||
forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; });
|
||||
block.scope.$destroy();
|
||||
@@ -330,9 +330,9 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
key = (collection === collectionKeys) ? index : collectionKeys[index];
|
||||
value = collection[key];
|
||||
block = nextBlockOrder[index];
|
||||
if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
|
||||
if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]);
|
||||
|
||||
if (block.startNode) {
|
||||
if (block.scope) {
|
||||
// if we have already seen this object, then we need to reuse the
|
||||
// associated scope/element
|
||||
childScope = block.scope;
|
||||
@@ -342,11 +342,11 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
nextNode = nextNode.nextSibling;
|
||||
} while(nextNode && nextNode[NG_REMOVED]);
|
||||
|
||||
if (block.startNode != nextNode) {
|
||||
if (getBlockStart(block) != nextNode) {
|
||||
// existing item which got moved
|
||||
$animate.move(getBlockElements(block), null, jqLite(previousNode));
|
||||
$animate.move(getBlockElements(block.clone), null, jqLite(previousNode));
|
||||
}
|
||||
previousNode = block.endNode;
|
||||
previousNode = getBlockEnd(block);
|
||||
} else {
|
||||
// new item which we don't know about
|
||||
childScope = $scope.$new();
|
||||
@@ -362,14 +362,16 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
childScope.$odd = !(childScope.$even = (index&1) === 0);
|
||||
// jshint bitwise: true
|
||||
|
||||
if (!block.startNode) {
|
||||
if (!block.scope) {
|
||||
$transclude(childScope, function(clone) {
|
||||
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
|
||||
$animate.enter(clone, null, jqLite(previousNode));
|
||||
previousNode = clone;
|
||||
block.scope = childScope;
|
||||
block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0];
|
||||
block.endNode = clone[clone.length - 1];
|
||||
// Note: We only need the first/last node of the cloned nodes.
|
||||
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
|
||||
// by a directive with templateUrl when it's template arrives.
|
||||
block.clone = clone;
|
||||
nextBlockMap[block.id] = block;
|
||||
});
|
||||
}
|
||||
@@ -378,5 +380,13 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function getBlockStart(block) {
|
||||
return block.clone[0];
|
||||
}
|
||||
|
||||
function getBlockEnd(block) {
|
||||
return block.clone[block.clone.length - 1];
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
@@ -103,8 +103,8 @@ function $InterpolateProvider() {
|
||||
*
|
||||
<pre>
|
||||
var $interpolate = ...; // injected
|
||||
var exp = $interpolate('Hello {{name}}!');
|
||||
expect(exp({name:'Angular'}).toEqual('Hello Angular!');
|
||||
var exp = $interpolate('Hello {{name | uppercase}}!');
|
||||
expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
|
||||
</pre>
|
||||
*
|
||||
*
|
||||
|
||||
+17
-16
@@ -44,23 +44,24 @@ function ensureSafeMemberName(name, fullExpression) {
|
||||
|
||||
function ensureSafeObject(obj, fullExpression) {
|
||||
// nifty check if obj is Function that is fast and works across iframes and other contexts
|
||||
if (obj && obj.constructor === obj) {
|
||||
throw $parseMinErr('isecfn',
|
||||
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// isWindow(obj)
|
||||
obj && obj.document && obj.location && obj.alert && obj.setInterval) {
|
||||
throw $parseMinErr('isecwindow',
|
||||
'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// isElement(obj)
|
||||
obj && (obj.nodeName || (obj.on && obj.find))) {
|
||||
throw $parseMinErr('isecdom',
|
||||
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else {
|
||||
return obj;
|
||||
if (obj) {
|
||||
if (obj.constructor === obj) {
|
||||
throw $parseMinErr('isecfn',
|
||||
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// isWindow(obj)
|
||||
obj.document && obj.location && obj.alert && obj.setInterval) {
|
||||
throw $parseMinErr('isecwindow',
|
||||
'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// isElement(obj)
|
||||
obj.children && (obj.nodeName || (obj.on && obj.find))) {
|
||||
throw $parseMinErr('isecdom',
|
||||
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
var OPERATORS = {
|
||||
|
||||
+47
-25
@@ -71,6 +71,7 @@
|
||||
function $RootScopeProvider(){
|
||||
var TTL = 10;
|
||||
var $rootScopeMinErr = minErr('$rootScope');
|
||||
var lastDirtyWatch = null;
|
||||
|
||||
this.digestTtl = function(value) {
|
||||
if (arguments.length) {
|
||||
@@ -172,7 +173,7 @@ function $RootScopeProvider(){
|
||||
*
|
||||
*/
|
||||
$new: function(isolate) {
|
||||
var Child,
|
||||
var ChildScope,
|
||||
child;
|
||||
|
||||
if (isolate) {
|
||||
@@ -182,11 +183,11 @@ function $RootScopeProvider(){
|
||||
child.$$asyncQueue = this.$$asyncQueue;
|
||||
child.$$postDigestQueue = this.$$postDigestQueue;
|
||||
} else {
|
||||
Child = function() {}; // should be anonymous; This is so that when the minifier munges
|
||||
ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges
|
||||
// the name it does not become random set of chars. This will then show up as class
|
||||
// name in the debugger.
|
||||
Child.prototype = this;
|
||||
child = new Child();
|
||||
ChildScope.prototype = this;
|
||||
child = new ChildScope();
|
||||
child.$id = nextUid();
|
||||
}
|
||||
child['this'] = child;
|
||||
@@ -266,7 +267,7 @@ function $RootScopeProvider(){
|
||||
|
||||
|
||||
|
||||
// Using a listener function
|
||||
// Using a listener function
|
||||
var food;
|
||||
scope.foodCounter = 0;
|
||||
expect(scope.foodCounter).toEqual(0);
|
||||
@@ -291,7 +292,7 @@ function $RootScopeProvider(){
|
||||
// Update food and run digest. Now the counter will increment
|
||||
food = 'cheeseburger';
|
||||
scope.$digest();
|
||||
expect(scope.foodCounter).toEqual(1);
|
||||
expect(scope.foodCounter).toEqual(1);
|
||||
|
||||
* </pre>
|
||||
*
|
||||
@@ -325,6 +326,8 @@ function $RootScopeProvider(){
|
||||
eq: !!objectEquality
|
||||
};
|
||||
|
||||
lastDirtyWatch = null;
|
||||
|
||||
// in the case user pass string, we need to compile it, do we really need this ?
|
||||
if (!isFunction(listener)) {
|
||||
var listenFn = compileToFn(listener || noop, 'listener');
|
||||
@@ -553,6 +556,8 @@ function $RootScopeProvider(){
|
||||
|
||||
beginPhase('$digest');
|
||||
|
||||
lastDirtyWatch = null;
|
||||
|
||||
do { // "while dirty" loop
|
||||
dirty = false;
|
||||
current = target;
|
||||
@@ -562,10 +567,13 @@ function $RootScopeProvider(){
|
||||
asyncTask = asyncQueue.shift();
|
||||
asyncTask.scope.$eval(asyncTask.expression);
|
||||
} catch (e) {
|
||||
clearPhase();
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
lastDirtyWatch = null;
|
||||
}
|
||||
|
||||
traverseScopesLoop:
|
||||
do { // "traverse the scopes" loop
|
||||
if ((watchers = current.$$watchers)) {
|
||||
// process our watches
|
||||
@@ -575,25 +583,34 @@ function $RootScopeProvider(){
|
||||
watch = watchers[length];
|
||||
// Most common watches are on primitives, in which case we can short
|
||||
// circuit it with === operator, only when === fails do we use .equals
|
||||
if (watch && (value = watch.get(current)) !== (last = watch.last) &&
|
||||
!(watch.eq
|
||||
? equals(value, last)
|
||||
: (typeof value == 'number' && typeof last == 'number'
|
||||
&& isNaN(value) && isNaN(last)))) {
|
||||
dirty = true;
|
||||
watch.last = watch.eq ? copy(value) : value;
|
||||
watch.fn(value, ((last === initWatchVal) ? value : last), current);
|
||||
if (ttl < 5) {
|
||||
logIdx = 4 - ttl;
|
||||
if (!watchLog[logIdx]) watchLog[logIdx] = [];
|
||||
logMsg = (isFunction(watch.exp))
|
||||
? 'fn: ' + (watch.exp.name || watch.exp.toString())
|
||||
: watch.exp;
|
||||
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
|
||||
watchLog[logIdx].push(logMsg);
|
||||
if (watch) {
|
||||
if ((value = watch.get(current)) !== (last = watch.last) &&
|
||||
!(watch.eq
|
||||
? equals(value, last)
|
||||
: (typeof value == 'number' && typeof last == 'number'
|
||||
&& isNaN(value) && isNaN(last)))) {
|
||||
dirty = true;
|
||||
lastDirtyWatch = watch;
|
||||
watch.last = watch.eq ? copy(value) : value;
|
||||
watch.fn(value, ((last === initWatchVal) ? value : last), current);
|
||||
if (ttl < 5) {
|
||||
logIdx = 4 - ttl;
|
||||
if (!watchLog[logIdx]) watchLog[logIdx] = [];
|
||||
logMsg = (isFunction(watch.exp))
|
||||
? 'fn: ' + (watch.exp.name || watch.exp.toString())
|
||||
: watch.exp;
|
||||
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
|
||||
watchLog[logIdx].push(logMsg);
|
||||
}
|
||||
} else if (watch === lastDirtyWatch) {
|
||||
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
|
||||
// have already been tested.
|
||||
dirty = false;
|
||||
break traverseScopesLoop;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
clearPhase();
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
@@ -602,13 +619,16 @@ function $RootScopeProvider(){
|
||||
// Insanity Warning: scope depth-first traversal
|
||||
// yes, this code is a bit crazy, but it works and we have tests to prove it!
|
||||
// this piece should be kept in sync with the traversal in $broadcast
|
||||
if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
|
||||
if (!(next = (current.$$childHead ||
|
||||
(current !== target && current.$$nextSibling)))) {
|
||||
while(current !== target && !(next = current.$$nextSibling)) {
|
||||
current = current.$parent;
|
||||
}
|
||||
}
|
||||
} while ((current = next));
|
||||
|
||||
// `break traverseScopesLoop;` takes us to here
|
||||
|
||||
if(dirty && !(ttl--)) {
|
||||
clearPhase();
|
||||
throw $rootScopeMinErr('infdig',
|
||||
@@ -616,6 +636,7 @@ function $RootScopeProvider(){
|
||||
'Watchers fired in the last 5 iterations: {1}',
|
||||
TTL, toJson(watchLog));
|
||||
}
|
||||
|
||||
} while (dirty || asyncQueue.length);
|
||||
|
||||
clearPhase();
|
||||
@@ -668,11 +689,12 @@ function $RootScopeProvider(){
|
||||
*/
|
||||
$destroy: function() {
|
||||
// we can't destroy the root scope or a scope that has been already destroyed
|
||||
if ($rootScope == this || this.$$destroyed) return;
|
||||
if (this.$$destroyed) return;
|
||||
var parent = this.$parent;
|
||||
|
||||
this.$broadcast('$destroy');
|
||||
this.$$destroyed = true;
|
||||
if (this === $rootScope) return;
|
||||
|
||||
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
|
||||
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
|
||||
@@ -710,7 +732,7 @@ function $RootScopeProvider(){
|
||||
*
|
||||
* - `string`: execute using the rules as defined in {@link guide/expression expression}.
|
||||
* - `function(scope)`: execute the function with the current `scope` parameter.
|
||||
*
|
||||
*
|
||||
* @param {(object)=} locals Local variables object, useful for overriding values in scope.
|
||||
* @returns {*} The result of evaluating the expression.
|
||||
*/
|
||||
|
||||
+4
-2
@@ -20,13 +20,15 @@
|
||||
<doc:source>
|
||||
<script>
|
||||
function Ctrl($scope, $window) {
|
||||
$scope.$window = $window;
|
||||
$scope.greeting = 'Hello, World!';
|
||||
$scope.doGreeting = function(greeting) {
|
||||
$window.alert(greeting);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl">
|
||||
<input type="text" ng-model="greeting" />
|
||||
<button ng-click="$window.alert(greeting)">ALERT</button>
|
||||
<button ng-click="doGreeting(greeting)">ALERT</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
|
||||
+54
-25
@@ -190,7 +190,7 @@
|
||||
*
|
||||
* <pre>
|
||||
* //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
|
||||
* var ngModule = angular.module('YourApp', []);
|
||||
* var ngModule = angular.module('YourApp', ['ngAnimate']);
|
||||
* ngModule.animation('.my-crazy-animation', function() {
|
||||
* return {
|
||||
* enter: function(element, done) {
|
||||
@@ -199,8 +199,8 @@
|
||||
* //this (optional) function will be called when the animation
|
||||
* //completes or when the animation is cancelled (the cancelled
|
||||
* //flag will be set to true if cancelled).
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
* },
|
||||
* leave: function(element, done) { },
|
||||
* move: function(element, done) { },
|
||||
*
|
||||
@@ -215,7 +215,7 @@
|
||||
*
|
||||
* //animation that can be triggered after the class is removed
|
||||
* removeClass: function(element, className, done) { }
|
||||
* }
|
||||
* };
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
@@ -258,6 +258,19 @@ angular.module('ngAnimate', ['ng'])
|
||||
var NG_ANIMATE_CLASS_NAME = 'ng-animate';
|
||||
var rootAnimateState = {running: true};
|
||||
|
||||
function extractElementNode(element) {
|
||||
for(var i = 0; i < element.length; i++) {
|
||||
var elm = element[i];
|
||||
if(elm.nodeType == ELEMENT_NODE) {
|
||||
return elm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isMatchingElement(elm1, elm2) {
|
||||
return extractElementNode(elm1) == extractElementNode(elm2);
|
||||
}
|
||||
|
||||
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
|
||||
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
|
||||
|
||||
@@ -370,7 +383,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
* Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
|
||||
* the animation is started, the following CSS classes will be added for the duration of the animation:
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during enter animation:
|
||||
* Below is a breakdown of each step that occurs during leave animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------|---------------------------------------------|
|
||||
@@ -556,7 +569,16 @@ angular.module('ngAnimate', ['ng'])
|
||||
and the onComplete callback will be fired once the animation is fully complete.
|
||||
*/
|
||||
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
|
||||
var currentClassName = element.attr('class') || '';
|
||||
var node = extractElementNode(element);
|
||||
//transcluded directives may sometimes fire an animation using only comment nodes
|
||||
//best to catch this early on to prevent any animation operations from occurring
|
||||
if(!node) {
|
||||
fireDOMOperation();
|
||||
closeAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentClassName = node.className;
|
||||
var classes = currentClassName + ' ' + className;
|
||||
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
|
||||
if (!parentElement) {
|
||||
@@ -760,11 +782,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
function cancelChildAnimations(element) {
|
||||
var node = element[0];
|
||||
if(node.nodeType != ELEMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
var node = extractElementNode(element);
|
||||
forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
|
||||
element = angular.element(element);
|
||||
var data = element.data(NG_ANIMATE_STATE);
|
||||
@@ -788,7 +806,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
function cleanup(element) {
|
||||
if(element[0] == $rootElement[0]) {
|
||||
if(isMatchingElement(element, $rootElement)) {
|
||||
if(!rootAnimateState.disabled) {
|
||||
rootAnimateState.running = false;
|
||||
rootAnimateState.structural = false;
|
||||
@@ -802,7 +820,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
function animationsDisabled(element, parentElement) {
|
||||
if (rootAnimateState.disabled) return true;
|
||||
|
||||
if(element[0] == $rootElement[0]) {
|
||||
if(isMatchingElement(element, $rootElement)) {
|
||||
return rootAnimateState.disabled || rootAnimateState.running;
|
||||
}
|
||||
|
||||
@@ -812,7 +830,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
//any animations on it
|
||||
if(parentElement.length === 0) break;
|
||||
|
||||
var isRoot = parentElement[0] == $rootElement[0];
|
||||
var isRoot = isMatchingElement(parentElement, $rootElement);
|
||||
var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
|
||||
var result = state && (!!state.disabled || !!state.running);
|
||||
if(isRoot || result) {
|
||||
@@ -865,6 +883,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
|
||||
var NG_ANIMATE_FALLBACK_CLASS_NAME = 'ng-animate-start';
|
||||
var NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME = 'ng-animate-active';
|
||||
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
|
||||
|
||||
var lookupCache = {};
|
||||
var parentCounter = 0;
|
||||
@@ -959,7 +978,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
|
||||
parentID = parentCounter;
|
||||
}
|
||||
return parentID + '-' + element[0].className;
|
||||
return parentID + '-' + extractElementNode(element).className;
|
||||
}
|
||||
|
||||
function animateSetup(element, className) {
|
||||
@@ -994,7 +1013,6 @@ angular.module('ngAnimate', ['ng'])
|
||||
return false;
|
||||
}
|
||||
|
||||
var node = element[0];
|
||||
//temporarily disable the transition so that the enter styles
|
||||
//don't animate twice (this is here to avoid a bug in Chrome/FF).
|
||||
var activeClassName = '';
|
||||
@@ -1024,35 +1042,37 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
function blockTransitions(element) {
|
||||
element[0].style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
|
||||
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
|
||||
}
|
||||
|
||||
function blockKeyframeAnimations(element) {
|
||||
element[0].style[ANIMATION_PROP] = 'none 0s';
|
||||
extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
|
||||
}
|
||||
|
||||
function unblockTransitions(element) {
|
||||
var node = element[0], prop = TRANSITION_PROP + PROPERTY_KEY;
|
||||
var prop = TRANSITION_PROP + PROPERTY_KEY;
|
||||
var node = extractElementNode(element);
|
||||
if(node.style[prop] && node.style[prop].length > 0) {
|
||||
node.style[prop] = '';
|
||||
}
|
||||
}
|
||||
|
||||
function unblockKeyframeAnimations(element) {
|
||||
var node = element[0], prop = ANIMATION_PROP;
|
||||
var prop = ANIMATION_PROP;
|
||||
var node = extractElementNode(element);
|
||||
if(node.style[prop] && node.style[prop].length > 0) {
|
||||
element[0].style[prop] = '';
|
||||
node.style[prop] = '';
|
||||
}
|
||||
}
|
||||
|
||||
function animateRun(element, className, activeAnimationComplete) {
|
||||
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
|
||||
if(!element.hasClass(className) || !data) {
|
||||
var node = extractElementNode(element);
|
||||
if(node.className.indexOf(className) == -1 || !data) {
|
||||
activeAnimationComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
var node = element[0];
|
||||
var timings = data.timings;
|
||||
var stagger = data.stagger;
|
||||
var maxDuration = data.maxDuration;
|
||||
@@ -1095,6 +1115,9 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
if(appliedStyles.length > 0) {
|
||||
//the element being animated may sometimes contain comment nodes in
|
||||
//the jqLite object, so we're safe to use a single variable to house
|
||||
//the styles since there is always only one element being animated
|
||||
var oldStyle = node.getAttribute('style') || '';
|
||||
node.setAttribute('style', oldStyle + ' ' + style);
|
||||
}
|
||||
@@ -1109,6 +1132,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
element.off(css3AnimationEvents, onAnimationProgress);
|
||||
element.removeClass(activeClassName);
|
||||
animateClose(element, className);
|
||||
var node = extractElementNode(element);
|
||||
for (var i in appliedStyles) {
|
||||
node.style.removeProperty(appliedStyles[i]);
|
||||
}
|
||||
@@ -1118,6 +1142,11 @@ angular.module('ngAnimate', ['ng'])
|
||||
event.stopPropagation();
|
||||
var ev = event.originalEvent || event;
|
||||
var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
|
||||
|
||||
/* Firefox (or possibly just Gecko) likes to not round values up
|
||||
* when a ms measurement is used for the animation */
|
||||
var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
|
||||
|
||||
/* $manualTimeStamp is a mocked timeStamp value which is set
|
||||
* within browserTrigger(). This is only here so that tests can
|
||||
* mock animations properly. Real events fallback to event.timeStamp,
|
||||
@@ -1125,7 +1154,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
* We're checking to see if the timeStamp surpasses the expected delay,
|
||||
* but we're using elapsedTime instead of the timeStamp on the 2nd
|
||||
* pre-condition since animations sometimes close off early */
|
||||
if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && ev.elapsedTime >= maxDuration) {
|
||||
if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
|
||||
activeAnimationComplete();
|
||||
}
|
||||
}
|
||||
@@ -1203,7 +1232,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
var parentElement = element.parent();
|
||||
var clone = angular.element(element[0].cloneNode());
|
||||
var clone = angular.element(extractElementNode(element).cloneNode());
|
||||
|
||||
//make the element super hidden and override any CSS style values
|
||||
clone.attr('style','position:absolute; top:-9999px; left:-9999px');
|
||||
|
||||
+17
-10
@@ -360,25 +360,32 @@ function htmlParser( html, handler ) {
|
||||
}
|
||||
}
|
||||
|
||||
var hiddenPre=document.createElement("pre");
|
||||
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
|
||||
/**
|
||||
* decodes all entities into regular string
|
||||
* @param value
|
||||
* @returns {string} A string with decoded entities.
|
||||
*/
|
||||
var hiddenPre=document.createElement("pre");
|
||||
function decodeEntities(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
if (!value) { return ''; }
|
||||
|
||||
// Note: IE8 does not preserve spaces at the start/end of innerHTML
|
||||
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
|
||||
// so we must capture them and reattach them afterward
|
||||
var parts = spaceRe.exec(value);
|
||||
parts[0] = '';
|
||||
if (parts[2]) {
|
||||
hiddenPre.innerHTML=parts[2].replace(/</g,"<");
|
||||
parts[2] = hiddenPre.innerText || hiddenPre.textContent;
|
||||
var spaceBefore = parts[1];
|
||||
var spaceAfter = parts[3];
|
||||
var content = parts[2];
|
||||
if (content) {
|
||||
hiddenPre.innerHTML=content.replace(/</g,"<");
|
||||
// innerText depends on styling as it doesn't display hidden elements.
|
||||
// Therefore, it's better to use textContent not to cause unnecessary
|
||||
// reflows. However, IE<9 don't support textContent so the innerText
|
||||
// fallback is necessary.
|
||||
content = 'textContent' in hiddenPre ?
|
||||
hiddenPre.textContent : hiddenPre.innerText;
|
||||
}
|
||||
return parts.join('');
|
||||
return spaceBefore + content + spaceAfter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1079,4 +1079,17 @@ describe('angular', function() {
|
||||
}
|
||||
});
|
||||
|
||||
describe('isElement', function() {
|
||||
it('should return a boolean value', inject(function($compile, $document, $rootScope) {
|
||||
var element = $compile('<p>Hello, world!</p>')($rootScope),
|
||||
body = $document.find('body')[0],
|
||||
expected = [false, false, false, false, false, false, false, true, true],
|
||||
tests = [null, undefined, "string", 1001, {}, 0, false, body, element];
|
||||
angular.forEach(tests, function(value, idx) {
|
||||
var result = angular.isElement(value);
|
||||
expect(typeof result).toEqual('boolean');
|
||||
expect(result).toEqual(expected[idx]);
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1335,6 +1335,12 @@ describe('jqLite', function() {
|
||||
expect(innerDiv.length).toEqual(1);
|
||||
expect(innerDiv.html()).toEqual('text');
|
||||
});
|
||||
|
||||
it('should find child by name and not care about text nodes', function() {
|
||||
var divs = jqLite('<div><span>aa</span></div>text<div><span>bb</span></div>');
|
||||
var innerSpan = divs.find('span');
|
||||
expect(innerSpan.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -969,6 +969,32 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve widgets after cloning in append mode without $templateCache', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
$exceptionHandlerProvider.mode('log');
|
||||
});
|
||||
inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser,
|
||||
$exceptionHandler) {
|
||||
$httpBackend.expect('GET', 'cau.html').respond('<span>{{name}}</span>');
|
||||
$rootScope.name = 'Elvis';
|
||||
var template = $compile('<div class="cau"></div>');
|
||||
var e1;
|
||||
var e2;
|
||||
|
||||
e1 = template($rootScope.$new(), noop); // clone
|
||||
expect(e1.text()).toEqual('');
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
e2 = template($rootScope.$new(), noop); // clone
|
||||
$rootScope.$digest();
|
||||
expect(e1.text()).toEqual('Elvis');
|
||||
expect(e2.text()).toEqual('Elvis');
|
||||
|
||||
dealoc(e1);
|
||||
dealoc(e2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve widgets after cloning in inline mode', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
@@ -1010,6 +1036,33 @@ describe('$compile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve widgets after cloning in inline mode without $templateCache', function() {
|
||||
module(function($exceptionHandlerProvider) {
|
||||
$exceptionHandlerProvider.mode('log');
|
||||
});
|
||||
inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser,
|
||||
$exceptionHandler) {
|
||||
$httpBackend.expect('GET', 'cau.html').respond('<span>{{name}}</span>');
|
||||
$rootScope.name = 'Elvis';
|
||||
var template = $compile('<div class="i-cau"></div>');
|
||||
var e1;
|
||||
var e2;
|
||||
|
||||
e1 = template($rootScope.$new(), noop); // clone
|
||||
expect(e1.text()).toEqual('');
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
e2 = template($rootScope.$new(), noop); // clone
|
||||
$rootScope.$digest();
|
||||
expect(e1.text()).toEqual('Elvis');
|
||||
expect(e2.text()).toEqual('Elvis');
|
||||
|
||||
dealoc(e1);
|
||||
dealoc(e2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be implicitly terminal and not compile placeholder content in append', inject(
|
||||
function($compile, $templateCache, $rootScope, log) {
|
||||
@@ -2386,6 +2439,24 @@ describe('$compile', function() {
|
||||
expect(componentScope.refAlias).toBe($rootScope.name);
|
||||
}));
|
||||
|
||||
it('should not break if local and origin both change to the same value', inject(function() {
|
||||
$rootScope.name = 'aaa';
|
||||
|
||||
compile('<div><span my-component ref="name">');
|
||||
|
||||
//change both sides to the same item withing the same digest cycle
|
||||
componentScope.ref = 'same';
|
||||
$rootScope.name = 'same';
|
||||
$rootScope.$apply();
|
||||
|
||||
//change origin back to it's previous value
|
||||
$rootScope.name = 'aaa';
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.name).toBe('aaa');
|
||||
expect(componentScope.ref).toBe('aaa');
|
||||
}));
|
||||
|
||||
it('should complain on non assignable changes', inject(function() {
|
||||
compile('<div><span my-component ref="\'hello \' + name">');
|
||||
$rootScope.name = 'world';
|
||||
|
||||
@@ -383,6 +383,29 @@ describe('ngModel', function() {
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep previously defined watches consistent when changes in validity are made',
|
||||
inject(function($compile, $rootScope) {
|
||||
|
||||
var isFormValid;
|
||||
$rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; });
|
||||
|
||||
var element = $compile('<form name="myForm">' +
|
||||
'<input name="myControl" ng-model="value" required >' +
|
||||
'</form>')($rootScope);
|
||||
|
||||
$rootScope.$apply();
|
||||
expect(isFormValid).toBe(false);
|
||||
expect($rootScope.myForm.$valid).toBe(false);
|
||||
|
||||
$rootScope.value='value';
|
||||
$rootScope.$apply();
|
||||
expect(isFormValid).toBe(true);
|
||||
expect($rootScope.myForm.$valid).toBe(true);
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
describe('ngIf', function () {
|
||||
var $scope, $compile, element;
|
||||
var $scope, $compile, element, $compileProvider;
|
||||
|
||||
beforeEach(module(function(_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
}));
|
||||
beforeEach(inject(function ($rootScope, _$compile_) {
|
||||
$scope = $rootScope.$new();
|
||||
$compile = _$compile_;
|
||||
@@ -146,6 +149,28 @@ describe('ngIf', function () {
|
||||
expect(element.children()[0].className).toContain('my-class');
|
||||
});
|
||||
|
||||
it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) {
|
||||
$compileProvider.directive('test', function() {
|
||||
return {
|
||||
templateUrl: 'test.html'
|
||||
};
|
||||
});
|
||||
$httpBackend.whenGET('test.html').respond('hello');
|
||||
element.append('<div ng-if="show" test></div>');
|
||||
$compile(element)($rootScope);
|
||||
$rootScope.show = true;
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$httpBackend.flush();
|
||||
expect(element.text()).toBe('hello');
|
||||
|
||||
$rootScope.show = false;
|
||||
$rootScope.$apply();
|
||||
// Note: there are still comments in element!
|
||||
expect(element.children().length).toBe(0);
|
||||
expect(element.text()).toBe('');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('ngIf and transcludes', function() {
|
||||
|
||||
@@ -13,4 +13,30 @@ describe('ngInit', function() {
|
||||
element = $compile('<div ng-init="a=123"></div>')($rootScope);
|
||||
expect($rootScope.a).toEqual(123);
|
||||
}));
|
||||
|
||||
|
||||
it("should be evaluated before ngInclude", inject(function($rootScope, $templateCache, $compile) {
|
||||
$templateCache.put('template1.tpl', '<span>1</span>');
|
||||
$templateCache.put('template2.tpl', '<span>2</span>');
|
||||
$rootScope.template = 'template1.tpl';
|
||||
element = $compile('<div><div ng-include="template" ' +
|
||||
'ng-init="template=\'template2.tpl\'"></div></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.template).toEqual('template2.tpl');
|
||||
expect(element.find('span').text()).toEqual('2');
|
||||
}));
|
||||
|
||||
|
||||
it("should be evaluated after ngController", function() {
|
||||
module(function($controllerProvider) {
|
||||
$controllerProvider.register('TestCtrl', function($scope) {});
|
||||
});
|
||||
inject(function($rootScope, $compile) {
|
||||
element = $compile('<div><div ng-controller="TestCtrl" ' +
|
||||
'ng-init="test=123"></div></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.test).toBeUndefined();
|
||||
expect(element.children('div').scope().test).toEqual(123);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -749,8 +749,30 @@ describe('ngRepeat', function() {
|
||||
expect(element.text()).toBe('123');
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) {
|
||||
$compileProvider.directive('test', function() {
|
||||
return {
|
||||
templateUrl: 'test.html'
|
||||
};
|
||||
});
|
||||
$httpBackend.whenGET('test.html').respond('hello');
|
||||
element = jqLite('<div><div ng-repeat="i in items" test></div></div>');
|
||||
$compile(element)($rootScope);
|
||||
$rootScope.items = [1];
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
$httpBackend.flush();
|
||||
expect(element.text()).toBe('hello');
|
||||
|
||||
$rootScope.items = [];
|
||||
$rootScope.$apply();
|
||||
// Note: there are still comments in element!
|
||||
expect(element.children().length).toBe(0);
|
||||
expect(element.text()).toBe('');
|
||||
}));
|
||||
});
|
||||
|
||||
it('should add separator comments after each item', inject(function ($compile, $rootScope) {
|
||||
var check = function () {
|
||||
|
||||
@@ -127,6 +127,15 @@ describe('Scope', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear phase if an exception interrupt $digest cycle', function() {
|
||||
inject(function($rootScope) {
|
||||
$rootScope.$watch('a', function() {throw new Error('abc');});
|
||||
$rootScope.a = 1;
|
||||
try { $rootScope.$digest(); } catch(e) { }
|
||||
expect($rootScope.$$phase).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should fire watches in order of addition', inject(function($rootScope) {
|
||||
// this is not an external guarantee, just our own sanity
|
||||
@@ -568,6 +577,82 @@ describe('Scope', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('optimizations', function() {
|
||||
|
||||
function setupWatches(scope, log) {
|
||||
scope.$watch(function() { log('w1'); return scope.w1; }, log.fn('w1action'));
|
||||
scope.$watch(function() { log('w2'); return scope.w2; }, log.fn('w2action'));
|
||||
scope.$watch(function() { log('w3'); return scope.w3; }, log.fn('w3action'));
|
||||
scope.$digest();
|
||||
log.reset();
|
||||
}
|
||||
|
||||
|
||||
it('should check watches only once during an empty digest', inject(function(log, $rootScope) {
|
||||
setupWatches($rootScope, log);
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual(['w1', 'w2', 'w3']);
|
||||
}));
|
||||
|
||||
|
||||
it('should quit digest early after we check the last watch that was previously dirty',
|
||||
inject(function(log, $rootScope) {
|
||||
setupWatches($rootScope, log);
|
||||
$rootScope.w1 = 'x';
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual(['w1', 'w1action', 'w2', 'w3', 'w1']);
|
||||
}));
|
||||
|
||||
|
||||
it('should not quit digest early if a new watch was added from an existing watch action',
|
||||
inject(function(log, $rootScope) {
|
||||
setupWatches($rootScope, log);
|
||||
$rootScope.$watch(log.fn('w4'), function() {
|
||||
log('w4action');
|
||||
$rootScope.$watch(log.fn('w5'), log.fn('w5action'));
|
||||
});
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action',
|
||||
'w1', 'w2', 'w3', 'w4', 'w5', 'w5action',
|
||||
'w1', 'w2', 'w3', 'w4', 'w5']);
|
||||
}));
|
||||
|
||||
|
||||
it('should not quit digest early if an evalAsync task was scheduled from a watch action',
|
||||
inject(function(log, $rootScope) {
|
||||
setupWatches($rootScope, log);
|
||||
$rootScope.$watch(log.fn('w4'), function() {
|
||||
log('w4action');
|
||||
$rootScope.$evalAsync(function() {
|
||||
log('evalAsync')
|
||||
});
|
||||
});
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action', 'evalAsync',
|
||||
'w1', 'w2', 'w3', 'w4']);
|
||||
}));
|
||||
|
||||
|
||||
it('should quit digest early but not too early when various watches fire', inject(function(log, $rootScope) {
|
||||
setupWatches($rootScope, log);
|
||||
$rootScope.$watch(function() { log('w4'); return $rootScope.w4; }, function(newVal) {
|
||||
log('w4action');
|
||||
$rootScope.w2 = newVal;
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
log.reset();
|
||||
|
||||
$rootScope.w1 = 'x';
|
||||
$rootScope.w4 = 'x';
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual(['w1', 'w1action', 'w2', 'w3', 'w4', 'w4action',
|
||||
'w1', 'w2', 'w2action', 'w3', 'w4',
|
||||
'w1', 'w2']);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -590,10 +675,14 @@ describe('Scope', function() {
|
||||
}));
|
||||
|
||||
|
||||
it('should ignore remove on root', inject(function($rootScope) {
|
||||
it('should broadcast $destroy on rootScope', inject(function($rootScope) {
|
||||
var spy = spyOn(angular, 'noop');
|
||||
$rootScope.$on('$destroy', angular.noop);
|
||||
$rootScope.$destroy();
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('123');
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect($rootScope.$$destroyed).toBe(true);
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -2852,5 +2852,79 @@ describe("ngAnimate", function() {
|
||||
$timeout.flush();
|
||||
});
|
||||
});
|
||||
|
||||
it('should round up long elapsedTime values to close off a CSS3 animation',
|
||||
inject(function($rootScope, $compile, $rootElement, $document, $animate, $sniffer, $timeout, $window) {
|
||||
if (!$sniffer.animations) return;
|
||||
|
||||
ss.addRule('.millisecond-transition.ng-leave', '-webkit-transition:510ms linear all;' +
|
||||
'transition:510ms linear all;');
|
||||
|
||||
var element = $compile('<div class="millisecond-transition"></div>')($rootScope);
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$animate.leave(element);
|
||||
$rootScope.$digest();
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 0.50999999991 });
|
||||
|
||||
expect($rootElement.children().length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should properly animate elements with compound directives', function() {
|
||||
var capturedAnimation;
|
||||
module(function($animateProvider) {
|
||||
$animateProvider.register('.special', function() {
|
||||
return {
|
||||
enter : function(element, done) {
|
||||
capturedAnimation = 'enter';
|
||||
done();
|
||||
},
|
||||
leave : function(element, done) {
|
||||
capturedAnimation = 'leave';
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
inject(function($rootScope, $compile, $rootElement, $document, $timeout, $templateCache, $sniffer) {
|
||||
if(!$sniffer.transitions) return;
|
||||
|
||||
$templateCache.put('item-template', 'item: #{{ item }} ');
|
||||
var element = $compile('<div>' +
|
||||
' <div ng-repeat="item in items"' +
|
||||
' ng-include="tpl"' +
|
||||
' class="special"></div>' +
|
||||
'</div>')($rootScope);
|
||||
|
||||
ss.addRule('.special', '-webkit-transition:1s linear all;' +
|
||||
'transition:1s linear all;');
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$rootScope.tpl = 'item-template';
|
||||
$rootScope.items = [1,2,3];
|
||||
$rootScope.$digest();
|
||||
$timeout.flush();
|
||||
|
||||
expect(capturedAnimation).toBe('enter');
|
||||
expect(element.text()).toContain('item: #1');
|
||||
|
||||
forEach(element.children(), function(kid) {
|
||||
browserTrigger(kid, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
|
||||
});
|
||||
$timeout.flush();
|
||||
|
||||
$rootScope.items = [];
|
||||
$rootScope.$digest();
|
||||
$timeout.flush();
|
||||
|
||||
expect(capturedAnimation).toBe('leave');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -411,3 +411,55 @@ describe('HTML', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('decodeEntities', function() {
|
||||
var handler, text,
|
||||
origHiddenPre = window.hiddenPre;
|
||||
|
||||
beforeEach(function() {
|
||||
text = '';
|
||||
handler = {
|
||||
start: function() {},
|
||||
chars: function(text_){
|
||||
text = text_;
|
||||
},
|
||||
end: function() {},
|
||||
comment: function() {}
|
||||
};
|
||||
module('ngSanitize');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
window.hiddenPre = origHiddenPre;
|
||||
});
|
||||
|
||||
it('should use innerText if textContent is not available (IE<9)', function() {
|
||||
window.hiddenPre = {
|
||||
innerText: 'INNER_TEXT'
|
||||
};
|
||||
inject(function($sanitize) {
|
||||
htmlParser('<tag>text</tag>', handler);
|
||||
expect(text).toEqual('INNER_TEXT');
|
||||
});
|
||||
});
|
||||
it('should use textContent if available', function() {
|
||||
window.hiddenPre = {
|
||||
textContent: 'TEXT_CONTENT',
|
||||
innerText: 'INNER_TEXT'
|
||||
};
|
||||
inject(function($sanitize) {
|
||||
htmlParser('<tag>text</tag>', handler);
|
||||
expect(text).toEqual('TEXT_CONTENT');
|
||||
});
|
||||
});
|
||||
it('should use textContent even if empty', function() {
|
||||
window.hiddenPre = {
|
||||
textContent: '',
|
||||
innerText: 'INNER_TEXT'
|
||||
};
|
||||
inject(function($sanitize) {
|
||||
htmlParser('<tag>text</tag>', handler);
|
||||
expect(text).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+2
-2
@@ -5,5 +5,5 @@ set -e
|
||||
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
|
||||
|
||||
grunt parallel:travis --reporters dots \
|
||||
--browsers BS_Chrome,BS_Safari,BS_Firefox,BS_IE_8,BS_IE_9,BS_IE_10,BS_IE_11 \
|
||||
--e2e-browsers BS_Chrome
|
||||
--browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10,SL_IE_11 \
|
||||
--e2e-browsers SL_Chrome
|
||||
|
||||
Reference in New Issue
Block a user