Compare commits

...

84 Commits

Author SHA1 Message Date
Georgios Kalpakas d7422da7d7 test($resource): fix broken test
(Introduced while "cleaning up" the tests for in edfb691.)
2017-02-05 17:58:25 +02:00
Georgios Kalpakas c7cbc978c6 refactor(*): remove ignored expensiveChecks argument passed to $parse()
This is a follow-up to #15094.

Closes #15680
2017-02-05 15:39:10 +02:00
Kyle Wuolle 27146e8a7f fix($resource): do not swallow errors in success callback
Previously, errors thrown inside the `success` callback would be swallowed by a
noop `catch()` handler. The `catch()` handler was added in order to avoid an
unnecessary "Possibly Unhandled Rejection" error, in case the user provided an
`error` callback (which would handle request errors).

The handler was added too "eagrly" and as a result would swallow errors thrown
in the `success` callback, despite the fact that those errors would _not_ be
handled by the `error` callback.

This commit fixes this, by adding the `catch()` handler "lazily", only when it
is certain that a rejection will be handled by the `error` callback.

Fixes #15624

Closes #15628
2017-02-04 21:01:22 +02:00
Kindy Lin 5e418b1145 fix($parse): make sure ES6 object computed properties are watched
Add the missing watches for ES6 object computed properties which were
implemented in #14407.

Closes #15678
2017-02-04 16:18:21 +02:00
Dimitris Vardoulakis f4bb973eb7 refactor(*): avoid non-existent property warnings from Closure Compiler
Closes #15672
2017-02-02 22:38:27 +02:00
Chris 848857aa5b docs(misc/started): update Twitter handle (@angularjs --> @angular)
Closes #15671
2017-02-02 19:30:48 +02:00
Jessica Soltero ee8a05d3f1 docs(guide/expression): typo in one-time-binding
Closes #15668
2017-02-02 12:45:58 +02:00
Michał Gołębiowski 275ebbf0ec refactor($injector): remove the Chrome stringification hack
The Chrome stringification hack added in afcedff34c
is no longer needed. I verified that both of the commented out tests pass
on Chrome 56.
2017-02-01 15:02:52 +00:00
Michał Gołębiowski 0f23df4c06 chore(anchorScroll): remove a Jasmine toHaveBeenCalled workaround
The Jasmine fix landed long time ago and we've updated Jasmine since that
happened.
2017-02-01 13:36:20 +00:00
Michał Gołębiowski 11f700f7bd docs($location): fix examples
The examples contained tests with assertions in form of regular equality
comparisons which would be noops and in case of an error nothing would get
reported. Also, one of the test mixed a HTML5 browser scenario with a non-HTML5
one.
2017-02-01 13:35:15 +00:00
Michał Gołębiowski 5785f2a991 docs($animation): fix weird spaces around colons 2017-02-01 13:29:28 +00:00
Peter Bacon Darwin 2546c29f81 feat(ngModel): add $overrideModelOptions support
This change allows developers to modify the model options for an `ngModel`
directive programmatically.

Closes #15415
2017-02-01 12:20:41 +00:00
Patrick McElhaney 19ea708c9d docs(guide/component): add replace option
Add `replace` to the table comparing components to directives options. The
`replace` option is deprecated, but it is still documented for directives, so
it is worth pointing it out as a difference between directives and components.

Closes #15658
2017-01-31 19:37:47 +02:00
Frederik Prijck 5cf05d67f2 chore(doc-gen): show arguments as a subsection of the usage section
Previously, on the docs of directives which include the `animation` section, `arguments` are shown as an `h3` element below the `animation` `h2` element, making it look like it's a subsection of `animations`.

This commit ensures that the àrgument` `h3`element is rendered correctly after the `usage` `h2` element.

Fixes #15645
Closes #15646
2017-01-30 19:26:27 +02:00
Georgios Kalpakas 0377c6f0e8 fix($compile): do not swallow thrown errors in test
In e13eeab, errors/rejections produced during fetching the template or compiling
an asynchronous directive, where overzealously silenced. This doesn't make any
difference in (most) production apps, where `$exceptionHandler` does not rethrow
the errors. In tests though (where `$exceptionHandler` rethrows by default), it
can unexpectedly "swallow" thrown errors.

This commit fixes it by removing the extraneous `.catch(noop)`, thus letting
errors thrown by `$exceptionHandler` to surface.

The changes in 'compileSpec.js' essentially revert the modifications that were
unnecessarily (and incorrectly) done in e13eeab (and also one incorrect
modification from [c22615c][1]).

[1]: https://github.com/angular/angular.js/commit/c22615cbfbaa7d1712e79b6bf2ace6eb41313bac#diff-348c2f3781ed66a24894c2046a52c628L2084

Fixes #15629

Closes #15631
2017-01-30 19:25:06 +02:00
Peter Bacon Darwin 9c13866824 chore(docs): don't use bower for docs dependencies 2017-01-30 14:10:25 +00:00
vteremasov 419a4813e3 fix($resource): correctly unescape /\. even if \. comes from a param value
Closes #15627
2017-01-27 18:20:11 +02:00
Martin Staffa 131af8272d fix(select): keep ngModel when selected option is recreated by ngRepeat
Fixes #15630 
Closes #15632
2017-01-27 16:14:18 +01:00
Martin Staffa c219a46f59 docs(ngDisabled): list some elements that natively support
Closes #15473
2017-01-27 16:14:06 +01:00
Jason Bedard 25f008f541 feat($parse): allow watching array/object of inputs to literal values
The inputs of array/object literals are now watched for changes.
If the an input changes then a new instance of the literal will be
provided when the parsed expression is executed.

Closes #15301
2017-01-27 10:06:31 +00:00
frederikprijck 4a593db79b fix($sniffer): allow history for NW.js apps
Previously `$sniffer` incorrectly detected NW.js apps as Chrome Packaged Apps,
disallowing them to use the history API.

This commit correctly detects NW.js apps and allows them to use the History API.

Fixes #15474

Closes #15633
2017-01-27 00:16:54 +02:00
Georgios Kalpakas ad4fef0431 refactor(*): replace HashMap with NgMap
For the time being, we will be using `NgMap`, which is an API-compatible
implementation of native `Map` (for the features required in Angular). This will
make it easy to switch to using the native implementations, once they become
more stable.

Note:
At the moment some native implementations are still buggy (often in subtle ways)
and can cause hard-to-debug failures.)

Closes #15483
2017-01-25 23:57:16 +02:00
Georgios Kalpakas 8a15fcc1f5 test(hashKey): add tests for hashKey() 2017-01-25 23:57:15 +02:00
Georgios Kalpakas f01212ab52 fix(ngAnimate): correctly animate transcluded clones with templateUrl
Previously, `$animate` would decide whether an animation should be cancelled
based on some assumption that didn't hold in specific cases (e.g. when animating
transcluded clones with `templateUrl` directives on them for the first time). As
a result, the entering elements would not be animated in such cases. This
affected commonly used, structural built-in directives (`ngIf`, `ngRepeat`,
`ngSwitch` etc).
This commit fixes it by avoiding invalid assumptions (i.e. by taking into
account the transformations that take place while compiling such elements).

Partly addresses #14074 and #14124.

Fixes #15510

Closes #15514
2017-01-24 23:56:04 +02:00
Georgios Kalpakas 28693a1a67 test(ngAnimate): make expectations more specific 2017-01-24 23:56:04 +02:00
Georgios Kalpakas 29fd499552 refactor(ngAnimate): simplify functions and remove redundant args/calls
Simplifies/Optimizes the following functions:

- `areAnimationsAllowed()`
- `cleanupEventListeners()`
- `closeChildAnimations()`
- `clearElementAnimationState()`
- `markElementAnimationState()`
- `findCallbacks()`

Although not its primary aim, this commit also offers a small performance boost
to animations (~5% as measured with the `animation-bp` benchmark).
2017-01-24 23:56:03 +02:00
Georgios Kalpakas 2f97d9d647 chore(benchmarks): add basic animation benchmark 2017-01-24 23:56:03 +02:00
Georgios Kalpakas 4146b38459 docs(*): document the breaking change in 7ceb5f6 2017-01-20 22:51:21 +02:00
frederikprijck 05aab660ce fix(ngValue): correctly update the value property when value is undefined
Previously, when the expression evaluated to `undefined` the `value` property
was not updated. This happened because jqLite/jQuery's `prop(_, undefined)` is
treated as a getter, thus not apdating the property.

This commit fixes it by setting the property to `null` instead.
(On IE9 we use `''` - see inline comments for more info.)

Fixes #15603

Closes #15605
2017-01-19 14:45:28 +02:00
Peter Bacon Darwin 5ecb64849e chore(package): relax yarn version constraint 2017-01-19 09:26:25 +00:00
Martin Brown 59dfe1b5a0 docs(guide/i18n): fix typos
Closes #15616
2017-01-17 18:18:48 +02:00
Georgios Kalpakas 50ebfb735c docs(changelog): update with changes for 1.5.11 2017-01-13 01:13:18 +02:00
Georgios Kalpakas 0bdbfe5069 style($compile): remove trailing whitespace 2017-01-12 23:12:44 +02:00
Grace Benz 3fc4d6028c docs($compile): add some detail about $onChanges
Closes #15604
2017-01-12 22:42:50 +02:00
Georgios Kalpakas 2eb12a052b test(e2e): make test less flakey-prone 2017-01-12 22:25:47 +02:00
Peter Bacon Darwin bd63b2235c fix(ngMockE2E): ensure that mocked $httpBackend uses correct $browser
The fix from #13124 enabled ngMock and ngMockE2E to work together but
did it in a way that meant that the "real" `$httpBackend` service that
was used in pass-through depended upon a different `$browser` service
to the rest of the app.

This broke Protractor since it watches the `$browser` for outstanding
requests and the pass through requests were being tracked by the wrong
`$browser` instance.

Closes #15593
2017-01-12 10:59:26 +00:00
Georgios Kalpakas f418ffd083 revert: fix($sce): consider document base URL in 'self' URL policy
This reverts commit cce98ff53a.
Reverting while investigating security implications of cce98ff without #15597
(which is possibly a breaking change, thus not suitable for this branch).
2017-01-12 11:24:10 +02:00
Georgios Kalpakas 6ab5f8ce4b chore(*): fix yarn.lock
(It turns out cherry-picking it from master wasn't a great idea :/)
2017-01-12 01:08:55 +02:00
Georgios Kalpakas becfeb5aa3 chore(*): update dgeni-packages (and other devDependencies)
`dgeni-packages` prior to version 0.16.3 specified `engine.yarn: '^0.17.9'`,
which was unnecessarily strict and would cause any task to fail if someone had a
yarn version >=0.18.0.
Other devDependencies were also updated (because why not).

Closes #15600
2017-01-12 01:02:08 +02:00
Sammy Jelin eb968c4a68 fix($route): make asynchronous tasks count as pending requests
Protractor users were having a problem where if they had asynchonous code in a
`route.resolve` or `route.resolveRedirectTo` variable, Protractor was not
waiting for that code to complete before continuing. See
https://github.com/angular/protractor/issues/789#issuecomment-190983200 for
details.

This commit fixes it by ensuring that `$browser#outstandingRequestCount` is
properly increased/decreased while `$route` (asynchronously) processes a route.

Also, enhanced `ngMock` to wait for pending requests, before calling callbacks
from `$browser.notifyWhenNoOutstandingRequests()`.

Related to angular/protractor#789.

Closes #14159
2017-01-12 00:15:20 +02:00
frederikprijck 7f2af3f923 fix($compile): allow the usage of "$" in isolate scope property alias
Previously, when using an alias for an isolate scope or `bindings` property
(e.g. `alias: '<attrName'` instead of `attrName: '<'`), a `$compile:iscp` error
was thrown if the attribute name contained a "$".
This commit removes the error by changing the regex to allow "$" characters in
the attribute name when using a property alias.

Fixes: #15586

Closes #15594
2017-01-11 11:46:31 +02:00
Alex Dobkin cce98ff53a fix($sce): consider document base URL in 'self' URL policy
Page authors can use the `<base>` tag in HTML to specify URL to use as a base
when resovling relative URLs. This can cause SCE to reject relative URLs on the
page, because they fail the same-origin test.

To improve compatibility with the `<base>` tag, this commit changes the logic
for matching URLs to the 'self' policy to allow URLs that match the protocol and
domain of the base URL in addition to URLs that match the loading origin.

**Security Note:**
If an attacker can inject a `<base>` tag into the page, they can circumvent SCE
protections. However, injecting a `<base>` tag typically requires the ability to
inject arbitrary HTML into the page, which is a more serious vulnerabilty than
bypassing SCE.

Fixes #15144

Closes #15145
2017-01-10 14:51:09 +02:00
Georgios Kalpakas b607618342 fix($location): correctly handle external URL change during $digest
Previously, when the URL was changed directly (e.g. via `location.href`) during
a `$digest` (e.g. via `scope.$evalAsync()` or `promise.then()`) the change was
not handled correctly, unless a `popstate` or `hashchange` event was fired
synchronously.

This was an issue when calling `history.pushState()/replaceState()` in all
browsers, since these methods do not emit any event. This was also an issue when
setting `location.href` in IE11, where (unlike other browsers) no `popstate`
event is fired at all for hash-only changes ([known bug][1]) and the
`hashchange` event is fired asynchronously (which is too late).

This commit fixes both usecases by:

1. Keeping track of `$location` setter methods being called and only processing
   a URL change if it originated from such a call. If there is a URL difference
   but no setter method has been called, this means that the browser URL/history
   has been updated directly and the change hasn't yet been propagated to
   `$location` (e.g. due to no event being fired synchronously or at all).
2. Checking for URL/state changes at the end of the `$digest`, in order to
   detect changes via `history` methods (that took place during the `$digest`).

[1]: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/3740423/

Fixes #11075
Fixes #12571
Fixes #15556

Closes #15561
2017-01-10 00:03:10 +02:00
Georgios Kalpakas fa50fbaf57 fix(*): detect external changes in history.state
Previously, `$browser.$$checkUrlChange()` (which was run before each `$digest`)
would only detect an external change (i.e. not via `$location`) to the browser
URL. External changes to `history.state` would not be detected and propagated to
`$location`.

This would not be a problem if changes were followed by a `popstate` or
`hashchange` event (which would call `cacheStateAndFireUrlChange()`). But since
`history.pushState()/replaceState()` do not fire any events, calling these
methods manually would result in `$location` getting out-of-sync with the actual
history state.

This was not detected in tests, because the mocked `window.history` would
incorrectly trigger `popstate` when calling `pushState()/replaceState()`, which
"covered" the bug.

This commit fixes it by always calling `cacheState()`, before looking for and
propagating a URL/state change.
2017-01-10 00:03:09 +02:00
David Jöch f135e2dc05 style($log): fix indentation
Closes #15579
2017-01-05 13:02:59 +02:00
Florian Berger 780351db5e docs(*): fix typos
Closes #15577
2017-01-05 11:10:52 +02:00
Nic Mitchell a50bb0bfec chore(*): update copyright year
Closes #15573
2017-01-04 12:21:08 +02:00
Georgios Kalpakas 4d86df6f48 docs(guide/$location): correctly format heading 2017-01-03 22:37:17 +02:00
Georgios Kalpakas bb464d16b4 fix(angularInit): allow auto-bootstraping from inline script
Some browsers (e.g. Safari 9.x, PhantomJS) do not set `link.origin/protocol`
correctly, when setting `link.href` to `null`, which prevented auto-bootstraping
Angular from scripts without a `src` attribute (i.e. inline scripts).
Inline scripts are on the same origin as the loading page, so auto-bootstraping
should be allowed.

Fixes #15567

Closes #15571
2017-01-03 19:23:43 +02:00
Soumya Ranjan Mohanty 85b2eb1472 docs(guide/services): fix syntax for Jasmine v2.x
Closes #15570
2017-01-03 16:14:32 +02:00
Georgios Kalpakas 7608f92c6a docs(ngShow/ngHide): improve docs and fix inconsistencies between ngShow/ngHide
Closes #15471
2017-01-03 12:55:38 +02:00
Deco c0bf8db63c docs(tutorial/step_04): fix typo
Closes #15562
2016-12-31 12:32:22 +02:00
Georgios Kalpakas c95a6737fb fix(input): fix step validation for input[type=number/range]
Previously, the validation would incorrectly fail in certain cases (e.g.
`step: 0.01`, `value: 1.16 or 20.1`), due to Floating Point Arithmetic
limitations. The previous fix for FPA limitations (081d06ff) tried to solve the
issue by converting the numbers to integers, before doing the actual
calculation, but it failed to account for cases where the conversion itself
returned non-integer values (again due to FPA limitations).
This commit fixes it by ensuring that the values used in the final calculation
are always integers.

Fixes #15504

Closes #15506
2016-12-29 10:18:32 +02:00
Georgios Kalpakas cd43d24402 docs(CHANGELOG.md): add missing commas 2016-12-29 10:09:04 +02:00
supasak 086c5d0354 fix($resource): delete $cancelRequest() in toJSON()
Closes #15244
2016-12-29 10:01:11 +02:00
Peter Bacon Darwin 2a2ac5f53a docs(CHANGELOG): add 1.6.1 release info 2016-12-23 10:38:58 +00:00
Peter Bacon Darwin 21deaf637a chore(deps): update changez-angular version 2016-12-23 10:38:48 +00:00
Naomi Black 72e15a3a83 docs(guide/forms): remove implicit bias from example
Closes #15543
2016-12-23 11:43:47 +02:00
Thomas Grainger 174cb4a8c8 fix($q): Add traceback to unhandled promise rejections
Fixes #14631
Closes #15527
2016-12-21 20:12:29 +01:00
sp00m 33f769b0a1 fix($$cookieReader): correctly handle forbidden access to document.cookie
In certain cases (e.g. on LG webOS using the `file:` protocol), access to
`document.cookie` may not be allowed and throw an error. This could break
`$http` which relies on `$$cookieReader()` for retrieving the XSRF token.
This commit fixes it by treating `document.cookie` as empty, when access to it
is fordibben.

Fixes  #15523

Closes #15532
2016-12-20 23:34:07 +02:00
Simon Legner c8abf20558 docs(CHANGELOG): fix typo
Closes #15529
2016-12-20 10:33:14 +02:00
Thomas Grainger 6dbb183e75 docs(guide/migration): improve grammar
Closes #15526
2016-12-20 01:17:19 +02:00
Georgios Kalpakas bc4844d3b2 fix(ngOptions): do not unset the selected property unless necessary
Previously, when updating the value of a `select[multiple]` element, all options
were first set to `selected = false` and then the selected ones were set to
`true`. By setting an already selected option to `selected = false` and then
`true` again - essentially unselecting and reselecting it - caused some browsers
(including Firefox, IE and under some circumstances Chrome) to unexpectedly
scroll to the last selected option.

This commit fixes it by ensuring that the `selected` property is only set if its
current value is different than the new one and even then it is set to its final
value at once (i.e. without first setting it to `false`), thus avoiding the
undesirable behavior.

Fixes #15477

Closes #15478
2016-12-19 22:52:18 +02:00
Peter Neave 708f8b47de docs(tutorial/step_13): add missing dependency phoneDetails module
Closes #15521
2016-12-19 21:09:21 +02:00
Georgios Kalpakas 5518126d42 docs(CHANGELOG.md): remove reverted bug fix
Related to 02f045b.
2016-12-16 11:16:08 +02:00
Peter Bacon Darwin 5fe73fdc3a docs(CHANGELOG): add note to 1.5.0-beta.1 2016-12-16 11:16:08 +02:00
Georgios Kalpakas 394c496bf2 docs(CHANGELOG.md): add changes for v1.5.10 2016-12-15 19:29:37 +02:00
Georgios Kalpakas 8e1aeba715 docs(CHANGELOG.md): add missing entries for v1.6.0-rc.0/v1.6.0 2016-12-15 19:29:06 +02:00
Georgios Kalpakas 0e6e7eb477 docs($q): document the default value for errorOnUnhandledRejections 2016-12-14 00:44:36 +02:00
Jannick Fahlbusch 183f636816 docs($interval): improve fn description
If no additional arguments are passed, the function is called with the current iteration count.

Closes #15503
2016-12-13 14:16:20 +02:00
Georgios Kalpakas 5f8ed63f2a fix(ngModelOptions): work correctly when on the template of replace directives
Previously, in order for `ngModel` and `ngModelOptions` to work correctly
together, the latter's pre-linking function should be run before the former's
pre-linking function. This was typically what happened, except when `ngModel`
was used on an element which also had a `replace` directive, whose template
included `ngModelOptions`. In that case, the order was reversed.

This commit fixes it by moving the initialization logic of `ngModelOptions` from
its pre-linking function to its controller's `$onInit()` lifecycle hook.

Fixes #15492

Closes #15493
2016-12-13 00:12:34 +02:00
Georgios Kalpakas e4f3c94e31 refactor(testabilityPatch): remove code duplication 2016-12-13 00:11:18 +02:00
Aaron Brewer 1e5cbcbd93 docs(ngMessageExp): improve description
Closes #15486
2016-12-11 21:01:57 +02:00
idhindsight dcf3ec160f docs(tutorial/step_09): fix typo (it's --> its)
Closes #15485
2016-12-10 22:27:59 +02:00
David Rodenas Pico a7beb5b6d3 chore(benchpress): add an ngClass benchmark
Closes #15243
2016-12-09 12:21:21 +02:00
Georgios Kalpakas 1d3b65adc2 perf(ngClass): avoid unnecessary .data() accesses, deep-watching and copies
Includes the following commits (see #15246 for details):

- **perf(ngClass): only access the element's `data` once**

- **refactor(ngClass): simplify conditions**

- **refactor(ngClass): move helper functions outside the closure**

- **refactor(ngClass): exit `arrayDifference()` early if an input is empty**

- **perf(ngClass): avoid deep-watching (if possible) and unnecessary copies**

  The cases that should benefit most are:

  1. When using large objects as values (e.g.: `{loaded: $ctrl.data}`).
  2. When using objects/arrays and there are frequent changes.
  3. When there are many `$index` changes (e.g. addition/deletion/reordering in large `ngRepeat`s).

  The differences in operations per digest include:

  1. `Regular expression (when not changed)`
     **Before:** `equals()`
     **After:**  `toClassString()`

  2. `Regular expression (when changed)`
     **Before:** `copy()` + 2 x `arrayClasses()` + `shallowCopy()`
     **After:**  2 x `split()`

  3. `One-time expression (when not changed)`
     **Before:** `equals()`
     **After:**  `toFlatValue()` + `equals()`*

  4. `One-time expression (when changed)`
     **Before:** `copy()` + 2 x `arrayClasses()` + `shallowCopy()`
     **After:**  `copy()`* + `toClassString()`* + 2 x `split()`

  5. `$index modulo changed`
     **Before:** `arrayClasses()`
     **After:**  -

  (*): on flatter structure

  In large based on #14404. Kudos to @drpicox for the initial idea and a big part
  of the implementation.

Closes #14404

Closes #15246
2016-12-09 12:04:47 +02:00
Georgios Kalpakas d528644fe3 fix(ngClassOdd/Even): add/remove the correct classes when expression/$index change simultaneously 2016-12-09 12:03:38 +02:00
David Rodenas Pico 6f1bcfc14e test(ngClass): add some tests about one-time bindings and objects inside arrays 2016-12-09 12:01:37 +02:00
Georgios Kalpakas 996914c6b0 refactor(ngClass): remove redundant $observer and dependency on $animate
Includes the following commits (see #15246 for details):

- **refactor(ngClass): remove unnecessary dependency on `$animate`**

- **refactor(ngClass): remove redundant `$observe`r**

  The code was added in b41fe9f in order to support having both `ngClass` and
  interpolation in `class` work together. `ngClass` has changed considerably since
  b41fe9f and for simple cases to work the `$observe`r is no longer necessary (as
  indicated by the expanded test still passing).

  That said, it is a [documented known issue][1] that `ngClass` should not be used
  together with interpolation in `class` and more complicated cases do not work
  anyway.

[1]: https://docs.angularjs.org/api/ng/directive/ngClass#known-issues
2016-12-09 12:00:41 +02:00
Aman Mittal fff048d099 docs(guide/external-resources): add "AngularJS in Action" book
Closes #15480
2016-12-09 11:36:26 +02:00
Peter Bacon Darwin dcb0da8225 chore(docs): fix plnkrOpener to use $onInit
Since 1.6.0 does not preassign bindings before running the controller
constructor function, we must move initialisation into the `$onInit`
method.
2016-12-09 11:28:23 +02:00
Peter Bacon Darwin b664e20d12 chore(package): update docs app to run on 1.6.0 2016-12-09 11:27:20 +02:00
Georgios Kalpakas 3d68b95028 fix(jqLite): silently ignore after() if element has no parent
Previously, the element was always assumed to have a parent and an error was
thrown when that was not the case.
This commit makes jqLite consistent with jQuery, which silently ignores a call
on elements that do not have a parent.

Fixes #15331
Closes #15367

Closes #15475
2016-12-09 10:56:14 +02:00
Georgios Kalpakas 163aca336d fix($rootScope): when adding/removing watchers during $digest
Previously, adding a watcher during a `$digest` (i.e. from within a watcher),
would result in the next watcher getting skipped. Similarly, removing a watcher
during a `$digest` could result in the current watcher being run twice (if the
removed watcher had not run yet in the current `$digest`).

This commit fixes both cases by keeping track of the current watcher index
during a digest and properly updating it when adding/removing watchers.

Fixes #15422

Closes #15424
2016-12-09 10:44:24 +02:00
100 changed files with 4424 additions and 2098 deletions
+361 -26
View File
@@ -1,3 +1,153 @@
<a name="1.5.11"></a>
# 1.5.11 princely-quest (2017-01-13)
## Bug Fixes
- **$compile:** allow the usage of "$" in isolate scope property alias
([e75fbc](https://github.com/angular/angular.js/commit/e75fbc494e6a0da6a9231b40bb0382431b62be07),
[#15586](https://github.com/angular/angular.js/issues/15586),
[#15594](https://github.com/angular/angular.js/issues/15594))
- **angularInit:** allow auto-bootstraping from inline script
([41aa91](https://github.com/angular/angular.js/commit/41aa9125b9aaf771addb250642f524a4e6f9d8d3),
[#15567](https://github.com/angular/angular.js/issues/15567),
[#15571](https://github.com/angular/angular.js/issues/15571))
- **$resource:** delete `$cancelRequest()` in `toJSON()`
([4f3858](https://github.com/angular/angular.js/commit/4f3858e7c371f87534397f45b9d002add33b00cc),
[#15244](https://github.com/angular/angular.js/issues/15244))
- **$$cookieReader:** correctly handle forbidden access to `document.cookie`
([6933cf](https://github.com/angular/angular.js/commit/6933cf64fe51f54b10d1639f2b95bab3c1178df9),
[#15523](https://github.com/angular/angular.js/issues/15523),
[#15532](https://github.com/angular/angular.js/issues/15532))
<a name="1.6.1"></a>
# 1.6.1 promise-rectification (2016-12-23)
## Bug Fixes
- **$q:** Add traceback to unhandled promise rejections
([174cb4](https://github.com/angular/angular.js/commit/174cb4a8c81e25581da5b452c2bb43b0fa377a9b),
[#14631](https://github.com/angular/angular.js/issues/14631))
- **$$cookieReader:** correctly handle forbidden access to `document.cookie`
([33f769](https://github.com/angular/angular.js/commit/33f769b0a1214055c16fb59adad4897bf53d62bf),
[#15523](https://github.com/angular/angular.js/issues/15523))
- **ngOptions:** do not unset the `selected` property unless necessary
([bc4844](https://github.com/angular/angular.js/commit/bc4844d3b297d80aecef89aa1b32615024decedc),
[#15477](https://github.com/angular/angular.js/issues/15477))
- **ngModelOptions:** work correctly when on the template of `replace` directives
([5f8ed6](https://github.com/angular/angular.js/commit/5f8ed63f2ab02ffb9c21bf9c29d27c851d162e26),
[#15492](https://github.com/angular/angular.js/issues/15492))
- **ngClassOdd/Even:** add/remove the correct classes when expression/`$index` change simultaneously
([d52864](https://github.com/angular/angular.js/commit/d528644fe3e9ffd43999e7fc67806059f9e1083e))
- **jqLite:** silently ignore `after()` if element has no parent
([3d68b9](https://github.com/angular/angular.js/commit/3d68b9502848ff6714ef89bfb95b8e70ae34eff6),
[#15331](https://github.com/angular/angular.js/issues/15331),
[#15475](https://github.com/angular/angular.js/issues/15475))
- **$rootScope:** when adding/removing watchers during $digest
([163aca](https://github.com/angular/angular.js/commit/163aca336d7586a45255787af41b14b2a12361dd),
[#15422](https://github.com/angular/angular.js/issues/15422))
## Performance Improvements
- **ngClass:** avoid unnecessary `.data()` accesses, deep-watching and copies
([1d3b65](https://github.com/angular/angular.js/commit/1d3b65adc2c22ff662159ef910089cf10d1edb7b),
[#14404](https://github.com/angular/angular.js/issues/14404))
<a name="1.5.10"></a>
# 1.5.10 asynchronous-synchronization (2016-12-15)
## Bug Fixes
- **$compile:**
- don't throw tplrt error when there is whitespace around a top-level comment
([12752f](https://github.com/angular/angular.js/commit/12752f66ac425ab38a5ee574a4bfbf3516adc42c),
[#15108](https://github.com/angular/angular.js/issues/15108))
- clean up `@`-binding observers when re-assigning bindings
([f3cb6e](https://github.com/angular/angular.js/commit/f3cb6e309aa1f676e5951ac745fa886d3581c2f4),
[#15268](https://github.com/angular/angular.js/issues/15268))
- set attribute value even if `ngAttr*` contains no interpolation
([229799](https://github.com/angular/angular.js/commit/22979904fb754c59e9f6ee5d8763e3b8de0e18c2),
[#15133](https://github.com/angular/angular.js/issues/15133))
- `bindToController` should work without `controllerAs`
([944989](https://github.com/angular/angular.js/commit/9449893763a4fd95ee8ff78b53c6966a874ec9ae),
[#15088](https://github.com/angular/angular.js/issues/15088))
- do not overwrite values set in `$onInit()` for `<`-bound literals
([07e1ba](https://github.com/angular/angular.js/commit/07e1ba365fb5e8a049be732bd7b62f71e0aa1672),
[#15118](https://github.com/angular/angular.js/issues/15118))
- avoid calling `$onChanges()` twice for `NaN` initial values
([0cf5be](https://github.com/angular/angular.js/commit/0cf5be52642f7e9d81a708b3005042eac6492572))
- **$location:** prevent infinite digest with IDN urls in Edge
([4bf892](https://github.com/angular/angular.js/commit/4bf89218130d434771089fdfe643490b8d2ee259),
[#15217](https://github.com/angular/angular.js/issues/15217))
- **$rootScope:** correctly handle adding/removing watchers during `$digest`
([a9708d](https://github.com/angular/angular.js/commit/a9708de84b50f06eacda33834d5bbdfc97c97f37),
[#15422](https://github.com/angular/angular.js/issues/15422))
- **$sce:** fix `adjustMatcher` to replace multiple `*` and `**`
([78eecb](https://github.com/angular/angular.js/commit/78eecb43dbb0500358d333aea8955bd0646a7790))
- **jqLite:** silently ignore `after()` if element has no parent
([77ed85](https://github.com/angular/angular.js/commit/77ed85bcd3be057a5a79231565ac7accc6d644c6),
[#15331](https://github.com/angular/angular.js/issues/15331))
- **input[radio]:** use non-strict comparison for checkedness
([593a50](https://github.com/angular/angular.js/commit/593a5034841b3b7661d3bcbdd06b7a9d0876fd34))
- **select, ngOptions:**
- let `ngValue` take precedence over option text with multiple interpolations
([5b7ec8](https://github.com/angular/angular.js/commit/5b7ec8c84e88ee08aacaf9404853eda0016093f5),
[#15413](https://github.com/angular/angular.js/issues/15413))
- don't add comment nodes as empty options
([1d29c9](https://github.com/angular/angular.js/commit/1d29c91c3429de96e4103533752700d1266741be),
[#15454](https://github.com/angular/angular.js/issues/15454))
- **ngClassOdd/Even:** add/remove the correct classes when expression/`$index` change simultaneously
([e3d020](https://github.com/angular/angular.js/commit/e3d02070ab8a02c818dcc5114db6fba9d3f385d6))
- **$sanitize:** reduce stack height in IE <= 11
([862dc2](https://github.com/angular/angular.js/commit/862dc2532f8126a4a71fd3d957884ba6f11f591c),
[#14928](https://github.com/angular/angular.js/issues/14928))
- **ngMock/$controller:** respect `$compileProvider.preAssignBindingsEnabled()`
([75c83f](https://github.com/angular/angular.js/commit/75c83ff3195931859a099f7a95bf81d32abf2eb3))
## New Features
- **bootstrap:** do not bootstrap from unknown schemes with a different origin
([bdeb33](https://github.com/angular/angular.js/commit/bdeb3392a8719131ab2b993f2a881c43a2860f92),
[#15428](https://github.com/angular/angular.js/issues/15428))
- **$anchorScroll:** convert numeric hash targets to string
([a52640](https://github.com/angular/angular.js/commit/a5264090b66ad0cf9a93de84bb7b307868c0edef),
[#14680](https://github.com/angular/angular.js/issues/14680))
- **$compile:**
- add `preAssignBindingsEnabled` option
([f86576](https://github.com/angular/angular.js/commit/f86576def44005f180a66e3aa12d6cc73c1ac72c))
- throw error when directive name or factory function is invalid
([5c9399](https://github.com/angular/angular.js/commit/5c9399d18ae5cd79e6cf6fc4377d66df00f6fcc7),
[#15056](https://github.com/angular/angular.js/issues/15056))
- **$controller:** throw when requested controller is not registered
([9ae793](https://github.com/angular/angular.js/commit/9ae793d8a69afe84370b601e07fc375fc18a576a),
[#14980](https://github.com/angular/angular.js/issues/14980))
- **$location:** add support for selectively rewriting links based on attribute
([a4a222](https://github.com/angular/angular.js/commit/a4a22266f127d3b9a6818e6f4754f048e253f693))
- **$resource:** pass `status`/`statusText` to success callbacks
([a8da25](https://github.com/angular/angular.js/commit/a8da25c74d2c1f6265f0fafd95bf72c981d9d678),
[#8341](https://github.com/angular/angular.js/issues/8841),
[#8841](https://github.com/angular/angular.js/issues/8841))
- **ngSwitch:** allow multiple case matches via optional attribute `ngSwitchWhenSeparator`
([0e1651](https://github.com/angular/angular.js/commit/0e1651bfd28ba73ebd0e4943d85af48c4506e02c),
[#3410](https://github.com/angular/angular.js/issues/3410),
[#3516](https://github.com/angular/angular.js/issues/3516))
## Performance Improvements
- **all:** don't trigger digests after enter/leave of structural directives
([c57779](https://github.com/angular/angular.js/commit/c57779d8725493c5853dceda0105dafd5c0e3a7c),
[#15322](https://github.com/angular/angular.js/issues/15322))
- **$compile:** validate `directive.restrict` property on directive init
([31d464](https://github.com/angular/angular.js/commit/31d464feef38b1cc950da6c8dccd0f194ebfc68b))
- **ngOptions:** avoid calls to `element.value`
([e269ad](https://github.com/angular/angular.js/commit/e269ad1244bc50fee9218f7c18fab3e9ab063aab))
- **jqLite:** move bind/unbind definitions out of the loop
([7717b9](https://github.com/angular/angular.js/commit/7717b96e950a5916a5f12fd611c73d3b06a8d717))
<a name="1.6.0"></a>
# 1.6.0 rainbow-tsunami (2016-12-08)
@@ -8,14 +158,23 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
- **ngModelOptions:** allow options to be inherited from ancestor `ngModelOptions`
([296cfc](https://github.com/angular/angular.js/commit/296cfce40c25e9438bfa46a0eb27240707a10ffa),
[#10922](https://github.com/angular/angular.js/issues/10922))
- **$compile:** set `preAssignBindingsEnabled` to false by default
([bcd0d4](https://github.com/angular/angular.js/commit/bcd0d4d896d0dfdd988ff4f849c1d40366125858),
[#15352](https://github.com/angular/angular.js/issues/15352))
- **$compile:**
- add `preAssignBindingsEnabled` option
([dfb8cf](https://github.com/angular/angular.js/commit/dfb8cf6402678206132e5bc603764d21e0f986ef))
- set `preAssignBindingsEnabled` to false by default
([bcd0d4](https://github.com/angular/angular.js/commit/bcd0d4d896d0dfdd988ff4f849c1d40366125858),
[#15352](https://github.com/angular/angular.js/issues/15352))
- throw error when directive name or factory function is invalid
([53a3bf](https://github.com/angular/angular.js/commit/53a3bf6634600c3aeff092eacc35edf399b27aec)
[#15056](https://github.com/angular/angular.js/issues/15056))
- **jqLite:**
- implement `jqLite(f)` as an alias to `jqLite(document).ready(f)`
([369fb7](https://github.com/angular/angular.js/commit/369fb7f4f73664bcdab0350701552d8bef6f605e))
- don't throw for elements with missing `getAttribute`
([4e6c14](https://github.com/angular/angular.js/commit/4e6c14dcae4a9a30b3610a288ef8d20db47c4417))
- don't get/set properties when getting/setting boolean attributes
([7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304),
[#14126](https://github.com/angular/angular.js/issues/14126))
- don't remove a boolean attribute for `.attr(attrName, '')`
([3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a))
- remove the attribute for `.attr(attribute, null)`
@@ -38,6 +197,9 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
- JSONP requests now require a trusted resource URL
([6476af](https://github.com/angular/angular.js/commit/6476af83cd0418c84e034a955b12a842794385c4),
[#11352](https://github.com/angular/angular.js/issues/11352))
- **$anchorScroll:** convert numeric hash targets to string
([9062ba](https://github.com/angular/angular.js/commit/9062bae05c002934fe7bfd76043dcc3de9acfde6)
[#14680](https://github.com/angular/angular.js/issues/14680))
- **select:** support values of any type added with `ngValue`
([f02b70](https://github.com/angular/angular.js/commit/f02b707b5e4a5ffd1e1a20d910754cfabfc19622),
[#9842](https://github.com/angular/angular.js/issues/9842))
@@ -51,6 +213,10 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
[#10597](https://github.com/angular/angular.js/issues/10597))
- allow `ngTrim` to work for `input[type=radio]`
([47724b](https://github.com/angular/angular.js/commit/47724baffe050269385b3481e9a9cf4ab3944b4b))
- **ngSwitch:** allow multiple case matches via optional attribute `ngSwitchWhenSeparator`
([0b221](https://github.com/angular/angular.js/commit/0b22173000596bf4b78f6a90083b994d46164d79)
[#3410](https://github.com/angular/angular.js/issues/3410)
[#3516](https://github.com/angular/angular.js/issues/3516))
- **$interpolate:** use custom `toString()` function if present
([a5fd2e](https://github.com/angular/angular.js/commit/a5fd2e4c0376676fa317e09a8d8be4966b82cbfe),
[#7317](https://github.com/angular/angular.js/issues/7317),
@@ -66,18 +232,30 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
([c9dffd](https://github.com/angular/angular.js/commit/c9dffde1cb167660120753181cb6d01dc1d1b3d0),
[#13653](https://github.com/angular/angular.js/issues/13653),
[#7992](https://github.com/angular/angular.js/issues/7992))
- **$location:** default hashPrefix to `'!'`
([aa077e](https://github.com/angular/angular.js/commit/aa077e81129c740041438688dff2e8d20c3d7b52),
[#13812](https://github.com/angular/angular.js/issues/13812))
- **$resource:** pass `status`/`statusText` to success callbacks
([e3a378](https://github.com/angular/angular.js/commit/e3a378e7a329f60f6b48517f83a4f4c9efecb056)
[#8341](https://github.com/angular/angular.js/issues/8841)
[#8841](https://github.com/angular/angular.js/issues/8841))
- **$location:**
- default hashPrefix to `'!'`
([aa077e](https://github.com/angular/angular.js/commit/aa077e81129c740041438688dff2e8d20c3d7b52)
[#13812](https://github.com/angular/angular.js/issues/13812))
- add support for selectively rewriting links based on attribute
([3d686a](https://github.com/angular/angular.js/commit/3d686a988dc4373da094cff6905e5b0d8da6afa4))
- **$controller:** throw when requested controller is not registered
([eacfe4](https://github.com/angular/angular.js/commit/eacfe4148eb97e550117ed7fd3c37b58537a9f64)
[#14980](https://github.com/angular/angular.js/issues/14980))
## Security Related
- Please read the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/09/angular-16-expression-sandbox-removal.html).
- **bootstrap:** explicitly whitelist URL schemes for bootstrap. (#15427)
([7f1b8b](https://github.com/angular/angular.js/commit/7f1b8bdfe1043871c5ead2ec602efc41e0de5e53))
- **bootstrap:**
- explicitly whitelist URL schemes for bootstrap.
([7f1b8b](https://github.com/angular/angular.js/commit/7f1b8bdfe1043871c5ead2ec602efc41e0de5e53))
- do not bootstrap from unknown schemes with a different origin
([465d17](https://github.com/angular/angular.js/commit/465d1734559ca4a7f4aa24387060f88fcc53ecb1))
([465d17](https://github.com/angular/angular.js/commit/465d1734559ca4a7f4aa24387060f88fcc53ecb1)
[#15428](https://github.com/angular/angular.js/issues/15428))
- **$compile:**
- secure `link[href]` as a `RESOURCE_URL`s in `$sce`
([04cad4](https://github.com/angular/angular.js/commit/04cad41d26ebaf44b5ee0c29a152d61f235f3efa),
@@ -87,14 +265,18 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
## Bug Fixes
- **$sce:** fix `adjustMatcher` to replace multiple '*' and '**' (#7897)
- **$sce:** fix `adjustMatcher` to replace multiple `*` and `**`
([991a2b](https://github.com/angular/angular.js/commit/991a2b30e00aed1d312e29555e356a795f9e3d62))
- **ngModelOptions:** handle debounce of `updateOn` triggers that are not in debounce list
([789790](https://github.com/angular/angular.js/commit/789790feee4d6c5b1f5d5b18ecb0ccf6edd36fb3))
- **ngMock/$controller:** respect `$compileProvider.preAssignBindingsEnabled()`
([7d9a79](https://github.com/angular/angular.js/commit/7d9a791c6a8c80d29d6c84afa287c81f2a307439))
- **$location:** throw if the path starts with double (back)slashes
([4aa953](https://github.com/angular/angular.js/commit/4aa9534b0fea732d6492a2863c3ee7e077c8d004))
- **$location:**
- prevent infinite digest with IDN URLs in Edge
([705afc](https://github.com/angular/angular.js/commit/705afcd160c8428133b36f2cd63db305dc52f2d7)
[#15217](https://github.com/angular/angular.js/issues/15217))
- throw if the path starts with double (back)slashes
([4aa953](https://github.com/angular/angular.js/commit/4aa9534b0fea732d6492a2863c3ee7e077c8d004))
- **core:** do not auto-bootstrap when loaded from an extension.
([0ff10e](https://github.com/angular/angular.js/commit/0ff10e1b56c6b7c4ac465e35c96a5886e294bac5))
- **input[radio]:** use strict comparison when evaluating checked-ness
@@ -124,6 +306,20 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
- don't throw tplrt error when there is a whitespace around a top-level comment
([76d3da](https://github.com/angular/angular.js/commit/76d3dafdeaf2f343d094b5a34ffb74adf64bb284),
[#15108](https://github.com/angular/angular.js/issues/15108))
- clean up `@`-binding observers when re-assigning bindings
([586e2a](https://github.com/angular/angular.js/commit/586e2acb269016a0fee66ac33f4a385f631afad0)
[#15268](https://github.com/angular/angular.js/issues/15268))
- set attribute value even if `ngAttr*` contains no interpolation
([3fe3da](https://github.com/angular/angular.js/commit/3fe3da8794571a1479d884be26a621f06cdb7842)
[#15133](https://github.com/angular/angular.js/issues/15133))
- `bindToController` should work without `controllerAs`
([16dcce](https://github.com/angular/angular.js/commit/16dccea8873b06285d4ec6eb3bb8e96ccbd3b64e)
[#15088](https://github.com/angular/angular.js/issues/15088))
- do not overwrite values set in `$onInit()` for `<`-bound literals
([a1bdff](https://github.com/angular/angular.js/commit/a1bdffa12f82e838dee5492956b380df7e54cdf9)
[#15118](https://github.com/angular/angular.js/issues/15118))
- avoid calling `$onChanges()` twice for `NaN` initial values
([7d7efb](https://github.com/angular/angular.js/commit/7d7efbf545c8c07713eb45301660dcfca4121445))
- disallow linking the same element more than once
([1e1fbc](https://github.com/angular/angular.js/commit/1e1fbc75f5e20e8541f517a5cf6f30f8f2eed53f))
- correctly merge consecutive text nodes on IE11
@@ -136,14 +332,14 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
[#5513](https://github.com/angular/angular.js/issues/5513),
[#5597](https://github.com/angular/angular.js/issues/5597))
- move check for interpolation of on-event attributes to compile time
([b89c21](https://github.com/angular/angular.js/commit/b89c2181a9a165e06c027390164e08635ec449f4),
[#13267](https://github.com/angular/angular.js/issues/13267))
([b89c21](https://github.com/angular/angular.js/commit/b89c2181a9a165e06c027390164e08635ec449f4),
[#13267](https://github.com/angular/angular.js/issues/13267))
- **select, ngOptions, ngValue:**
- don't add comment nodes as empty options
([245b27](https://github.com/angular/angular.js/commit/245b27101aad129061585252b73652054319ca82),
[#15454](https://github.com/angular/angular.js/issues/15454))
- do not throw when removing the element (e.g. via `ngIf`)
([7a667c](https://github.com/angular/angular.js/commit/7a667c77e36f2b1738425a9cfb52d48bb9d8220f))
([7a667c](https://github.com/angular/angular.js/commit/7a667c77e36f2b1738425a9cfb52d48bb9d8220f))
- add/remove selected attribute for selected/unselected options
([c75698](https://github.com/angular/angular.js/commit/c75698df55f5a026bcd7fcecbb9d4ff0bc3ebc3e))
- don't register options when select has no ngModel
@@ -158,7 +354,7 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
([e6afca](https://github.com/angular/angular.js/commit/e6afca00c9061a3e13b570796ca3ab428c1723a1),
[#14031](https://github.com/angular/angular.js/issues/14031))
- **$resource:**
- **$resource:** allow params in `hostname` (except for IPv6 addresses)
- allow params in `hostname` (except for IPv6 addresses)
([752b1e](https://github.com/angular/angular.js/commit/752b1e69b7a8e9c0b908f1980e9c738888f3647c),
[#14542](https://github.com/angular/angular.js/issues/14542))
- fulfill promise with the correct value on error
@@ -202,6 +398,9 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
- **loader:** `module.decorator` order of operations is now irrelevant
([6a2ebd](https://github.com/angular/angular.js/commit/6a2ebdba5df27e789e3cb10f11eedf90f7b9b97e),
[#12382](https://github.com/angular/angular.js/issues/12382))
- **$sanitize:** reduce stack height in IE <= 11
([45129c](https://github.com/angular/angular.js/commit/45129cfd06104bd89f469dded9ccbaf20894bd76)
[#14928](https://github.com/angular/angular.js/issues/14928))
- **ngAnimate:** make svg elements work with `classNameFilter`
([81bf7e](https://github.com/angular/angular.js/commit/81bf7ed73ee67f9eb997da869c52839449ca02b3))
@@ -221,8 +420,11 @@ consolidating all the changes shown in the previous 1.6.0 release candidates.**
([d71dc2](https://github.com/angular/angular.js/commit/d71dc2f5afec230711351e9f160873a41eb60597))
- **injector:** cache the results of the native class detection check
([5ceb5d](https://github.com/angular/angular.js/commit/5ceb5dbfa6d9b6d15232a1f5c767b2f431325948))
- **$compile:** use strict comparison for `controller === '@'`
([bbd3db](https://github.com/angular/angular.js/commit/bbd3db14f857aab996ad129f2f15ca6348e9fd9f))
- **$compile:**
- use strict comparison for `controller === '@'`
([bbd3db](https://github.com/angular/angular.js/commit/bbd3db14f857aab996ad129f2f15ca6348e9fd9f))
- validate `directive.restrict` property on directive init
([11f273](https://github.com/angular/angular.js/commit/11f2731f72e932615e8ce15e6a73f4ac808cc7e7))
- **$parse:**
- Inline constants
([bd7d5f](https://github.com/angular/angular.js/commit/bd7d5f6345439aa2d1da708ffee20b4c565131d4))
@@ -452,6 +654,48 @@ var bgColor = elem.css('background-color');
var bgColor = elem.css('backgroundColor');
```
- **[7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304)**: don't get/set properties when getting/setting boolean attributes
Previously, all boolean attributes were reflected into the corresponding property when calling a
setter and from the corresponding property when calling a getter, even on elements that don't treat
those attributes in a special way. Now Angular doesn't do it by itself, but relies on browsers to
know when to reflect the property. Note that this browser-level conversion differs between browsers;
if you need to dynamically change the state of an element, you should modify the property, not the
attribute. See https://jquery.com/upgrade-guide/1.9/#attr-versus-prop- for a more detailed
description about a related change in jQuery 1.9.
This change aligns jqLite with jQuery 3. To migrate the code follow the example below:
Before:
CSS:
```css
input[checked="checked"] { ... }
```
JS:
```js
elem1.attr('checked', 'checked');
elem2.attr('checked', false);
```
After:
CSS:
```css
input:checked { ... }
```
JS:
```js
elem1.prop('checked', true);
elem2.prop('checked', false);
```
- **[3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a)**:
don't remove a boolean attribute for `.attr(attrName, '')`
@@ -875,7 +1119,7 @@ previous behaviour simply add a comment:
**Note:** Everything described below affects **IE11 only**.
Previously, consecutive text nodes would not get merged if they had no parent. They will now, which
might have unexpectd side effects in the following cases:
might have unexpected side effects in the following cases:
1. Passing an array or jqLite/jQuery collection of parent-less text nodes to `$compile` directly:
@@ -1029,7 +1273,7 @@ In cases where `ngView` was loaded asynchronously, `$route` (and its dependencie
might also have been instantiated asynchronously. After this change, `$route` (and its dependencies)
will - by default - be instantiated early on.
Although this is not expected to have unwanted side-effects in normal application bebavior, it may
Although this is not expected to have unwanted side-effects in normal application behavior, it may
affect your unit tests: When testing a module that (directly or indirectly) depends on `ngRoute`, a
request will be made for the default route's template. If not properly "trained", `$httpBackend`
will complain about this unexpected request.
@@ -1595,6 +1839,7 @@ Please read the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/0
- **ngModel:** treat synchronous validators as boolean always ([7bc71a](https://github.com/angular/angular.js/commit/7bc71adc63bb6bb609b44dd2d3ea8fb0cd3f300b) [#14734](https://github.com/angular/angular.js/issues/14734))
- **$q:** treat thrown errors as regular rejections ([e13eea](https://github.com/angular/angular.js/commit/e13eeabd7e34a78becec06cfbe72c23f2dcb85f9) [#3174](https://github.com/angular/angular.js/issues/3174) [#15213](https://github.com/angular/angular.js/issues/15213))
- **ngTransclude:** use fallback content if only whitespace is provided ([32aa7e](https://github.com/angular/angular.js/commit/32aa7e7395527624119e3917c54ee43b4d219301) [#15077](https://github.com/angular/angular.js/issues/15077))
- **$location:** prevent infinite digest with IDN URLs in Edge ([705afc](https://github.com/angular/angular.js/commit/705afcd160c8428133b36f2cd63db305dc52f2d7) [#15217](https://github.com/angular/angular.js/issues/15217))
- **$compile:**
- don't throw tplrt error when there is a whitespace around a top-level comment ([76d3da](https://github.com/angular/angular.js/commit/76d3dafdeaf2f343d094b5a34ffb74adf64bb284) [#15108](https://github.com/angular/angular.js/issues/15108))
- disallow linking the same element more than once ([1e1fbc](https://github.com/angular/angular.js/commit/1e1fbc75f5e20e8541f517a5cf6f30f8f2eed53f))
@@ -1604,6 +1849,20 @@ Please read the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/0
- don't add leading white-space in attributes for a specific merge case ([305ba1](https://github.com/angular/angular.js/commit/305ba1a3fb3529cb3fdf04c12ac03fbb4f634456))
- don't trim white-space in attributes ([97bbf8](https://github.com/angular/angular.js/commit/97bbf86a1979d099802f0d631c17c54b87563b40) [#5513](https://github.com/angular/angular.js/issues/5513) [#5597](https://github.com/angular/angular.js/issues/5597))
- move check for interpolation of on-event attributes to compile time ([b89c21](https://github.com/angular/angular.js/commit/b89c2181a9a165e06c027390164e08635ec449f4) [#13267](https://github.com/angular/angular.js/issues/13267))
- clean up `@`-binding observers when re-assigning bindings
([586e2a](https://github.com/angular/angular.js/commit/586e2acb269016a0fee66ac33f4a385f631afad0)
[#15268](https://github.com/angular/angular.js/issues/15268))
- set attribute value even if `ngAttr*` contains no interpolation
([3fe3da](https://github.com/angular/angular.js/commit/3fe3da8794571a1479d884be26a621f06cdb7842)
[#15133](https://github.com/angular/angular.js/issues/15133))
- `bindToController` should work without `controllerAs`
([16dcce](https://github.com/angular/angular.js/commit/16dccea8873b06285d4ec6eb3bb8e96ccbd3b64e)
[#15088](https://github.com/angular/angular.js/issues/15088))
- do not overwrite values set in `$onInit()` for `<`-bound literals
([a1bdff](https://github.com/angular/angular.js/commit/a1bdffa12f82e838dee5492956b380df7e54cdf9)
[#15118](https://github.com/angular/angular.js/issues/15118))
- avoid calling `$onChanges()` twice for `NaN` initial values
([7d7efb](https://github.com/angular/angular.js/commit/7d7efbf545c8c07713eb45301660dcfca4121445))
- **select:**
- add/remove selected attribute for selected/unselected options ([c75698](https://github.com/angular/angular.js/commit/c75698df55f5a026bcd7fcecbb9d4ff0bc3ebc3e))
- don't register options when select has no ngModel ([e8c2e1](https://github.com/angular/angular.js/commit/e8c2e119758e58e18fe43932d09a8ff9f506aa9d))
@@ -1627,6 +1886,9 @@ Please read the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/0
- **ngMock/$httpBackend:** fail if a url is provided but is `undefined` ([7551b8](https://github.com/angular/angular.js/commit/7551b8975a91ee286cc2cf4af5e78f924533575e) [#8442](https://github.com/angular/angular.js/issues/8442) [#10934](https://github.com/angular/angular.js/issues/10934))
- **$route:** don't process route change controllers and templates for `redirectTo` routes ([7f4b35](https://github.com/angular/angular.js/commit/7f4b356c2bebb87f0c26b57a20415b004b20bcd1) [#3332](https://github.com/angular/angular.js/issues/3332))
- **loader:** `module.decorator` order of operations is now irrelevant ([6a2ebd](https://github.com/angular/angular.js/commit/6a2ebdba5df27e789e3cb10f11eedf90f7b9b97e) [#12382](https://github.com/angular/angular.js/issues/12382))
- **$sanitize:** reduce stack height in IE <= 11
([45129c](https://github.com/angular/angular.js/commit/45129cfd06104bd89f469dded9ccbaf20894bd76)
[#14928](https://github.com/angular/angular.js/issues/14928))
- **ngAnimate:** make svg elements work with `classNameFilter` ([81bf7e](https://github.com/angular/angular.js/commit/81bf7ed73ee67f9eb997da869c52839449ca02b3))
@@ -1634,24 +1896,50 @@ Please read the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/0
- **jqLite:**
- implement `jqLite(f)` as an alias to `jqLite(document).ready(f)` ([369fb7](https://github.com/angular/angular.js/commit/369fb7f4f73664bcdab0350701552d8bef6f605e))
- don't throw for elements with missing `getAttribute` ([4e6c14](https://github.com/angular/angular.js/commit/4e6c14dcae4a9a30b3610a288ef8d20db47c4417))
- don't get/set properties when getting/setting boolean attributes ([7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304), [#14126](https://github.com/angular/angular.js/issues/14126))
- don't remove a boolean attribute for `.attr(attrName, '')` ([3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a))
- remove the attribute for `.attr(attribute, null)` ([4e3624](https://github.com/angular/angular.js/commit/4e3624552284d0e725bf6262b2e468cd2c7682fa))
- return `[]` for `.val()` on `<select multiple>` with no selection ([d882fd](https://github.com/angular/angular.js/commit/d882fde2e532216e7cf424495db1ccb5be1789f8))
- **$compile:**
- add `preAssignBindingsEnabled` option
([dfb8cf](https://github.com/angular/angular.js/commit/dfb8cf6402678206132e5bc603764d21e0f986ef))
- throw error when directive name or factory function is invalid
([53a3bf](https://github.com/angular/angular.js/commit/53a3bf6634600c3aeff092eacc35edf399b27aec)
[#15056](https://github.com/angular/angular.js/issues/15056))
- **$http:**
- remove deprecated callback methods: `success()/error()` ([b54a39](https://github.com/angular/angular.js/commit/b54a39e2029005e0572fbd2ac0e8f6a4e5d69014))
- JSONP callback must be specified by `jsonpCallbackParam` config ([fb6634](https://github.com/angular/angular.js/commit/fb663418710736161a6b5da49c345e92edf58dcb) [#15161](https://github.com/angular/angular.js/issues/15161) [#11352](https://github.com/angular/angular.js/issues/11352))
- JSONP requests now require a trusted resource URL ([6476af](https://github.com/angular/angular.js/commit/6476af83cd0418c84e034a955b12a842794385c4) [#11352](https://github.com/angular/angular.js/issues/11352))
- **$anchorScroll:** convert numeric hash targets to string
([9062ba](https://github.com/angular/angular.js/commit/9062bae05c002934fe7bfd76043dcc3de9acfde6)
[#14680](https://github.com/angular/angular.js/issues/14680))
- **ngModelOptions:** allow options to be inherited from ancestor `ngModelOptions` ([87a2ff](https://github.com/angular/angular.js/commit/87a2ff76af5d0a9268d8eb84db5755077d27c84c) [#10922](https://github.com/angular/angular.js/issues/10922))
- **input:**
- add support for binding to `input[type=range]` ([913016](https://github.com/angular/angular.js/commit/9130166767c4792c5d32d08a918fc7becf32c9a6) [#5892](https://github.com/angular/angular.js/issues/5892) [#14870](https://github.com/angular/angular.js/issues/14870))
- add support for `step` to `input[type=number]` ([e1da4be](https://github.com/angular/angular.js/commit/e1da4bed8e291003d485a8ad346ab80bed8ae2e3) [#10597](https://github.com/angular/angular.js/issues/10597))
- allow `ngTrim` to work for `input[type=radio]` ([47724b](https://github.com/angular/angular.js/commit/47724baffe050269385b3481e9a9cf4ab3944b4b))
- **ngSwitch:** allow multiple case matches via optional attribute `ngSwitchWhenSeparator`
([0b221](https://github.com/angular/angular.js/commit/0b22173000596bf4b78f6a90083b994d46164d79)
[#3410](https://github.com/angular/angular.js/issues/3410)
[#3516](https://github.com/angular/angular.js/issues/3516))
- **ngRoute:** allow `ngView` to be included in an asynchronously loaded template ([c13c66](https://github.com/angular/angular.js/commit/c13c666728c1a1485ef18e92d7cb35118ce39609) [#1213](https://github.com/angular/angular.js/issues/1213))
- **select:** support values of any type added with `ngValue` ([f02b70](https://github.com/angular/angular.js/commit/f02b707b5e4a5ffd1e1a20d910754cfabfc19622) [#9842](https://github.com/angular/angular.js/issues/9842))
- **$interpolate:** use custom `toString()` function if present ([a5fd2e](https://github.com/angular/angular.js/commit/a5fd2e4c0376676fa317e09a8d8be4966b82cbfe) [#7317](https://github.com/angular/angular.js/issues/7317) [#11406](https://github.com/angular/angular.js/issues/11406))
- **$route:** implement `resolveRedirectTo` ([e98656](https://github.com/angular/angular.js/commit/e9865654b39c71be71034c38581a8c7bd16bc716) [#5150](https://github.com/angular/angular.js/issues/5150))
- **$q:** report promises with non rejection callback ([c9dffd](https://github.com/angular/angular.js/commit/c9dffde1cb167660120753181cb6d01dc1d1b3d0) [#13653](https://github.com/angular/angular.js/issues/13653) [#7992](https://github.com/angular/angular.js/issues/7992))
- **$location:** default hashPrefix to `'!'` ([aa077e](https://github.com/angular/angular.js/commit/aa077e81129c740041438688dff2e8d20c3d7b52) [#13812](https://github.com/angular/angular.js/issues/13812))
- **$resource:** pass `status`/`statusText` to success callbacks
([e3a378](https://github.com/angular/angular.js/commit/e3a378e7a329f60f6b48517f83a4f4c9efecb056)
[#8341](https://github.com/angular/angular.js/issues/8841)
[#8841](https://github.com/angular/angular.js/issues/8841))
- **$location:**
- default hashPrefix to `'!'`
([aa077e](https://github.com/angular/angular.js/commit/aa077e81129c740041438688dff2e8d20c3d7b52)
[#13812](https://github.com/angular/angular.js/issues/13812))
- add support for selectively rewriting links based on attribute
([3d686a](https://github.com/angular/angular.js/commit/3d686a988dc4373da094cff6905e5b0d8da6afa4))
- **$controller:** throw when requested controller is not registered
([eacfe4](https://github.com/angular/angular.js/commit/eacfe4148eb97e550117ed7fd3c37b58537a9f64)
[#14980](https://github.com/angular/angular.js/issues/14980))
## Performance Improvements
@@ -1660,7 +1948,11 @@ Please read the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/0
- **$animate:** listen for document visibility changes ([d71dc2](https://github.com/angular/angular.js/commit/d71dc2f5afec230711351e9f160873a41eb60597))
- **injector:** cache the results of the native class detection check ([5ceb5d](https://github.com/angular/angular.js/commit/5ceb5dbfa6d9b6d15232a1f5c767b2f431325948))
- **$parse:** Inline constants ([bd7d5f](https://github.com/angular/angular.js/commit/bd7d5f6345439aa2d1da708ffee20b4c565131d4))
- **$compile:** use strict comparison for `controller === '@'` ([bbd3db](https://github.com/angular/angular.js/commit/bbd3db14f857aab996ad129f2f15ca6348e9fd9f))
- **$compile:**
- use strict comparison for `controller === '@'`
([bbd3db](https://github.com/angular/angular.js/commit/bbd3db14f857aab996ad129f2f15ca6348e9fd9f))
- validate `directive.restrict` property on directive init
([11f273](https://github.com/angular/angular.js/commit/11f2731f72e932615e8ce15e6a73f4ac808cc7e7))
- **$parse:** remove Angular expression sandbox ([1547c7](https://github.com/angular/angular.js/commit/1547c751aa48efe7dbefef701c3df5983b04aa2e) [#15094](https://github.com/angular/angular.js/issues/15094))
@@ -1782,6 +2074,48 @@ var bgColor = elem.css('background-color');
var bgColor = elem.css('backgroundColor');
```
- **[7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304)**: don't get/set properties when getting/setting boolean attributes
Previously, all boolean attributes were reflected into the corresponding property when calling a
setter and from the corresponding property when calling a getter, even on elements that don't treat
those attributes in a special way. Now Angular doesn't do it by itself, but relies on browsers to
know when to reflect the property. Note that this browser-level conversion differs between browsers;
if you need to dynamically change the state of an element, you should modify the property, not the
attribute. See https://jquery.com/upgrade-guide/1.9/#attr-versus-prop- for a more detailed
description about a related change in jQuery 1.9.
This change aligns jqLite with jQuery 3. To migrate the code follow the example below:
Before:
CSS:
```css
input[checked="checked"] { ... }
```
JS:
```js
elem1.attr('checked', 'checked');
elem2.attr('checked', false);
```
After:
CSS:
```css
input:checked { ... }
```
JS:
```js
elem1.prop('checked', true);
elem2.prop('checked', false);
```
- **[3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a)**: don't remove a boolean attribute for `.attr(attrName, '')`
Before, using the `attr` method with an empty string as a value
@@ -2215,7 +2549,7 @@ affects custom directives that might have been reading options for their own pur
**Note:** Everything described below affects **IE11 only**.
Previously, consecutive text nodes would not get merged if they had no parent. They will now, which
might have unexpectd side effects in the following cases:
might have unexpected side effects in the following cases:
1. Passing an array or jqLite/jQuery collection of parent-less text nodes to `$compile` directly:
@@ -2365,7 +2699,7 @@ In cases where `ngView` was loaded asynchronously, `$route` (and its dependencie
might also have been instantiated asynchronously. After this change, `$route` (and its dependencies)
will - by default - be instantiated early on.
Although this is not expected to have unwanted side-effects in normal application bebavior, it may
Although this is not expected to have unwanted side-effects in normal application behavior, it may
affect your unit tests: When testing a module that (directly or indirectly) depends on `ngRoute`, a
request will be made for the default route's template. If not properly "trained", `$httpBackend`
will complain about this unexpected request.
@@ -4273,6 +4607,7 @@ support asynchronous loading of resources.)
- **$compile:** Lazily compile the `transclude` function
([652b83eb](https://github.com/angular/angular.js/commit/652b83eb226131d131a44453520a569202aa4aac))
See https://github.com/angular/angular.js/issues/14343#issuecomment-229037252 for more information.
## Breaking Changes
@@ -12079,7 +12414,7 @@ Contains only these fixes cherry-picked from [v1.2.0rc1](#1.2.0rc1).
- due to [39841f2e](https://github.com/angular/angular.js/commit/39841f2ec9b17b3b2920fd1eb548d444251f4f56),
Interpolations inside DOM event handlers are disallowed.
DOM event handlers execute arbitrary Javascript code. Using an interpolation for such handlers means that the interpolated value is a JS string that is evaluated. Storing or generating such strings is error prone and leads to XSS vulnerabilities. On the other hand, `ngClick` and other Angular specific event handlers evaluate Angular expressions in non-window (Scope) context which makes them much safer.
DOM event handlers execute arbitrary JavaScript code. Using an interpolation for such handlers means that the interpolated value is a JS string that is evaluated. Storing or generating such strings is error prone and leads to XSS vulnerabilities. On the other hand, `ngClick` and other Angular specific event handlers evaluate Angular expressions in non-window (Scope) context which makes them much safer.
To migrate the code follow the example below:
@@ -14710,7 +15045,7 @@ with the `$route` service
### Docs
- rewrite of several major portions of angular.service.*, angular.Array.*, angular.Object.* docs
- added support for [sitemap]((http://docs.angularjs.org/sitemap.xml) to make the docs indexable by
- added support for [sitemap](http://docs.angularjs.org/sitemap.xml) to make the docs indexable by
search crawlers
- transition of Developer Guide docs from the wiki into docs.angularjs.org
- lots of improvements related to formatting of the content of docs.anguarjs.org
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2010-2016 Google, Inc. http://angularjs.org
Copyright (c) 2010-2017 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+44
View File
@@ -0,0 +1,44 @@
'use strict';
angular
.module('animationBenchmark', ['ngAnimate'], config)
.controller('BenchmarkController', BenchmarkController);
// Functions - Definitions
function config($compileProvider) {
$compileProvider
.commentDirectivesEnabled(false)
.cssClassDirectivesEnabled(false)
.debugInfoEnabled(false);
}
function BenchmarkController($scope) {
var self = this;
var itemCount = 1000;
var items = (new Array(itemCount + 1)).join('.').split('');
benchmarkSteps.push({
name: 'create',
fn: function() {
$scope.$apply(function() {
self.items = items;
});
}
});
benchmarkSteps.push({
name: '$digest',
fn: function() {
$scope.$root.$digest();
}
});
benchmarkSteps.push({
name: 'destroy',
fn: function() {
$scope.$apply(function() {
self.items = [];
});
}
});
}
+22
View File
@@ -0,0 +1,22 @@
/* eslint-env node */
'use strict';
module.exports = function(config) {
config.set({
scripts: [
{
id: 'jquery',
src: 'jquery-noop.js'
}, {
id: 'angular',
src: '/build/angular.js'
}, {
id: 'angular-animate',
src: '/build/angular-animate.js'
}, {
src: 'app.js'
}
]
});
};
+1
View File
@@ -0,0 +1 @@
// Override me with ?jquery=/bower_components/jquery/dist/jquery.js
+28
View File
@@ -0,0 +1,28 @@
<style>
[ng-cloak] { display: none !important; }
.animation-container .ng-enter,
.animation-container .ng-leave {
transition: all 0.1s;
}
.animation-container .ng-enter,
.animation-container .ng-leave.ng-leave-active {
opacity: 0;
}
.animation-container .ng-enter.ng-enter-active,
.animation-container .ng-leave {
opacity: 1;
}
</style>
<div ng-app="animationBenchmark" ng-cloak ng-controller="BenchmarkController as bm">
<div class="container-fluid">
<h2>Large collection of elements animated in and out with ngAnimate</h2>
<div class="animation-container">
<div ng-repeat="i in bm.items track by $index">
Just a plain ol' element
</div>
</div>
</div>
</div>
+108
View File
@@ -0,0 +1,108 @@
'use strict';
var app = angular.module('ngClassBenchmark', []);
app.controller('DataController', function DataController($scope) {
this.init = function() {
this.numberOfTodos = 1000;
this.implementation = 'tableOptimized';
this.completedPeriodicity = 3;
this.importantPeriodicity = 13;
this.urgentPeriodicity = 29;
this.createTodos(100);
this.setTodosValuesWithSeed(0);
};
this.clearTodos = function() {
this.todos = null;
};
this.createTodos = function(count) {
var i;
this.todos = [];
for (i = 0; i < count; i++) {
this.todos.push({
id: i + 1,
completed: false,
important: false,
urgent: false
});
}
};
this.setTodosValuesWithSeed = function(offset) {
var i, todo;
for (i = 0; i < this.todos.length; i++) {
todo = this.todos[i];
todo.completed = 0 === (i + offset) % this.completedPeriodicity;
todo.important = 0 === (i + offset) % this.importantPeriodicity;
todo.urgent = 0 === (i + offset) % this.urgentPeriodicity;
}
};
this.init();
benchmarkSteps.push({
name: 'setup',
fn: function() {
$scope.$apply();
this.clearTodos();
this.createTodos(this.numberOfTodos);
}.bind(this)
});
benchmarkSteps.push({
name: 'create',
fn: function() {
// initialize data for first time that will construct the DOM
this.setTodosValuesWithSeed(0);
$scope.$apply();
}.bind(this)
});
benchmarkSteps.push({
name: '$apply',
fn: function() {
$scope.$apply();
}
});
benchmarkSteps.push({
name: 'update',
fn: function() {
// move everything but completed
this.setTodosValuesWithSeed(3);
$scope.$apply();
}.bind(this)
});
benchmarkSteps.push({
name: 'unclass',
fn: function() {
// remove all classes
this.setTodosValuesWithSeed(NaN);
$scope.$apply();
}.bind(this)
});
benchmarkSteps.push({
name: 'class',
fn: function() {
// add all classes as the initial state
this.setTodosValuesWithSeed(0);
$scope.$apply();
}.bind(this)
});
benchmarkSteps.push({
name: 'destroy',
fn: function() {
this.clearTodos();
$scope.$apply();
}.bind(this)
});
});
+15
View File
@@ -0,0 +1,15 @@
/* eslint-env node */
'use strict';
module.exports = function(config) {
config.set({
scripts: [{
id: 'angular',
src: '/build/angular.js'
},
{
src: 'app.js'
}]
});
};
+177
View File
@@ -0,0 +1,177 @@
<style>
.gold {
background: gold;
}
.silver {
background: silver;
}
.table tbody tr > td.success {
background-color: #dff0d8;
}
.table tbody tr > td.error {
background-color: #f2dede;
}
.table tbody tr > td.warning {
background-color: #fcf8e3;
}
.table tbody tr > td.info {
background-color: #d9edf7;
}
.completed {
text-decoration: line-through;
}
.important {
font-weight: bold;
}
.urgent {
color: red;
}
</style>
<div ng-app="ngClassBenchmark" ng-cloak class="container-fluid">
<div ng-controller="DataController as benchmark" class="row">
<div class="col-lg-12">
<div class="well">
<h3>Parameters</h3>
<br>
<p>
<label>Number of todos</label><br>
<input type="number" ng-model="benchmark.numberOfTodos">
</p>
<br>
<p>
<label>Implementation</label><br>
<div class="radio">
<label>
<input ng-model="benchmark.implementation" value="tableOptimized"
type="radio" name="implementation">
Table optimized <br>
<code>ng-class="todo.completed && 'success'"</code>
</label>
</div>
<div class="radio">
<label>
<input ng-model="benchmark.implementation" value="table"
type="radio" name="implementation">
Table <br>
<code>ng-class="{success: todo.completed}"</code>
</label>
</div>
<div class="radio">
<label>
<input ng-model="benchmark.implementation" value="list"
type="radio" name="implementation">
List <br>
<code>ng-class="{completed: todo.completed, urgent: todo.urgent, important: todo.important"}</code>
</label>
</div>
<div class="radio">
<label>
<input ng-model="benchmark.implementation" value="singleOptimized"
type="radio" name="implementation">
Single ngClass optimized <br>
<code>
ng-class="{'panel-success': !!benchmark.todos, 'panel-danger': !benchmark.todos}"
</code>
</label>
</div>
<div class="radio">
<label>
<input ng-model="benchmark.implementation" value="single"
type="radio" name="implementation">
Single ngClass <br>
<code>
ng-class="{'panel-success': benchmark.todos, 'panel-danger': !benchmark.todos}"
</code>
</label>
</div>
</p>
</div>
<br>
<h3>Example</h3>
<div ng-switch="benchmark.implementation">
<table ng-switch-when="tableOptimized" class="table">
<thead>
<tr>
<th>todo #id</th>
<th>completed?</th>
<th>urgent?</th>
<th>important?</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="todo in benchmark.todos track by todo.id"
ng-class="todo.completed && 'active'"
ng-class-even="todo.completed && todo.important && 'gold'"
ng-class-odd="todo.completed && todo.important && 'silver'"
>
<td>#{{todo.id}}</td>
<td>{{todo.completed}}</td>
<td ng-class="todo.urgent && 'danger'">{{todo.urgent}}</td>
<td ng-class="todo.important && 'success'">{{todo.important}}</td>
</tr>
</tbody>
</table>
<table ng-switch-when="table" class="table">
<thead>
<tr>
<th>todo #id</th>
<th>completed?</th>
<th>urgent?</th>
<th>important?</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="todo in benchmark.todos track by todo.id"
ng-class="{active: todo.completed}"
ng-class-even="{gold: todo.completed && todo.important}"
ng-class-odd="{silver: todo.completed && todo.important}"
>
<td>#{{todo.id}}</td>
<td>{{todo.completed}}</td>
<td ng-class="{danger: todo.urgent}">{{todo.urgent}}</td>
<td ng-class="{success: todo.important}">{{todo.important}}</td>
</tr>
</tbody>
</table>
<ul ng-switch-when="list">
<li ng-repeat="todo in benchmark.todos track by todo.id"
ng-class="{
completed: todo.completed,
urgent: todo.urgent,
important: todo.important
}">#{{todo.id}}</li>
</ul>
<div ng-switch-when="singleOptimized"
class="panel"
ng-class="{'panel-success': !!benchmark.todos, 'panel-danger': !benchmark.todos}">
<div class="panel-heading">
<h3 class="panel-title">Information</h3>
</div>
<div class="panel-body"> The title is green because there are todos... </div>
</div>
<div ng-switch-when="single"
class="panel"
ng-class="{'panel-success': benchmark.todos, 'panel-danger': !benchmark.todos}">
<div class="panel-heading">
<h3 class="panel-title">Information</h3>
</div>
<div class="panel-body"> The title is green because there are todos... </div>
</div>
</div>
</div>
</div>
</div>
<br><br><br>
+1 -1
View File
@@ -4,7 +4,7 @@
/* global importScripts, lunr */
// Load up the lunr library
importScripts('../components/lunr.js-0.5.12/lunr.min.js');
importScripts('../components/lunr-0.7.2/lunr.min.js');
// Create the lunr index - the docs should be an array of object, each object containing
// the path and search terms for a page
+5 -4
View File
@@ -159,10 +159,11 @@ angular.module('examples', [])
};
// Initialize the example data, so it's ready when clicking the open button.
// Otherwise pop-up blockers will prevent a new window from opening
ctrl.prepareExampleData(ctrl.example.path);
ctrl.$onInit = function() {
// Initialize the example data, so it's ready when clicking the open button.
// Otherwise pop-up blockers will prevent a new window from opening
ctrl.prepareExampleData(ctrl.example.path);
};
}]
};
}])
-10
View File
@@ -1,10 +0,0 @@
{
"name": "AngularJS-docs-app",
"dependencies": {
"jquery": "2.2.3",
"lunr.js": "0.5.12",
"open-sans-fontface": "1.0.4",
"google-code-prettify": "1.0.1",
"bootstrap": "3.1.1"
}
}
+2 -2
View File
@@ -17,9 +17,9 @@ module.exports = function debugDeployment(getVersion) {
'../angular-sanitize.js',
'../angular-touch.js',
'../angular-animate.js',
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
'components/marked-' + getVersion('marked') + '/lib/marked.js',
'js/angular-bootstrap/dropdown-toggle.js',
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.js',
'components/lunr-' + getVersion('lunr') + '/lunr.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
'js/current-version-data.js',
+2 -2
View File
@@ -17,9 +17,9 @@ module.exports = function defaultDeployment(getVersion) {
'../angular-sanitize.min.js',
'../angular-touch.min.js',
'../angular-animate.min.js',
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
'components/marked-' + getVersion('marked') + '/lib/marked.js',
'js/angular-bootstrap/dropdown-toggle.min.js',
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
'components/lunr-' + getVersion('lunr') + '/lunr.min.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
'js/current-version-data.js',
+2 -2
View File
@@ -21,9 +21,9 @@ module.exports = function jqueryDeployment(getVersion) {
'../angular-sanitize.min.js',
'../angular-touch.min.js',
'../angular-animate.min.js',
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
'components/marked-' + getVersion('marked') + '/lib/marked.js',
'js/angular-bootstrap/dropdown-toggle.min.js',
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
'components/lunr-' + getVersion('lunr') + '/lunr.min.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
'js/current-version-data.js',
@@ -34,9 +34,9 @@ module.exports = function productionDeployment(getVersion) {
cdnUrl + '/angular-sanitize.min.js',
cdnUrl + '/angular-touch.min.js',
cdnUrl + '/angular-animate.min.js',
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
'components/marked-' + getVersion('marked') + '/lib/marked.js',
'js/angular-bootstrap/dropdown-toggle.min.js',
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
'components/lunr-' + getVersion('lunr') + '/lunr.min.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
'js/current-version-data.js',
+2 -2
View File
@@ -10,8 +10,8 @@ module.exports = function getVersion(readFilesProcessor) {
var basePath = readFilesProcessor.basePath;
return function(component, sourceFolder, packageFile) {
sourceFolder = path.resolve(basePath, sourceFolder || 'docs/bower_components');
packageFile = packageFile || 'bower.json';
sourceFolder = path.resolve(basePath, sourceFolder || 'node_modules');
packageFile = packageFile || 'package.json';
return require(path.join(sourceFolder,component,packageFile)).version;
};
};
@@ -214,7 +214,7 @@
<p class="pull-right"><a back-to-top>Back to top</a></p>
<p>
Super-powered by Google ©2010-2016
Super-powered by Google ©2010-2017
(<a id="version"
ng-href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
ng-bind-template="v{{version}}" title="Changelog of this version of Angular JS">
@@ -61,12 +61,12 @@
</div>
{% endblock -%}
{% include "lib/params.template.html" %}
{% include "lib/events.template.html" %}
{%- if doc.animations %}
<h2 id="animations">Animations</h2>
{$ doc.animations | marked $}
{$ 'module:ngAnimate.$animate' | link('Click here', doc) $} to learn more about the steps involved in the animation.
{%- endif -%}
{% include "lib/params.template.html" %}
{% include "lib/events.template.html" %}
{% endblock %}
+41 -31
View File
@@ -232,27 +232,27 @@ than the hash fragment was changed.
### Example
```js
it('should show example', inject(
function($locationProvider) {
it('should show example', function() {
module(function($locationProvider) {
$locationProvider.html5Mode(false);
$locationProvider.hashPrefix('!');
},
function($location) {
});
inject(function($location) {
// open http://example.com/base/index.html#!/a
$location.absUrl() === 'http://example.com/base/index.html#!/a'
$location.path() === '/a'
expect($location.absUrl()).toBe('http://example.com/base/index.html#!/a');
expect($location.path()).toBe('/a');
$location.path('/foo')
$location.absUrl() === 'http://example.com/base/index.html#!/foo'
$location.path('/foo');
expect($location.absUrl()).toBe('http://example.com/base/index.html#!/foo');
$location.search() === {}
expect($location.search()).toEqual({});
$location.search({a: 'b', c: true});
$location.absUrl() === 'http://example.com/base/index.html#!/foo?a=b&c'
expect($location.absUrl()).toBe('http://example.com/base/index.html#!/foo?a=b&c');
$location.path('/new').search('x=y');
$location.absUrl() === 'http://example.com/base/index.html#!/new?x=y'
}
));
expect($location.absUrl()).toBe('http://example.com/base/index.html#!/new?x=y');
});
});
```
## HTML5 mode
@@ -274,40 +274,50 @@ and updates the url in a way that never performs a full page reload.
### Example
```js
it('should show example', inject(
function($locationProvider) {
it('should show example', function() {
module(function($locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
},
function($location) {
});
inject(function($location) {
// in browser with HTML5 history support:
// open http://example.com/#!/a -> rewrite to http://example.com/a
// (replacing the http://example.com/#!/a history record)
$location.path() === '/a'
expect($location.path()).toBe('/a');
$location.path('/foo');
$location.absUrl() === 'http://example.com/foo'
expect($location.absUrl()).toBe('http://example.com/foo');
$location.search() === {}
expect($location.search()).toEqual({});
$location.search({a: 'b', c: true});
$location.absUrl() === 'http://example.com/foo?a=b&c'
expect($location.absUrl()).toBe('http://example.com/foo?a=b&c');
$location.path('/new').search('x=y');
$location.url() === 'new?x=y'
$location.absUrl() === 'http://example.com/new?x=y'
expect($location.url()).toBe('/new?x=y');
expect($location.absUrl()).toBe('http://example.com/new?x=y');
});
});
it('should show example (when browser doesn\'t support HTML5 mode', function() {
module(function($provide, $locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
$provide.value('$sniffer', {history: false});
});
inject(initBrowser({ url: 'http://example.com/new?x=y', basePath: '/' }),
function($location) {
// in browser without html5 history support:
// open http://example.com/new?x=y -> redirect to http://example.com/#!/new?x=y
// (again replacing the http://example.com/new?x=y history item)
$location.path() === '/new'
$location.search() === {x: 'y'}
expect($location.path()).toBe('/new');
expect($location.search()).toEqual({x: 'y'});
$location.path('/foo/bar');
$location.path() === '/foo/bar'
$location.url() === '/foo/bar?x=y'
$location.absUrl() === 'http://example.com/#!/foo/bar?x=y'
}
));
expect($location.path()).toBe('/foo/bar');
expect($location.url()).toBe('/foo/bar?x=y');
expect($location.absUrl()).toBe('http://example.com/#!/foo/bar?x=y');
});
});
```
### Fallback for legacy browsers
@@ -555,7 +565,7 @@ In these examples we use `<base href="/base/index.html" />`. The inputs represen
</example>
####Browser in HTML5 Fallback mode (Hashbang mode)
#### Browser in HTML5 Fallback mode (Hashbang mode)
<example module="hashbang-mode" name="location-hashbang-mode">
<file name="index.html">
<div ng-controller="LocationController">
+5 -5
View File
@@ -122,7 +122,7 @@ The same approach to animation can be used using JavaScript code (**jQuery is us
```js
myModule.animation('.repeated-item', function() {
return {
enter : function(element, done) {
enter: function(element, done) {
element.css('opacity',0);
jQuery(element).animate({
opacity: 1
@@ -137,7 +137,7 @@ myModule.animation('.repeated-item', function() {
}
}
},
leave : function(element, done) {
leave: function(element, done) {
element.css('opacity', 1);
jQuery(element).animate({
opacity: 0
@@ -152,7 +152,7 @@ myModule.animation('.repeated-item', function() {
}
}
},
move : function(element, done) {
move: function(element, done) {
element.css('opacity', 0);
jQuery(element).animate({
opacity: 1
@@ -169,8 +169,8 @@ myModule.animation('.repeated-item', function() {
},
// you can also capture these animation events
addClass : function(element, className, done) {},
removeClass : function(element, className, done) {}
addClass: function(element, className, done) {},
removeClass: function(element, className, done) {}
}
});
```
+1
View File
@@ -78,6 +78,7 @@ It's also possible to add components via {@link $compileProvider#component} in a
| link functions | Yes | No |
| multiElement | Yes | No |
| priority | Yes | No |
| replace | Yes (deprecated) | No |
| require | Yes | Yes |
| restrict | Yes | No (restricted to elements only) |
| scope | Yes (default: false) | No (scope is always isolate) |
+7 -7
View File
@@ -241,7 +241,7 @@ An expression that starts with `::` is considered a one-time expression. One-tim
will stop recalculating once they are stable, which happens after the first digest if the expression
result is a non-undefined value (see value stabilization algorithm below).
<example module="oneTimeBidingExampleApp" name="expression-one-time">
<example module="oneTimeBindingExampleApp" name="expression-one-time">
<file name="index.html">
<div ng-controller="EventController">
<button ng-click="clickMe($event)">Click Me</button>
@@ -250,7 +250,7 @@ result is a non-undefined value (see value stabilization algorithm below).
</div>
</file>
<file name="script.js">
angular.module('oneTimeBidingExampleApp', []).
angular.module('oneTimeBindingExampleApp', []).
controller('EventController', ['$scope', function($scope) {
var counter = 0;
var names = ['Igor', 'Misko', 'Chirayu', 'Lucas'];
@@ -265,24 +265,24 @@ result is a non-undefined value (see value stabilization algorithm below).
</file>
<file name="protractor.js" type="protractor">
it('should freeze binding after its value has stabilized', function() {
var oneTimeBiding = element(by.id('one-time-binding-example'));
var oneTimeBinding = element(by.id('one-time-binding-example'));
var normalBinding = element(by.id('normal-binding-example'));
expect(oneTimeBiding.getText()).toEqual('One time binding:');
expect(oneTimeBinding.getText()).toEqual('One time binding:');
expect(normalBinding.getText()).toEqual('Normal binding:');
element(by.buttonText('Click Me')).click();
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
expect(oneTimeBinding.getText()).toEqual('One time binding: Igor');
expect(normalBinding.getText()).toEqual('Normal binding: Igor');
element(by.buttonText('Click Me')).click();
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
expect(oneTimeBinding.getText()).toEqual('One time binding: Igor');
expect(normalBinding.getText()).toEqual('Normal binding: Misko');
element(by.buttonText('Click Me')).click();
element(by.buttonText('Click Me')).click();
expect(oneTimeBiding.getText()).toEqual('One time binding: Igor');
expect(oneTimeBinding.getText()).toEqual('One time binding: Igor');
expect(normalBinding.getText()).toEqual('Normal binding: Lucas');
});
</file>
+2 -1
View File
@@ -119,7 +119,8 @@ You can find a larger list of Angular external libraries at [ngmodules.org](http
### Books
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
* [AngularJS Essentials (Free eBook)](https://www.packtpub.com/packt/free-ebook/angularjs-essentials) by Rodrigo Branas
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
* [AngularJS in Action](https://www.manning.com/books/angularjs-in-action) by Lukas Ruebbelke
* [AngularJS: Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
+2 -2
View File
@@ -28,8 +28,8 @@ for other directives to augment its behavior.
<form novalidate class="simple-form">
<label>Name: <input type="text" ng-model="user.name" /></label><br />
<label>E-mail: <input type="email" ng-model="user.email" /></label><br />
Gender: <label><input type="radio" ng-model="user.gender" value="male" />male</label>
<label><input type="radio" ng-model="user.gender" value="female" />female</label><br />
Best Editor: <label><input type="radio" ng-model="user.preference" value="vi" />vi</label>
<label><input type="radio" ng-model="user.preference" value="emacs" />emacs</label><br />
<input type="button" ng-click="reset()" value="Reset" />
<input type="submit" ng-click="update(user)" value="Save" />
</form>
+2 -2
View File
@@ -26,7 +26,7 @@ directive}. Additionally, you can use {@link guide/i18n#messageformat-extension
All localizable Angular components depend on locale-specific rule sets managed by the {@link
ng.$locale `$locale` service}.
There a few examples that showcase how to use Angular filters with various locale rule sets in the
There are a few examples that showcase how to use Angular filters with various locale rule sets in the
[`i18n/e2e` directory](https://github.com/angular/angular.js/tree/master/i18n/e2e) of the Angular
source code.
@@ -85,7 +85,7 @@ requires German locale, you would serve index_de-de.html which will look somethi
Both approaches described above require you to prepare different `index.html` pages or JavaScript
files for each locale that your app may use. You also need to configure your server to serve
the correct file that correspond to the desired locale.
the correct file that corresponds to the desired locale.
The second approach (including the locale JavaScript file in `index.html`) may be slower because
an extra script needs to be loaded.
+49 -5
View File
@@ -88,7 +88,9 @@ commits for more info.
- **jqLite** is more aligned to jQuery 3, which required the following changes
(see [details](guide/migration#migrate1.5to1.6-ng-misc-jqLite) below):
- Keys passed to `.data()` and `.css()` are now camelCased in the same as jQuery does it.
- Keys passed to `.data()` and `.css()` are now camelCased in the same way as the jQuery methods
do.
- Getting/setting boolean attributes no longer takes the corresponding properties into account.
- Setting boolean attributes to empty string no longer removes the attribute.
- Calling `.val()` on a multiple select will always return an array, even if no option is
selected.
@@ -416,7 +418,7 @@ will be removed in a future version, so we strongly recommend migrating your app
rely on it as soon as possible.
Initialization logic that relies on bindings being present should be put in the controller's
`$onInit()` method, which is guarranteed to always be called _after_ the bindings have been
`$onInit()` method, which is guaranteed to always be called _after_ the bindings have been
assigned.
Before:
@@ -501,7 +503,7 @@ running at `https://docs.angularjs.org` then the following will fail:
<link href="{{ 'http://mydomain.org/unsafe.css' }}" rel="stylesheet" />
```
By default, only URLs with the same domain and prototocl as the application document are considered
By default, only URLs with the same domain and protocol as the application document are considered
safe in the `RESOURCE_URL` context. To use URLs from other domains and/or protocols, you may either
whitelist them or wrap them into a trusted value by calling `$sce.trustAsResourceUrl(url)`.
@@ -880,6 +882,48 @@ var bgColor = elem.css('background-color');
var bgColor = elem.css('backgroundColor');
```
<hr />
<major />
**Due to [7ceb5f](https://github.com/angular/angular.js/commit/7ceb5f6fcc43d35d1b66c3151ce6a71c60309304)**,
getting/setting boolean attributes will no longer take the corresponding properties into account.
Previously, all boolean attributes were reflected into the corresponding property when calling a
setter and from the corresponding property when calling a getter, even on elements that don't treat
those attributes in a special way. Now Angular doesn't do it by itself, but relies on browsers to
know when to reflect the property. Note that this browser-level conversion differs between browsers;
if you need to dynamically change the state of an element, you should modify the property, not the
attribute. See https://jquery.com/upgrade-guide/1.9/#attr-versus-prop- for a more detailed
description about a related change in jQuery 1.9.
This change aligns jqLite with jQuery 3. To migrate the code follow the example below:
Before:
```css
/* CSS */
input[checked="checked"] { ... }
```
```js
// JS
elem1.attr('checked', 'checked');
elem2.attr('checked', false);
```
After:
```css
/* CSS */
input:checked { ... }
```
```js
// JS
elem1.prop('checked', true);
elem2.prop('checked', false);
```
<hr />
<major />
**Due to [3faf45](https://github.com/angular/angular.js/commit/3faf4505732758165083c9d21de71fa9b6983f4a)**,
@@ -1169,7 +1213,7 @@ with an unencoded `;` character.
Previously, in cases where `ngView` was loaded asynchronously, `$route` (and its dependencies) might
also have been instantiated asynchronously.
Although this is not expected to have unwanted side-effects in normal application bebavior, it may
Although this is not expected to have unwanted side-effects in normal application behavior, it may
affect your unit tests: When testing a module that (directly or indirectly) depends on `ngRoute`, a
request will be made for the default route's template. If not properly "trained", `$httpBackend`
will complain about this unexpected request. You can restore the previous behavior (and avoid
@@ -2605,7 +2649,7 @@ See [38deedd6](https://github.com/angular/angular.js/commit/38deedd6e3d806eb8262
### Interpolations inside DOM event handlers are now disallowed
DOM event handlers execute arbitrary Javascript code. Using an interpolation for such handlers
DOM event handlers execute arbitrary JavaScript code. Using an interpolation for such handlers
means that the interpolated value is a JS string that is evaluated. Storing or generating such
strings is error prone and leads to XSS vulnerabilities. On the other hand, `ngClick` and other
Angular specific event handlers evaluate Angular expressions in non-window (Scope) context which
+2 -2
View File
@@ -221,8 +221,8 @@ it('should clear messages after alert', function() {
notify('two');
notify('third');
expect(mock.alert.callCount).toEqual(2);
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
expect(mock.alert.calls.count()).toEqual(2);
expect(mock.alert.calls.mostRecent().args).toEqual(["more\ntwo\nthird"]);
});
```
+1 -1
View File
@@ -32,7 +32,7 @@ tutorials.
## Subscribe
* Subscribe to the [mailing list](http://groups.google.com/forum/?fromgroups#!forum/angular). Ask questions here!
* Follow us on [Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fangularjs.org%2F&region=follow_link&screen_name=angularjs&source=followbutton&variant=2.0)
* Follow us on [Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fangularjs.org%2F&region=follow_link&screen_name=angular&source=followbutton&variant=2.0)
* Add us to your circles on [Google+](https://plus.google.com/110323587230527980117/posts)
## Read more
+1 -1
View File
@@ -54,7 +54,7 @@ We will keep this in mind though, as we add more features.
So, now that we learned we should put everything in its own file, our `app/` directory will soon be
full with dozens of files and specs (remember we keep our unit test files next to the corresponding
source code files). What's more important, logically related files will not be grouped together; it
will be really difficult of locate all files related to a specific section of the application and
will be really difficult to locate all files related to a specific section of the application and
make a change or fix a bug.
So, what shall we do?
+1 -1
View File
@@ -194,7 +194,7 @@ angular.module('phonecatApp', [
```
Now, in addition to the core services and directives, we can also configure the `$route` service
(using it's provider) for our application. In order to be able to quickly locate the configuration
(using its provider) for our application. In order to be able to quickly locate the configuration
code, we put it into a separate file and used the `.config` suffix.
<br />
+4 -1
View File
@@ -173,7 +173,10 @@ angular.module('phoneList', ['core.phone']);
**`app/phone-detail/phone-detail.module.js`:**
```js
angular.module('phoneDetail', ['core.phone']);
angular.module('phoneDetail', [
'ngRoute',
'core.phone'
]);
```
<br />
+6 -21
View File
@@ -1,10 +1,8 @@
'use strict';
var gulp = require('gulp');
var log = require('gulp-util').log;
var concat = require('gulp-concat');
var eslint = require('gulp-eslint');
var bower = require('bower');
var Dgeni = require('dgeni');
var merge = require('event-stream').merge;
var path = require('canonical-path');
@@ -18,7 +16,6 @@ var rename = require('gulp-rename');
// See clean and bower for async tasks, and see assets and doc-gen for dependent tasks below
var outputFolder = '../build/docs';
var bowerFolder = 'bower_components';
var src = 'app/src/**/*.js';
var ignoredFiles = '!src/angular.bind.js';
@@ -57,8 +54,8 @@ var getMergedEslintConfig = function(filepath) {
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
pattern = pattern || '/**/*';
sourceFolder = sourceFolder || bowerFolder;
packageFile = packageFile || 'bower.json';
sourceFolder = sourceFolder || '../node_modules';
packageFile = packageFile || 'package.json';
var version = require(path.resolve(sourceFolder, component, packageFile)).version;
return gulp
.src(sourceFolder + '/' + component + pattern)
@@ -66,18 +63,6 @@ var copyComponent = function(component, pattern, sourceFolder, packageFile) {
};
gulp.task('bower', function() {
var bowerTask = bower.commands.install();
bowerTask.on('log', function(result) {
log('bower:', result.id, result.data.endpoint.name);
});
bowerTask.on('error', function(error) {
log(error);
});
return bowerTask;
});
gulp.task('build-app', function() {
var file = 'docs.js';
var minFile = 'docs.min.js';
@@ -94,7 +79,7 @@ gulp.task('build-app', function() {
});
gulp.task('assets', ['bower'], function() {
gulp.task('assets', function() {
var JS_EXT = /\.js$/;
return merge(
gulp.src(['img/**/*']).pipe(gulp.dest(outputFolder + '/img')),
@@ -113,15 +98,15 @@ gulp.task('assets', ['bower'], function() {
})),
copyComponent('bootstrap', '/dist/**/*'),
copyComponent('open-sans-fontface'),
copyComponent('lunr.js', '/*.js'),
copyComponent('lunr', '/*.js'),
copyComponent('google-code-prettify'),
copyComponent('jquery', '/dist/*.js'),
copyComponent('marked', '/**/*.js', '../node_modules', 'package.json')
copyComponent('marked', '/**/*.js')
);
});
gulp.task('doc-gen', ['bower'], function() {
gulp.task('doc-gen', function() {
var dgeni = new Dgeni([require('./config')]);
return dgeni.generate().catch(function() {
process.exit(1);
+9 -4
View File
@@ -1,7 +1,7 @@
{
"name": "angularjs",
"license": "MIT",
"branchVersion": "^1.5.8",
"branchVersion": "^1.6.0",
"branchPattern": "1.6.*",
"distTag": "latest",
"repository": {
@@ -10,7 +10,7 @@
},
"engines": {
"node": "^6.9.1",
"yarn": "^0.17.9",
"yarn": ">=0.17.9",
"grunt": "^1.2.0"
},
"scripts": {
@@ -22,19 +22,21 @@
"devDependencies": {
"angular-benchpress": "0.x.x",
"benchmark": "1.x.x",
"bootstrap": "3.1.1",
"bower": "~1.3.9",
"browserstacktunnel-wrapper": "^1.4.2",
"canonical-path": "0.0.2",
"changez": "^2.1.1",
"changez-angular": "^2.1.0",
"changez-angular": "^2.1.2",
"cheerio": "^0.17.0",
"commitizen": "^2.3.0",
"cross-spawn": "^4.0.0",
"cz-conventional-changelog": "1.1.4",
"dgeni": "^0.4.0",
"dgeni-packages": "^0.16.0",
"dgeni-packages": "^0.16.4",
"event-stream": "~3.1.0",
"glob": "^6.0.1",
"google-code-prettify": "1.0.1",
"grunt": "^1.0.1",
"grunt-bump": "^0.8.0",
"grunt-cli": "^1.2.0",
@@ -57,6 +59,7 @@
"jasmine-core": "^2.4.0",
"jasmine-node": "^2.0.0",
"jasmine-reporters": "^2.2.0",
"jquery": "^3.1.1",
"karma": "^1.1.2",
"karma-browserstack-launcher": "^1.0.1",
"karma-chrome-launcher": "^1.0.1",
@@ -69,9 +72,11 @@
"load-grunt-tasks": "^3.5.0",
"lodash": "~2.4.1",
"log4js": "^0.6.27",
"lunr": "^0.7.2",
"marked": "~0.3.0",
"node-html-encoder": "0.0.2",
"npm-run": "^4.1.0",
"open-sans-fontface": "^1.4.0",
"promises-aplus-tests": "~2.1.0",
"protractor": "^4.0.10",
"q": "~1.0.0",
+1 -1
View File
@@ -150,7 +150,7 @@
/* apis.js */
"hashKey": false,
"HashMap": false,
"NgMap": false,
/* urlUtils.js */
"urlResolve": false,
+7 -3
View File
@@ -1479,12 +1479,16 @@ function getNgAttribute(element, ngAttr) {
}
function allowAutoBootstrap(document) {
if (!document.currentScript) {
var script = document.currentScript;
var src = script && script.getAttribute('src');
if (!src) {
return true;
}
var src = document.currentScript.getAttribute('src');
var link = document.createElement('a');
link.href = src;
if (document.location.origin === link.origin) {
// Same-origin resources are always allowed, even for non-whitelisted schemes.
return true;
@@ -1866,7 +1870,7 @@ function bindJQuery() {
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
isolateScope: JQLitePrototype.isolateScope,
controller: JQLitePrototype.controller,
controller: /** @type {?} */ (JQLitePrototype).controller,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});
+2 -2
View File
@@ -70,7 +70,6 @@
$$ForceReflowProvider,
$InterpolateProvider,
$IntervalProvider,
$$HashMapProvider,
$HttpProvider,
$HttpParamSerializerProvider,
$HttpParamSerializerJQLikeProvider,
@@ -79,6 +78,7 @@
$jsonpCallbacksProvider,
$LocationProvider,
$LogProvider,
$$MapProvider,
$ParseProvider,
$RootScopeProvider,
$QProvider,
@@ -260,7 +260,7 @@ function publishExternalAPI(angular) {
$window: $WindowProvider,
$$rAF: $$RAFProvider,
$$jqLite: $$jqLiteProvider,
$$HashMap: $$HashMapProvider,
$$Map: $$MapProvider,
$$cookieReader: $$CookieReaderProvider
});
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2016 Google, Inc. http://angularjs.org
* (c) 2010-2017 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window) {
+55 -36
View File
@@ -1,6 +1,5 @@
'use strict';
/**
* Computes a hash of an 'obj'.
* Hash of a:
@@ -33,49 +32,69 @@ function hashKey(obj, nextUidFn) {
return key;
}
/**
* HashMap which can use objects as keys
*/
function HashMap(array, isolatedUid) {
if (isolatedUid) {
var uid = 0;
this.nextUid = function() {
return ++uid;
};
}
forEach(array, this.put, this);
// A minimal ES2015 Map implementation.
// Should be bug/feature equivalent to the native implementations of supported browsers
// (for the features required in Angular).
// See https://kangax.github.io/compat-table/es6/#test-Map
var nanKey = Object.create(null);
function NgMapShim() {
this._keys = [];
this._values = [];
this._lastKey = NaN;
this._lastIndex = -1;
}
HashMap.prototype = {
/**
* Store key value pair
* @param key key to store can be any type
* @param value value to store can be any type
*/
put: function(key, value) {
this[hashKey(key, this.nextUid)] = value;
NgMapShim.prototype = {
_idx: function(key) {
if (key === this._lastKey) {
return this._lastIndex;
}
this._lastKey = key;
this._lastIndex = this._keys.indexOf(key);
return this._lastIndex;
},
_transformKey: function(key) {
return isNumberNaN(key) ? nanKey : key;
},
/**
* @param key
* @returns {Object} the value for the key
*/
get: function(key) {
return this[hashKey(key, this.nextUid)];
key = this._transformKey(key);
var idx = this._idx(key);
if (idx !== -1) {
return this._values[idx];
}
},
set: function(key, value) {
key = this._transformKey(key);
var idx = this._idx(key);
if (idx === -1) {
idx = this._lastIndex = this._keys.length;
}
this._keys[idx] = key;
this._values[idx] = value;
/**
* Remove the key/value pair
* @param key
*/
remove: function(key) {
var value = this[key = hashKey(key, this.nextUid)];
delete this[key];
return value;
// Support: IE11
// Do not `return this` to simulate the partial IE11 implementation
},
delete: function(key) {
key = this._transformKey(key);
var idx = this._idx(key);
if (idx === -1) {
return false;
}
this._keys.splice(idx, 1);
this._values.splice(idx, 1);
this._lastKey = NaN;
this._lastIndex = -1;
return true;
}
};
var $$HashMapProvider = [/** @this */function() {
// For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations
// are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map`
// implementations get more stable, we can reconsider switching to `window.Map` (when available).
var NgMap = NgMapShim;
var $$MapProvider = [/** @this */function() {
this.$get = [function() {
return HashMap;
return NgMap;
}];
}];
+3 -7
View File
@@ -71,11 +71,7 @@ var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');
function stringifyFn(fn) {
// Support: Chrome 50-51 only
// Creating a new string by adding `' '` at the end, to hack around some bug in Chrome v50/51
// (See https://github.com/angular/angular.js/issues/14487.)
// TODO (gkalpak): Remove workaround when Chrome v52 is released
return Function.prototype.toString.call(fn) + ' ';
return Function.prototype.toString.call(fn);
}
function extractArgs(fn) {
@@ -649,7 +645,7 @@ function createInjector(modulesToLoad, strictDi) {
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap([], true),
loadedModules = new NgMap(),
providerCache = {
$provide: {
provider: supportObject(provider),
@@ -757,7 +753,7 @@ function createInjector(modulesToLoad, strictDi) {
var runBlocks = [], moduleFn;
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
loadedModules.put(module, true);
loadedModules.set(module, true);
function runInvokeQueue(queue) {
var i, ii;
+8 -5
View File
@@ -979,12 +979,15 @@ forEach({
after: function(element, newElement) {
var index = element, parent = element.parentNode;
newElement = new JQLite(newElement);
for (var i = 0, ii = newElement.length; i < ii; i++) {
var node = newElement[i];
parent.insertBefore(node, index.nextSibling);
index = node;
if (parent) {
newElement = new JQLite(newElement);
for (var i = 0, ii = newElement.length; i < ii; i++) {
var node = newElement[i];
parent.insertBefore(node, index.nextSibling);
index = node;
}
}
},
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2016 Google, Inc. http://angularjs.org
* (c) 2010-2017 Google, Inc. http://angularjs.org
* License: MIT
*/
'use strict';
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2016 Google, Inc. http://angularjs.org
* (c) 2010-2017 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular) {
+3 -3
View File
@@ -60,7 +60,7 @@ var $$CoreAnimateJsProvider = /** @this */ function() {
// this is prefixed with Core since it conflicts with
// the animateQueueProvider defined in ngAnimate/animateQueue.js
var $$CoreAnimateQueueProvider = /** @this */ function() {
var postDigestQueue = new HashMap();
var postDigestQueue = new NgMap();
var postDigestElements = [];
this.$get = ['$$AnimateRunner', '$rootScope',
@@ -139,7 +139,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() {
jqLiteRemoveClass(elm, toRemove);
}
});
postDigestQueue.remove(element);
postDigestQueue.delete(element);
}
});
postDigestElements.length = 0;
@@ -154,7 +154,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() {
if (classesAdded || classesRemoved) {
postDigestQueue.put(element, data);
postDigestQueue.set(element, data);
postDigestElements.push(element);
if (postDigestElements.length === 1) {
+9 -8
View File
@@ -96,7 +96,6 @@ function Browser(window, document, $log, $sniffer) {
};
cacheState();
lastHistoryState = cachedState;
/**
* @name $browser#url
@@ -150,8 +149,6 @@ function Browser(window, document, $log, $sniffer) {
if ($sniffer.history && (!sameBase || !sameState)) {
history[replace ? 'replaceState' : 'pushState'](state, '', url);
cacheState();
// Do the assignment again so that those two variables are referentially identical.
lastHistoryState = cachedState;
} else {
if (!sameBase) {
pendingLocation = url;
@@ -200,8 +197,7 @@ function Browser(window, document, $log, $sniffer) {
function cacheStateAndFireUrlChange() {
pendingLocation = null;
cacheState();
fireUrlChange();
fireStateOrUrlChange();
}
// This variable should be used *only* inside the cacheState function.
@@ -215,11 +211,16 @@ function Browser(window, document, $log, $sniffer) {
if (equals(cachedState, lastCachedState)) {
cachedState = lastCachedState;
}
lastCachedState = cachedState;
lastHistoryState = cachedState;
}
function fireUrlChange() {
if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) {
function fireStateOrUrlChange() {
var prevLastHistoryState = lastHistoryState;
cacheState();
if (lastBrowserUrl === self.url() && prevLastHistoryState === cachedState) {
return;
}
@@ -285,7 +286,7 @@ function Browser(window, document, $log, $sniffer) {
* Needs to be exported to be able to check for changes that have been done in sync,
* as hashchange/popstate events fire in async.
*/
self.$$checkUrlChange = fireUrlChange;
self.$$checkUrlChange = fireStateOrUrlChange;
//////////////////////////////////////////////////////////////
// Misc API
+4 -3
View File
@@ -128,7 +128,8 @@
* * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
* `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
* object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
* component such as cloning the bound value to prevent accidental mutation of the outer value.
* component such as cloning the bound value to prevent accidental mutation of the outer value. Note that this will
* also be called when your bindings are initialized.
* * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on
* changes. Any actions that you wish to take in response to the changes that you detect must be
* invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook
@@ -983,7 +984,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var bindingCache = createMap();
function parseIsolateBindings(scope, directiveName, isController) {
var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/;
var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*([\w$]*)\s*$/;
var bindings = createMap();
@@ -3155,7 +3156,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (error instanceof Error) {
$exceptionHandler(error);
}
}).catch(noop);
});
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
var childBoundTranscludeFn = boundTranscludeFn;
+9 -1
View File
@@ -14,6 +14,14 @@ function $$CookieReader($document) {
var lastCookies = {};
var lastCookieString = '';
function safeGetCookie(rawDocument) {
try {
return rawDocument.cookie || '';
} catch (e) {
return '';
}
}
function safeDecodeURIComponent(str) {
try {
return decodeURIComponent(str);
@@ -24,7 +32,7 @@ function $$CookieReader($document) {
return function() {
var cookieArray, cookie, i, index, name;
var currentCookieString = rawDocument.cookie || '';
var currentCookieString = safeGetCookie(rawDocument);
if (currentCookieString !== lastCookieString) {
lastCookieString = currentCookieString;
+2 -1
View File
@@ -159,7 +159,8 @@
*
* @description
*
* This directive sets the `disabled` attribute on the element if the
* This directive sets the `disabled` attribute on the element (typically a form control,
* e.g. `input`, `button`, `select` etc.) if the
* {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
*
* A special directive is necessary because we cannot use interpolation inside the `disabled`
+18 -3
View File
@@ -1565,15 +1565,27 @@ function isValidForStep(viewValue, stepBase, step) {
// and `viewValue` is expected to be a valid stringified number.
var value = Number(viewValue);
var isNonIntegerValue = !isNumberInteger(value);
var isNonIntegerStepBase = !isNumberInteger(stepBase);
var isNonIntegerStep = !isNumberInteger(step);
// Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or
// `0.5 % 0.1 !== 0`), we need to convert all numbers to integers.
if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) {
var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step));
if (isNonIntegerValue || isNonIntegerStepBase || isNonIntegerStep) {
var valueDecimals = isNonIntegerValue ? countDecimals(value) : 0;
var stepBaseDecimals = isNonIntegerStepBase ? countDecimals(stepBase) : 0;
var stepDecimals = isNonIntegerStep ? countDecimals(step) : 0;
var decimalCount = Math.max(valueDecimals, stepBaseDecimals, stepDecimals);
var multiplier = Math.pow(10, decimalCount);
value = value * multiplier;
stepBase = stepBase * multiplier;
step = step * multiplier;
if (isNonIntegerValue) value = Math.round(value);
if (isNonIntegerStepBase) stepBase = Math.round(stepBase);
if (isNonIntegerStep) step = Math.round(step);
}
return (value - stepBase) % step === 0;
@@ -2130,7 +2142,10 @@ var ngValueDirective = function() {
* makes it possible to use ngValue as a sort of one-way bind.
*/
function updateElementValue(element, attr, value) {
element.prop('value', value);
// Support: IE9 only
// In IE9 values are converted to string (e.g. `input.value = null` results in `input.value === 'null'`).
var propValue = isDefined(value) ? value : (msie === 9) ? '' : null;
element.prop('value', propValue);
attr.$set('value', value);
}
+144 -95
View File
@@ -8,51 +8,71 @@
function classDirective(name, selector) {
name = 'ngClass' + name;
return ['$animate', function($animate) {
var indexWatchExpression;
return ['$parse', function($parse) {
return {
restrict: 'AC',
link: function(scope, element, attr) {
var oldVal;
var expression = attr[name].trim();
var isOneTime = (expression.charAt(0) === ':') && (expression.charAt(1) === ':');
scope.$watch(attr[name], ngClassWatchAction, true);
var watchInterceptor = isOneTime ? toFlatValue : toClassString;
var watchExpression = $parse(expression, watchInterceptor);
var watchAction = isOneTime ? ngClassOneTimeWatchAction : ngClassWatchAction;
attr.$observe('class', function(value) {
ngClassWatchAction(scope.$eval(attr[name]));
});
var classCounts = element.data('$classCounts');
var oldModulo = true;
var oldClassString;
if (name !== 'ngClass') {
scope.$watch('$index', function($index, old$index) {
/* eslint-disable no-bitwise */
var mod = $index & 1;
if (mod !== (old$index & 1)) {
var classes = arrayClasses(scope.$eval(attr[name]));
if (mod === selector) {
addClasses(classes);
} else {
removeClasses(classes);
}
}
/* eslint-enable */
});
}
function addClasses(classes) {
var newClasses = digestClassCounts(classes, 1);
attr.$addClass(newClasses);
}
function removeClasses(classes) {
var newClasses = digestClassCounts(classes, -1);
attr.$removeClass(newClasses);
}
function digestClassCounts(classes, count) {
if (!classCounts) {
// Use createMap() to prevent class assumptions involving property
// names in Object.prototype
var classCounts = element.data('$classCounts') || createMap();
classCounts = createMap();
element.data('$classCounts', classCounts);
}
if (name !== 'ngClass') {
if (!indexWatchExpression) {
indexWatchExpression = $parse('$index', function moduloTwo($index) {
// eslint-disable-next-line no-bitwise
return $index & 1;
});
}
scope.$watch(indexWatchExpression, ngClassIndexWatchAction);
}
scope.$watch(watchExpression, watchAction, isOneTime);
function addClasses(classString) {
classString = digestClassCounts(split(classString), 1);
attr.$addClass(classString);
}
function removeClasses(classString) {
classString = digestClassCounts(split(classString), -1);
attr.$removeClass(classString);
}
function updateClasses(oldClassString, newClassString) {
var oldClassArray = split(oldClassString);
var newClassArray = split(newClassString);
var toRemoveArray = arrayDifference(oldClassArray, newClassArray);
var toAddArray = arrayDifference(newClassArray, oldClassArray);
var toRemoveString = digestClassCounts(toRemoveArray, -1);
var toAddString = digestClassCounts(toAddArray, 1);
attr.$addClass(toAddString);
attr.$removeClass(toRemoveString);
}
function digestClassCounts(classArray, count) {
var classesToUpdate = [];
forEach(classes, function(className) {
forEach(classArray, function(className) {
if (count > 0 || classCounts[className]) {
classCounts[className] = (classCounts[className] || 0) + count;
if (classCounts[className] === +(count > 0)) {
@@ -60,77 +80,106 @@ function classDirective(name, selector) {
}
}
});
element.data('$classCounts', classCounts);
return classesToUpdate.join(' ');
}
function updateClasses(oldClasses, newClasses) {
var toAdd = arrayDifference(newClasses, oldClasses);
var toRemove = arrayDifference(oldClasses, newClasses);
toAdd = digestClassCounts(toAdd, 1);
toRemove = digestClassCounts(toRemove, -1);
if (toAdd && toAdd.length) {
$animate.addClass(element, toAdd);
function ngClassIndexWatchAction(newModulo) {
// This watch-action should run before the `ngClass[OneTime]WatchAction()`, thus it
// adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the
// `ngClass[OneTime]WatchAction()` will update the classes.
if (newModulo === selector) {
addClasses(oldClassString);
} else {
removeClasses(oldClassString);
}
if (toRemove && toRemove.length) {
$animate.removeClass(element, toRemove);
oldModulo = newModulo;
}
function ngClassOneTimeWatchAction(newClassValue) {
var newClassString = toClassString(newClassValue);
if (newClassString !== oldClassString) {
ngClassWatchAction(newClassString);
}
}
function ngClassWatchAction(newVal) {
// eslint-disable-next-line no-bitwise
if (selector === true || (scope.$index & 1) === selector) {
var newClasses = arrayClasses(newVal || []);
if (!oldVal) {
addClasses(newClasses);
} else if (!equals(newVal,oldVal)) {
var oldClasses = arrayClasses(oldVal);
updateClasses(oldClasses, newClasses);
}
}
if (isArray(newVal)) {
oldVal = newVal.map(function(v) { return shallowCopy(v); });
} else {
oldVal = shallowCopy(newVal);
function ngClassWatchAction(newClassString) {
if (oldModulo === selector) {
updateClasses(oldClassString, newClassString);
}
oldClassString = newClassString;
}
}
};
function arrayDifference(tokens1, tokens2) {
var values = [];
outer:
for (var i = 0; i < tokens1.length; i++) {
var token = tokens1[i];
for (var j = 0; j < tokens2.length; j++) {
if (token === tokens2[j]) continue outer;
}
values.push(token);
}
return values;
}
function arrayClasses(classVal) {
var classes = [];
if (isArray(classVal)) {
forEach(classVal, function(v) {
classes = classes.concat(arrayClasses(v));
});
return classes;
} else if (isString(classVal)) {
return classVal.split(' ');
} else if (isObject(classVal)) {
forEach(classVal, function(v, k) {
if (v) {
classes = classes.concat(k.split(' '));
}
});
return classes;
}
return classVal;
}
}];
// Helpers
function arrayDifference(tokens1, tokens2) {
if (!tokens1 || !tokens1.length) return [];
if (!tokens2 || !tokens2.length) return tokens1;
var values = [];
outer:
for (var i = 0; i < tokens1.length; i++) {
var token = tokens1[i];
for (var j = 0; j < tokens2.length; j++) {
if (token === tokens2[j]) continue outer;
}
values.push(token);
}
return values;
}
function split(classString) {
return classString && classString.split(' ');
}
function toClassString(classValue) {
var classString = classValue;
if (isArray(classValue)) {
classString = classValue.map(toClassString).join(' ');
} else if (isObject(classValue)) {
classString = Object.keys(classValue).
filter(function(key) { return classValue[key]; }).
join(' ');
}
return classString;
}
function toFlatValue(classValue) {
var flatValue = classValue;
if (isArray(classValue)) {
flatValue = classValue.map(toFlatValue);
} else if (isObject(classValue)) {
var hasUndefined = false;
flatValue = Object.keys(classValue).filter(function(key) {
var value = classValue[key];
if (!hasUndefined && isUndefined(value)) {
hasUndefined = true;
}
return value;
});
if (hasUndefined) {
// Prevent the `oneTimeLiteralWatchInterceptor` from unregistering
// the watcher, by including at least one `undefined` value.
flatValue.push(undefined);
}
}
return flatValue;
}
}
/**
+6 -6
View File
@@ -53,15 +53,15 @@ forEach(
return {
restrict: 'A',
compile: function($element, attr) {
// 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);
// NOTE:
// We expose the powerful `$event` object on the scope that provides access to the Window,
// etc. This is OK, because expressions are not sandboxed any more (and the expression
// sandbox was never meant to be a security feature anyway).
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
fn(scope, {$event:event});
fn(scope, {$event: event});
};
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
scope.$evalAsync(callback);
+23
View File
@@ -829,6 +829,29 @@ NgModelController.prototype = {
that.$commitViewValue();
});
}
},
/**
* @ngdoc method
*
* @name ngModel.NgModelController#$overrideModelOptions
*
* @description
*
* Override the current model options settings programmatically.
*
* The previous `ModelOptions` value will not be modified. Instead, a
* new `ModelOptions` object will inherit from the previous one overriding
* or inheriting settings that are defined in the given parameter.
*
* See {@link ngModelOptions} for information about what options can be specified
* and how model option inheritance works.
*
* @param {Object} options a hash of settings to override the previous options
*
*/
$overrideModelOptions: function(options) {
this.$options = this.$options.createChild(options);
}
};
+17 -9
View File
@@ -331,19 +331,27 @@ defaultModelOptions = new ModelOptions({
*
*/
var ngModelOptionsDirective = function() {
NgModelOptionsController.$inject = ['$attrs', '$scope'];
function NgModelOptionsController($attrs, $scope) {
this.$$attrs = $attrs;
this.$$scope = $scope;
}
NgModelOptionsController.prototype = {
$onInit: function() {
var parentOptions = this.parentCtrl ? this.parentCtrl.$options : defaultModelOptions;
var modelOptionsDefinition = this.$$scope.$eval(this.$$attrs.ngModelOptions);
this.$options = parentOptions.createChild(modelOptionsDefinition);
}
};
return {
restrict: 'A',
// ngModelOptions needs to run before ngModel and input directives
priority: 10,
require: ['ngModelOptions', '?^^ngModelOptions'],
controller: function NgModelOptionsController() {},
link: {
pre: function ngModelOptionsPreLinkFn(scope, element, attrs, ctrls) {
var optionsCtrl = ctrls[0];
var parentOptions = ctrls[1] ? ctrls[1].$options : defaultModelOptions;
optionsCtrl.$options = parentOptions.createChild(scope.$eval(attrs.ngModelOptions));
}
}
require: {parentCtrl: '?^^ngModelOptions'},
bindToController: true,
controller: NgModelOptionsController
};
};
+18 -10
View File
@@ -505,17 +505,17 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
} else {
selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
options.items.forEach(function(option) {
option.element.selected = false;
});
selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
// Only set `<option>.selected` if necessary, in order to prevent some browsers from
// scrolling to `<option>` elements that are outside the `<select>` element's viewport.
if (value) {
value.forEach(function(item) {
var option = options.getOptionFromViewValue(item);
if (option) option.element.selected = true;
});
}
var selectedOptions = values && values.map(getAndUpdateSelectedOption) || [];
options.items.forEach(function(option) {
if (option.element.selected && !includes(selectedOptions, option)) {
option.element.selected = false;
}
});
};
@@ -605,6 +605,14 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
updateOptionElement(option, optionElement);
}
function getAndUpdateSelectedOption(viewValue) {
var option = options.getOptionFromViewValue(viewValue);
var element = option && option.element;
if (element && !element.selected) element.selected = true;
return option;
}
function updateOptionElement(option, element) {
option.element = element;
+224 -159
View File
@@ -8,11 +8,13 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
* @multiElement
*
* @description
* The `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. The `.ng-hide` CSS class is predefined
* in AngularJS and sets the display style to none (using an !important flag).
* For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
* The `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.
* The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
* `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
* {@link ng.directive:ngCsp ngCsp}).
*
* ```html
* <!-- when $scope.myValue is truthy (element is visible) -->
@@ -22,31 +24,32 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
* <div ng-show="myValue" class="ng-hide"></div>
* ```
*
* When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class
* attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed
* from the element causing the element not to appear hidden.
* When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added
* to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide`
* CSS class is removed from the element causing the element not to appear hidden.
*
* ## Why is !important used?
* ## Why is `!important` used?
*
* You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
* can be easily overridden by heavier selectors. For example, something as simple
* as changing the display style on a HTML list item would make hidden elements appear visible.
* This also becomes a bigger issue when dealing with CSS frameworks.
* You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
* `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
* simple as changing the display style on a HTML list item would make hidden elements appear
* visible. This also becomes a bigger issue when dealing with CSS frameworks.
*
* By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
* specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
* styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
* By using `!important`, the show and hide behavior will work as expected despite any clash between
* CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
* developer chooses to override the styling to change how to hide an element then it is just a
* matter of using `!important` in their own CSS code.
*
* ### Overriding `.ng-hide`
*
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
* with extra animation classes that can be added.
* By default, the `.ng-hide` class will style the element with `display: none !important`. If you
* wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
* the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
* `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
*
* ```css
* .ng-hide:not(.ng-hide-animate) {
* /&#42; this is just another form of hiding an element &#42;/
* /&#42; These are just alternative ways of hiding an element &#42;/
* display: block!important;
* position: absolute;
* top: -9999px;
@@ -54,29 +57,20 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
* }
* ```
*
* By default you don't need to override in CSS anything and the animations will work around the display style.
* By default you don't need to override anything in CSS and the animations will work around the
* display style.
*
* ## A note about animations with `ngShow`
*
* Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
* is true and false. This system works like the animation system present with ngClass except that
* you must also include the !important flag to override the display property
* so that you can perform an animation when the element is hidden during the time of the animation.
* Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
* directive expression is true and false. This system works like the animation system present with
* `ngClass` except that you must also include the `!important` flag to override the display
* property so that the elements are not actually hidden during the animation.
*
* ```css
* //
* //a working example can be found at the bottom of this page
* //
* /&#42; A working example can be found at the bottom of this page. &#42;/
* .my-element.ng-hide-add, .my-element.ng-hide-remove {
* /&#42; this is required as of 1.3x to properly
* apply all styling in a show/hide animation &#42;/
* transition: 0s linear all;
* }
*
* .my-element.ng-hide-add-active,
* .my-element.ng-hide-remove-active {
* /&#42; the transition is defined in the active class &#42;/
* transition: 1s linear all;
* transition: all 0.5s linear;
* }
*
* .my-element.ng-hide-add { ... }
@@ -85,76 +79,108 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
* .my-element.ng-hide-remove.ng-hide-remove-active { ... }
* ```
*
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display
* property to block during animation states--ngAnimate will handle the style toggling automatically for you.
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
* to block during animation states - ngAnimate will automatically handle the style toggling for you.
*
* @animations
* | Animation | Occurs |
* |----------------------------------|-------------------------------------|
* | {@link $animate#addClass addClass} `.ng-hide` | after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden |
* | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngShow` expression evaluates to a truthy value and just before contents are set to visible |
* | Animation | Occurs |
* |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
* | {@link $animate#addClass addClass} `.ng-hide` | After the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden. |
* | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible. |
*
* @element ANY
* @param {expression} ngShow If the {@link guide/expression expression} is truthy
* then the element is shown or hidden respectively.
* @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the
* element is shown/hidden respectively.
*
* @example
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show">
* A simple example, animating the element's opacity:
*
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-simple">
<file name="index.html">
Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
<div>
Show:
<div class="check-element animate-show" ng-show="checked">
<span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
</div>
Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
<div class="check-element animate-show-hide" ng-show="checked">
I show up when your checkbox is checked.
</div>
<div>
Hide:
<div class="check-element animate-show" ng-hide="checked">
<span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
</div>
</div>
</file>
<file name="glyphicons.css">
@import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
</file>
<file name="animations.css">
.animate-show {
line-height: 20px;
opacity: 1;
padding: 10px;
border: 1px solid black;
background: white;
.animate-show-hide.ng-hide {
opacity: 0;
}
.animate-show.ng-hide-add, .animate-show.ng-hide-remove {
.animate-show-hide.ng-hide-add,
.animate-show-hide.ng-hide-remove {
transition: all linear 0.5s;
}
.animate-show.ng-hide {
line-height: 0;
opacity: 0;
padding: 0 10px;
}
.check-element {
padding: 10px;
border: 1px solid black;
background: white;
opacity: 1;
padding: 10px;
}
</file>
<file name="protractor.js" type="protractor">
var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
it('should check ngShow', function() {
var checkbox = element(by.model('checked'));
var checkElem = element(by.css('.check-element'));
it('should check ng-show / ng-hide', function() {
expect(thumbsUp.isDisplayed()).toBeFalsy();
expect(thumbsDown.isDisplayed()).toBeTruthy();
expect(checkElem.isDisplayed()).toBe(false);
checkbox.click();
expect(checkElem.isDisplayed()).toBe(true);
});
</file>
</example>
*
* <hr />
* @example
* A more complex example, featuring different show/hide animations:
*
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-show-complex">
<file name="index.html">
Show: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br />
<div class="check-element funky-show-hide" ng-show="checked">
I show up when your checkbox is checked.
</div>
</file>
<file name="animations.css">
body {
overflow: hidden;
perspective: 1000px;
}
element(by.model('checked')).click();
.funky-show-hide.ng-hide-add {
transform: rotateZ(0);
transform-origin: right;
transition: all 0.5s ease-in-out;
}
expect(thumbsUp.isDisplayed()).toBeTruthy();
expect(thumbsDown.isDisplayed()).toBeFalsy();
.funky-show-hide.ng-hide-add.ng-hide-add-active {
transform: rotateZ(-135deg);
}
.funky-show-hide.ng-hide-remove {
transform: rotateY(90deg);
transform-origin: left;
transition: all 0.5s ease;
}
.funky-show-hide.ng-hide-remove.ng-hide-remove-active {
transform: rotateY(0);
}
.check-element {
border: 1px solid black;
opacity: 1;
padding: 10px;
}
</file>
<file name="protractor.js" type="protractor">
it('should check ngShow', function() {
var checkbox = element(by.model('checked'));
var checkElem = element(by.css('.check-element'));
expect(checkElem.isDisplayed()).toBe(false);
checkbox.click();
expect(checkElem.isDisplayed()).toBe(true);
});
</file>
</example>
@@ -184,11 +210,13 @@ var ngShowDirective = ['$animate', function($animate) {
* @multiElement
*
* @description
* The `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 `.ng-hide` CSS class is predefined
* in AngularJS and sets the display style to none (using an !important flag).
* For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
* The `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 `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an
* `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see
* {@link ng.directive:ngCsp ngCsp}).
*
* ```html
* <!-- when $scope.myValue is truthy (element is hidden) -->
@@ -198,30 +226,32 @@ var ngShowDirective = ['$animate', function($animate) {
* <div ng-hide="myValue"></div>
* ```
*
* When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class
* attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed
* from the element causing the element not to appear hidden.
* When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added
* to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide`
* CSS class is removed from the element causing the element not to appear hidden.
*
* ## Why is !important used?
* ## Why is `!important` used?
*
* You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector
* can be easily overridden by heavier selectors. For example, something as simple
* as changing the display style on a HTML list item would make hidden elements appear visible.
* This also becomes a bigger issue when dealing with CSS frameworks.
* You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the
* `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as
* simple as changing the display style on a HTML list item would make hidden elements appear
* visible. This also becomes a bigger issue when dealing with CSS frameworks.
*
* By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
* specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
* styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
* By using `!important`, the show and hide behavior will work as expected despite any clash between
* CSS selector specificity (when `!important` isn't used with any conflicting styles). If a
* developer chooses to override the styling to change how to hide an element then it is just a
* matter of using `!important` in their own CSS code.
*
* ### Overriding `.ng-hide`
*
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class in CSS:
* By default, the `.ng-hide` class will style the element with `display: none !important`. If you
* wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for
* the `.ng-hide` CSS class. Note that the selector that needs to be used is actually
* `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added.
*
* ```css
* .ng-hide {
* /&#42; this is just another form of hiding an element &#42;/
* .ng-hide:not(.ng-hide-animate) {
* /&#42; These are just alternative ways of hiding an element &#42;/
* display: block!important;
* position: absolute;
* top: -9999px;
@@ -229,20 +259,20 @@ var ngShowDirective = ['$animate', function($animate) {
* }
* ```
*
* By default you don't need to override in CSS anything and the animations will work around the display style.
* By default you don't need to override in CSS anything and the animations will work around the
* display style.
*
* ## A note about animations with `ngHide`
*
* Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression
* is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide`
* CSS class is added and removed for you instead of your own CSS class.
* Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the
* directive expression is true and false. This system works like the animation system present with
* `ngClass` except that you must also include the `!important` flag to override the display
* property so that the elements are not actually hidden during the animation.
*
* ```css
* //
* //a working example can be found at the bottom of this page
* //
* /&#42; A working example can be found at the bottom of this page. &#42;/
* .my-element.ng-hide-add, .my-element.ng-hide-remove {
* transition: 0.5s linear all;
* transition: all 0.5s linear;
* }
*
* .my-element.ng-hide-add { ... }
@@ -251,74 +281,109 @@ var ngShowDirective = ['$animate', function($animate) {
* .my-element.ng-hide-remove.ng-hide-remove-active { ... }
* ```
*
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display
* property to block during animation states--ngAnimate will handle the style toggling automatically for you.
* Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property
* to block during animation states - ngAnimate will automatically handle the style toggling for you.
*
* @animations
* | Animation | Occurs |
* |----------------------------------|-------------------------------------|
* | {@link $animate#addClass addClass} `.ng-hide` | after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden |
* | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible |
* | Animation | Occurs |
* |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------|
* | {@link $animate#addClass addClass} `.ng-hide` | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden. |
* | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. |
*
*
* @element ANY
* @param {expression} ngHide If the {@link guide/expression expression} is truthy then
* the element is shown or hidden respectively.
* @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the
* element is hidden/shown respectively.
*
* @example
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide">
* A simple example, animating the element's opacity:
*
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-simple">
<file name="index.html">
Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/>
<div>
Show:
<div class="check-element animate-hide" ng-show="checked">
<span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked.
</div>
Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
<div class="check-element animate-show-hide" ng-hide="checked">
I hide when your checkbox is checked.
</div>
<div>
Hide:
<div class="check-element animate-hide" ng-hide="checked">
<span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked.
</div>
</div>
</file>
<file name="glyphicons.css">
@import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
</file>
<file name="animations.css">
.animate-hide {
transition: all linear 0.5s;
line-height: 20px;
opacity: 1;
padding: 10px;
border: 1px solid black;
background: white;
.animate-show-hide.ng-hide {
opacity: 0;
}
.animate-hide.ng-hide {
line-height: 0;
opacity: 0;
padding: 0 10px;
.animate-show-hide.ng-hide-add,
.animate-show-hide.ng-hide-remove {
transition: all linear 0.5s;
}
.check-element {
padding: 10px;
border: 1px solid black;
background: white;
opacity: 1;
padding: 10px;
}
</file>
<file name="protractor.js" type="protractor">
var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
it('should check ngHide', function() {
var checkbox = element(by.model('checked'));
var checkElem = element(by.css('.check-element'));
it('should check ng-show / ng-hide', function() {
expect(thumbsUp.isDisplayed()).toBeFalsy();
expect(thumbsDown.isDisplayed()).toBeTruthy();
expect(checkElem.isDisplayed()).toBe(true);
checkbox.click();
expect(checkElem.isDisplayed()).toBe(false);
});
</file>
</example>
*
* <hr />
* @example
* A more complex example, featuring different show/hide animations:
*
<example module="ngAnimate" deps="angular-animate.js" animations="true" name="ng-hide-complex">
<file name="index.html">
Hide: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br />
<div class="check-element funky-show-hide" ng-hide="checked">
I hide when your checkbox is checked.
</div>
</file>
<file name="animations.css">
body {
overflow: hidden;
perspective: 1000px;
}
element(by.model('checked')).click();
.funky-show-hide.ng-hide-add {
transform: rotateZ(0);
transform-origin: right;
transition: all 0.5s ease-in-out;
}
expect(thumbsUp.isDisplayed()).toBeTruthy();
expect(thumbsDown.isDisplayed()).toBeFalsy();
.funky-show-hide.ng-hide-add.ng-hide-add-active {
transform: rotateZ(-135deg);
}
.funky-show-hide.ng-hide-remove {
transform: rotateY(90deg);
transform-origin: left;
transition: all 0.5s ease;
}
.funky-show-hide.ng-hide-remove.ng-hide-remove-active {
transform: rotateY(0);
}
.check-element {
border: 1px solid black;
opacity: 1;
padding: 10px;
}
</file>
<file name="protractor.js" type="protractor">
it('should check ngHide', function() {
var checkbox = element(by.model('checked'));
var checkElem = element(by.css('.check-element'));
expect(checkElem.isDisplayed()).toBe(true);
checkbox.click();
expect(checkElem.isDisplayed()).toBe(false);
});
</file>
</example>
+7 -7
View File
@@ -16,7 +16,7 @@ var SelectController =
['$element', '$scope', /** @this */ function($element, $scope) {
var self = this,
optionsMap = new HashMap();
optionsMap = new NgMap();
self.selectValueMap = {}; // Keys are the hashed values, values the original values
@@ -137,7 +137,7 @@ var SelectController =
self.emptyOption = element;
}
var count = optionsMap.get(value) || 0;
optionsMap.put(value, count + 1);
optionsMap.set(value, count + 1);
// Only render at the end of a digest. This improves render performance when many options
// are added during a digest and ensures all relevant options are correctly marked as selected
scheduleRender();
@@ -148,13 +148,13 @@ var SelectController =
var count = optionsMap.get(value);
if (count) {
if (count === 1) {
optionsMap.remove(value);
optionsMap.delete(value);
if (value === '') {
self.hasEmptyOption = false;
self.emptyOption = undefined;
}
} else {
optionsMap.put(value, count - 1);
optionsMap.set(value, count - 1);
}
}
};
@@ -281,7 +281,7 @@ var SelectController =
var removeValue = optionAttrs.value;
self.removeOption(removeValue);
self.ngModelCtrl.$render();
scheduleRender();
if (self.multiple && currentValue && currentValue.indexOf(removeValue) !== -1 ||
currentValue === removeValue
@@ -606,9 +606,9 @@ var selectDirective = function() {
// Write value now needs to set the selected property of each matching option
selectCtrl.writeValue = function writeMultipleValue(value) {
var items = new HashMap(value);
forEach(element.find('option'), function(option) {
option.selected = isDefined(items.get(option.value)) || isDefined(items.get(selectCtrl.selectValueMap[option.value]));
option.selected = !!value && (includes(value, option.value) ||
includes(value, selectCtrl.selectValueMap[option.value]));
});
};
+2 -1
View File
@@ -1255,7 +1255,8 @@ function $HttpProvider() {
if ((config.cache || defaults.cache) && config.cache !== false &&
(config.method === 'GET' || config.method === 'JSONP')) {
cache = isObject(config.cache) ? config.cache
: isObject(defaults.cache) ? defaults.cache
: isObject(/** @type {?} */ (defaults).cache)
? /** @type {?} */ (defaults).cache
: defaultCache;
}
+2 -1
View File
@@ -33,7 +33,8 @@ function $IntervalProvider() {
* appropriate moment. See the example below for more details on how and when to do this.
* </div>
*
* @param {function()} fn A function that should be called repeatedly.
* @param {function()} fn A function that should be called repeatedly. If no additional arguments
* are passed (see below), the function is called with the current iteration count.
* @param {number} delay Number of milliseconds between each function call.
* @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
* indefinitely.
+37 -26
View File
@@ -137,6 +137,8 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
this.$$urlUpdatedByLocation = true;
};
this.$$parseLinkUrl = function(url, relHref) {
@@ -214,7 +216,7 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
withoutHashUrl = '';
if (isUndefined(withoutBaseUrl)) {
appBase = url;
this.replace();
/** @type {?} */ (this).replace();
}
}
}
@@ -270,6 +272,8 @@ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) {
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
this.$$urlUpdatedByLocation = true;
};
this.$$parseLinkUrl = function(url, relHref) {
@@ -327,6 +331,8 @@ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) {
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
// include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
this.$$absUrl = appBase + hashPrefix + this.$$url;
this.$$urlUpdatedByLocation = true;
};
}
@@ -656,6 +662,7 @@ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], fun
// but we're changing the $$state reference to $browser.state() during the $digest
// so the modification window is narrow.
this.$$state = isUndefined(state) ? null : state;
this.$$urlUpdatedByLocation = true;
return this;
};
@@ -968,36 +975,40 @@ function $LocationProvider() {
// update browser
$rootScope.$watch(function $locationWatch() {
var oldUrl = trimEmptyHash($browser.url());
var newUrl = trimEmptyHash($location.absUrl());
var oldState = $browser.state();
var currentReplace = $location.$$replace;
var urlOrStateChanged = oldUrl !== newUrl ||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
if (initializing || $location.$$urlUpdatedByLocation) {
$location.$$urlUpdatedByLocation = false;
if (initializing || urlOrStateChanged) {
initializing = false;
var oldUrl = trimEmptyHash($browser.url());
var newUrl = trimEmptyHash($location.absUrl());
var oldState = $browser.state();
var currentReplace = $location.$$replace;
var urlOrStateChanged = oldUrl !== newUrl ||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
$rootScope.$evalAsync(function() {
var newUrl = $location.absUrl();
var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
$location.$$state, oldState).defaultPrevented;
if (initializing || urlOrStateChanged) {
initializing = false;
// if the location was changed by a `$locationChangeStart` handler then stop
// processing this location change
if ($location.absUrl() !== newUrl) return;
$rootScope.$evalAsync(function() {
var newUrl = $location.absUrl();
var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
$location.$$state, oldState).defaultPrevented;
if (defaultPrevented) {
$location.$$parse(oldUrl);
$location.$$state = oldState;
} else {
if (urlOrStateChanged) {
setBrowserUrlWithFallback(newUrl, currentReplace,
oldState === $location.$$state ? null : $location.$$state);
// if the location was changed by a `$locationChangeStart` handler then stop
// processing this location change
if ($location.absUrl() !== newUrl) return;
if (defaultPrevented) {
$location.$$parse(oldUrl);
$location.$$state = oldState;
} else {
if (urlOrStateChanged) {
setBrowserUrlWithFallback(newUrl, currentReplace,
oldState === $location.$$state ? null : $location.$$state);
}
afterLocationChange(oldUrl, oldState);
}
afterLocationChange(oldUrl, oldState);
}
});
});
}
}
$location.$$replace = false;
+1 -1
View File
@@ -60,7 +60,7 @@ function $LogProvider() {
this.debugEnabled = function(flag) {
if (isDefined(flag)) {
debug = flag;
return this;
return this;
} else {
return debug;
}
+11 -4
View File
@@ -717,6 +717,13 @@ function findConstantAndWatchExpressions(ast, $filter) {
if (!property.value.constant) {
argsToWatch.push.apply(argsToWatch, property.value.toWatch);
}
if (property.computed) {
findConstantAndWatchExpressions(property.key, $filter);
if (!property.key.constant) {
argsToWatch.push.apply(argsToWatch, property.key.toWatch);
}
}
});
ast.constant = allConstants;
ast.toWatch = argsToWatch;
@@ -1782,13 +1789,13 @@ function $ParseProvider() {
}
}
function expressionInputDirtyCheck(newValue, oldValueOfValue) {
function expressionInputDirtyCheck(newValue, oldValueOfValue, compareObjectIdentity) {
if (newValue == null || oldValueOfValue == null) { // null/undefined
return newValue === oldValueOfValue;
}
if (typeof newValue === 'object') {
if (typeof newValue === 'object' && !compareObjectIdentity) {
// attempt to convert the value to a primitive type
// TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can
@@ -1817,7 +1824,7 @@ function $ParseProvider() {
inputExpressions = inputExpressions[0];
return scope.$watch(function expressionInputWatch(scope) {
var newInputValue = inputExpressions(scope);
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, parsedExpression.literal)) {
lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
oldInputValueOf = newInputValue && getValueOf(newInputValue);
}
@@ -1837,7 +1844,7 @@ function $ParseProvider() {
for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
var newInputValue = inputExpressions[i](scope);
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) {
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], parsedExpression.literal))) {
oldInputValues[i] = newInputValue;
oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
}
+6 -1
View File
@@ -239,6 +239,7 @@ function $QProvider() {
*
* @description
* Retrieves or overrides whether to generate an error when a rejected promise is not handled.
* This feature is enabled by default.
*
* @param {boolean=} value Whether to generate an error when a rejected promise is not handled.
* @returns {boolean|ng.$qProvider} Current value when called without a new value or self for
@@ -380,7 +381,11 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) {
if (!toCheck.pur) {
toCheck.pur = true;
var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value);
exceptionHandler(errorMessage);
if (toCheck.value instanceof Error) {
exceptionHandler(toCheck.value, errorMessage);
} else {
exceptionHandler(errorMessage);
}
}
}
}
+14 -5
View File
@@ -416,15 +416,21 @@ function $RootScopeProvider() {
if (!array) {
array = scope.$$watchers = [];
array.$$digestWatchIndex = -1;
}
// we use unshift since we use a while loop in $digest for speed.
// the while loop reads in reverse order.
array.unshift(watcher);
array.$$digestWatchIndex++;
incrementWatchersCount(this, 1);
return function deregisterWatch() {
if (arrayRemove(array, watcher) >= 0) {
var index = arrayRemove(array, watcher);
if (index >= 0) {
incrementWatchersCount(scope, -1);
if (index < array.$$digestWatchIndex) {
array.$$digestWatchIndex--;
}
}
lastDirtyWatch = null;
};
@@ -757,7 +763,6 @@ function $RootScopeProvider() {
$digest: function() {
var watch, value, last, fn, get,
watchers,
length,
dirty, ttl = TTL,
next, current, target = this,
watchLog = [],
@@ -798,10 +803,10 @@ function $RootScopeProvider() {
do { // "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
// process our watches
length = watchers.length;
while (length--) {
watchers.$$digestWatchIndex = watchers.length;
while (watchers.$$digestWatchIndex--) {
try {
watch = watchers[length];
watch = watchers[watchers.$$digestWatchIndex];
// 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) {
@@ -871,6 +876,10 @@ function $RootScopeProvider() {
}
}
postDigestQueue.length = postDigestQueuePosition = 0;
// Check for changes to browser url that happened during the $digest
// (for which no event is fired; e.g. via `history.pushState()`)
$browser.$$checkUrlChange();
},
+3
View File
@@ -25,7 +25,10 @@ function $SnifferProvider() {
// (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by
// the presence of an extension runtime ID and the absence of other Chrome runtime APIs
// (see https://developer.chrome.com/apps/manifest/sandbox).
// (NW.js apps have access to Chrome APIs, but do support `history`.)
isNw = $window.nw && $window.nw.process,
isChromePackagedApp =
!isNw &&
$window.chrome &&
($window.chrome.app && $window.chrome.app.runtime ||
!$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id),
+79 -83
View File
@@ -36,9 +36,9 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
}
}
function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
function isAllowed(ruleType, currentAnimation, previousAnimation) {
return rules[ruleType].some(function(fn) {
return fn(element, currentAnimation, previousAnimation);
return fn(currentAnimation, previousAnimation);
});
}
@@ -48,40 +48,40 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
return and ? a && b : a || b;
}
rules.join.push(function(element, newAnimation, currentAnimation) {
rules.join.push(function(newAnimation, currentAnimation) {
// if the new animation is class-based then we can just tack that on
return !newAnimation.structural && hasAnimationClasses(newAnimation);
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
rules.skip.push(function(newAnimation, currentAnimation) {
// there is no need to animate anything if no classes are being added and
// there is no structural animation that will be triggered
return !newAnimation.structural && !hasAnimationClasses(newAnimation);
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
rules.skip.push(function(newAnimation, currentAnimation) {
// why should we trigger a new structural animation if the element will
// be removed from the DOM anyway?
return currentAnimation.event === 'leave' && newAnimation.structural;
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
rules.skip.push(function(newAnimation, currentAnimation) {
// if there is an ongoing current animation then don't even bother running the class-based animation
return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
rules.cancel.push(function(newAnimation, currentAnimation) {
// there can never be two structural animations running at the same time
return currentAnimation.structural && newAnimation.structural;
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
rules.cancel.push(function(newAnimation, currentAnimation) {
// if the previous animation is already running, but the new animation will
// be triggered, but the new animation is structural
return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
rules.cancel.push(function(newAnimation, currentAnimation) {
// cancel the animation if classes added / removed in both animation cancel each other out,
// but only if the current animation isn't structural
@@ -100,15 +100,15 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
});
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$Map',
'$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
'$$isDocumentHidden',
function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
function($$rAF, $rootScope, $rootElement, $document, $$Map,
$$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow,
$$isDocumentHidden) {
var activeAnimationsLookup = new $$HashMap();
var disabledElementsLookup = new $$HashMap();
var activeAnimationsLookup = new $$Map();
var disabledElementsLookup = new $$Map();
var animationsEnabled = null;
function postDigestTaskFactory() {
@@ -181,10 +181,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
return this === arg || !!(this.compareDocumentPosition(arg) & 16);
};
function findCallbacks(parent, element, event) {
var targetNode = getDomNode(element);
var targetParentNode = getDomNode(parent);
function findCallbacks(targetParentNode, targetNode, event) {
var matches = [];
var entries = callbackRegistry[event];
if (entries) {
@@ -209,11 +206,11 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
});
}
function cleanupEventListeners(phase, element) {
if (phase === 'close' && !element[0].parentNode) {
function cleanupEventListeners(phase, node) {
if (phase === 'close' && !node.parentNode) {
// If the element is not attached to a parentNode, it has been removed by
// the domOperation, and we can safely remove the event callbacks
$animate.off(element);
$animate.off(node);
}
}
@@ -294,7 +291,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
bool = !disabledElementsLookup.get(node);
} else {
// (element, bool) - Element setter
disabledElementsLookup.put(node, !bool);
disabledElementsLookup.set(node, !bool);
}
}
}
@@ -305,18 +302,15 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
return $animate;
function queueAnimation(element, event, initialOptions) {
function queueAnimation(originalElement, event, initialOptions) {
// we always make a copy of the options since
// there should never be any side effects on
// the input data when running `$animateCss`.
var options = copy(initialOptions);
var node, parent;
element = stripCommentsFromElement(element);
if (element) {
node = getDomNode(element);
parent = element.parent();
}
var element = stripCommentsFromElement(originalElement);
var node = getDomNode(element);
var parentNode = node && node.parentNode;
options = prepareAnimationOptions(options);
@@ -381,7 +375,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// there is no point in traversing the same collection of parent ancestors if a followup
// animation will be run on the same element that already did all that checking work
if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) {
skipAnimations = !areAnimationsAllowed(element, parent, event);
skipAnimations = !areAnimationsAllowed(node, parentNode, event);
}
if (skipAnimations) {
@@ -393,7 +387,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
}
if (isStructural) {
closeChildAnimations(element);
closeChildAnimations(node);
}
var newAnimation = {
@@ -408,7 +402,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
};
if (hasExistingAnimation) {
var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
var skipAnimationFlag = isAllowed('skip', newAnimation, existingAnimation);
if (skipAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
close();
@@ -418,7 +412,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
return existingAnimation.runner;
}
}
var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
var cancelAnimationFlag = isAllowed('cancel', newAnimation, existingAnimation);
if (cancelAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
// this will end the animation right away and it is safe
@@ -440,7 +434,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// a joined animation means that this animation will take over the existing one
// so an example would involve a leave animation taking over an enter. Then when
// the postDigest kicks in the enter will be ignored.
var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
var joinAnimationFlag = isAllowed('join', newAnimation, existingAnimation);
if (joinAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
normalizeAnimationDetails(element, newAnimation);
@@ -474,7 +468,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
if (!isValidAnimation) {
close();
clearElementAnimationState(element);
clearElementAnimationState(node);
return runner;
}
@@ -482,9 +476,18 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
var counter = (existingAnimation.counter || 0) + 1;
newAnimation.counter = counter;
markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
markElementAnimationState(node, PRE_DIGEST_STATE, newAnimation);
$rootScope.$$postDigest(function() {
// It is possible that the DOM nodes inside `originalElement` have been replaced. This can
// happen if the animated element is a transcluded clone and also has a `templateUrl`
// directive on it. Therefore, we must recreate `element` in order to interact with the
// actual DOM nodes.
// Note: We still need to use the old `node` for certain things, such as looking up in
// HashMaps where it was used as the key.
element = stripCommentsFromElement(originalElement);
var animationDetails = activeAnimationsLookup.get(node);
var animationCancelled = !animationDetails;
animationDetails = animationDetails || {};
@@ -523,7 +526,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// isn't allowed to animate from here then we need to clear the state of the element
// so that any future animations won't read the expired animation data.
if (!isValidAnimation) {
clearElementAnimationState(element);
clearElementAnimationState(node);
}
return;
@@ -535,7 +538,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
? 'setClass'
: animationDetails.event;
markElementAnimationState(element, RUNNING_STATE);
markElementAnimationState(node, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options);
// this will update the runner's flow-control events based on
@@ -547,7 +550,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
close(!status);
var animationDetails = activeAnimationsLookup.get(node);
if (animationDetails && animationDetails.counter === counter) {
clearElementAnimationState(getDomNode(element));
clearElementAnimationState(node);
}
notifyProgress(runner, event, 'close', {});
});
@@ -557,7 +560,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
function notifyProgress(runner, event, phase, data) {
runInNextPostDigestOrNow(function() {
var callbacks = findCallbacks(parent, element, event);
var callbacks = findCallbacks(parentNode, node, event);
if (callbacks.length) {
// do not optimize this call here to RAF because
// we don't know how heavy the callback code here will
@@ -567,10 +570,10 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
forEach(callbacks, function(callback) {
callback(element, phase, data);
});
cleanupEventListeners(phase, element);
cleanupEventListeners(phase, node);
});
} else {
cleanupEventListeners(phase, element);
cleanupEventListeners(phase, node);
}
});
runner.progress(event, phase, data);
@@ -585,8 +588,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
}
}
function closeChildAnimations(element) {
var node = getDomNode(element);
function closeChildAnimations(node) {
var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
forEach(children, function(child) {
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME), 10);
@@ -597,21 +599,16 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
animationDetails.runner.end();
/* falls through */
case PRE_DIGEST_STATE:
activeAnimationsLookup.remove(child);
activeAnimationsLookup.delete(child);
break;
}
}
});
}
function clearElementAnimationState(element) {
var node = getDomNode(element);
function clearElementAnimationState(node) {
node.removeAttribute(NG_ANIMATE_ATTR_NAME);
activeAnimationsLookup.remove(node);
}
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
activeAnimationsLookup.delete(node);
}
/**
@@ -621,54 +618,54 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
* c) the element is not a child of the body
* d) the element is not a child of the $rootElement
*/
function areAnimationsAllowed(element, parentElement, event) {
var bodyElement = jqLite($document[0].body);
var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
var rootElementDetected = isMatchingElement(element, $rootElement);
var parentAnimationDetected = false;
var animateChildren;
var elementDisabled = disabledElementsLookup.get(getDomNode(element));
function areAnimationsAllowed(node, parentNode, event) {
var bodyNode = $document[0].body;
var rootNode = getDomNode($rootElement);
var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
var bodyNodeDetected = (node === bodyNode) || node.nodeName === 'HTML';
var rootNodeDetected = (node === rootNode);
var parentAnimationDetected = false;
var elementDisabled = disabledElementsLookup.get(node);
var animateChildren;
var parentHost = jqLite.data(node, NG_ANIMATE_PIN_DATA);
if (parentHost) {
parentElement = parentHost;
parentNode = getDomNode(parentHost);
}
parentElement = getDomNode(parentElement);
while (parentElement) {
if (!rootElementDetected) {
while (parentNode) {
if (!rootNodeDetected) {
// angular doesn't want to attempt to animate elements outside of the application
// therefore we need to ensure that the rootElement is an ancestor of the current element
rootElementDetected = isMatchingElement(parentElement, $rootElement);
rootNodeDetected = (parentNode === rootNode);
}
if (parentElement.nodeType !== ELEMENT_NODE) {
if (parentNode.nodeType !== ELEMENT_NODE) {
// no point in inspecting the #document element
break;
}
var details = activeAnimationsLookup.get(parentElement) || {};
var details = activeAnimationsLookup.get(parentNode) || {};
// either an enter, leave or move animation will commence
// therefore we can't allow any animations to take place
// but if a parent animation is class-based then that's ok
if (!parentAnimationDetected) {
var parentElementDisabled = disabledElementsLookup.get(parentElement);
var parentNodeDisabled = disabledElementsLookup.get(parentNode);
if (parentElementDisabled === true && elementDisabled !== false) {
if (parentNodeDisabled === true && elementDisabled !== false) {
// disable animations if the user hasn't explicitly enabled animations on the
// current element
elementDisabled = true;
// element is disabled via parent element, no need to check anything else
break;
} else if (parentElementDisabled === false) {
} else if (parentNodeDisabled === false) {
elementDisabled = false;
}
parentAnimationDetected = details.structural;
}
if (isUndefined(animateChildren) || animateChildren === true) {
var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
var value = jqLite.data(parentNode, NG_ANIMATE_CHILDREN_DATA);
if (isDefined(value)) {
animateChildren = value;
}
@@ -677,47 +674,46 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// there is no need to continue traversing at this point
if (parentAnimationDetected && animateChildren === false) break;
if (!bodyElementDetected) {
if (!bodyNodeDetected) {
// we also need to ensure that the element is or will be a part of the body element
// otherwise it is pointless to even issue an animation to be rendered
bodyElementDetected = isMatchingElement(parentElement, bodyElement);
bodyNodeDetected = (parentNode === bodyNode);
}
if (bodyElementDetected && rootElementDetected) {
if (bodyNodeDetected && rootNodeDetected) {
// If both body and root have been found, any other checks are pointless,
// as no animation data should live outside the application
break;
}
if (!rootElementDetected) {
// If no rootElement is detected, check if the parentElement is pinned to another element
parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
if (!rootNodeDetected) {
// If `rootNode` is not detected, check if `parentNode` is pinned to another element
parentHost = jqLite.data(parentNode, NG_ANIMATE_PIN_DATA);
if (parentHost) {
// The pin target element becomes the next parent element
parentElement = getDomNode(parentHost);
parentNode = getDomNode(parentHost);
continue;
}
}
parentElement = parentElement.parentNode;
parentNode = parentNode.parentNode;
}
var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
return allowAnimation && rootElementDetected && bodyElementDetected;
return allowAnimation && rootNodeDetected && bodyNodeDetected;
}
function markElementAnimationState(element, state, details) {
function markElementAnimationState(node, state, details) {
details = details || {};
details.state = state;
var node = getDomNode(element);
node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
var oldValue = activeAnimationsLookup.get(node);
var newValue = oldValue
? extend(oldValue, details)
: details;
activeAnimationsLookup.put(node, newValue);
activeAnimationsLookup.set(node, newValue);
}
}];
}];
+6 -6
View File
@@ -21,21 +21,21 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
return element.data(RUNNER_STORAGE_KEY);
}
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$Map', '$$rAFScheduler',
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$Map, $$rAFScheduler) {
var animationQueue = [];
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
function sortAnimations(animations) {
var tree = { children: [] };
var i, lookup = new $$HashMap();
var i, lookup = new $$Map();
// this is done first beforehand so that the hashmap
// this is done first beforehand so that the map
// is filled with a list of the elements that will be animated
for (i = 0; i < animations.length; i++) {
var animation = animations[i];
lookup.put(animation.domNode, animations[i] = {
lookup.set(animation.domNode, animations[i] = {
domNode: animation.domNode,
fn: animation.fn,
children: []
@@ -54,7 +54,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
var elementNode = entry.domNode;
var parentNode = elementNode.parentNode;
lookup.put(elementNode, entry);
lookup.set(elementNode, entry);
var parentEntry;
while (parentNode) {
+1 -1
View File
@@ -355,7 +355,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return {
restrict: 'A',
compile: function(elem, attr) {
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
var fn = $parse(attr.ngClick);
return function(scope, elem, attr) {
if (!isNodeOneOf(elem, nodeBlackList)) {
+2 -4
View File
@@ -629,10 +629,8 @@ angular.module('ngMessages', [], function initAngularHelpers() {
* @scope
*
* @description
* `ngMessageExp` is a directive with the purpose to show and hide a particular message.
* For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
* `ngMessageExp` is the same as {@link directive:ngMessage `ngMessage`}, but instead of a static
* value, it accepts an expression to be evaluated for the message key.
*
* @usage
* ```html
+35 -20
View File
@@ -37,10 +37,30 @@ angular.mock.$Browser = function() {
self.$$lastUrl = self.$$url; // used by url polling fn
self.pollFns = [];
// TODO(vojta): remove this temporary api
self.$$completeOutstandingRequest = angular.noop;
self.$$incOutstandingRequestCount = angular.noop;
// Testability API
var outstandingRequestCount = 0;
var outstandingRequestCallbacks = [];
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
self.$$completeOutstandingRequest = function(fn) {
try {
fn();
} finally {
outstandingRequestCount--;
if (!outstandingRequestCount) {
while (outstandingRequestCallbacks.length) {
outstandingRequestCallbacks.pop()();
}
}
}
};
self.notifyWhenNoOutstandingRequests = function(callback) {
if (outstandingRequestCount) {
outstandingRequestCallbacks.push(callback);
} else {
callback();
}
};
// register url polling fn
@@ -65,6 +85,8 @@ angular.mock.$Browser = function() {
self.deferredNextId = 0;
self.defer = function(fn, delay) {
// Note that we do not use `$$incOutstandingRequestCount` or `$$completeOutstandingRequest`
// in this mock implementation.
delay = delay || 0;
self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
self.deferredFns.sort(function(a, b) { return a.time - b.time;});
@@ -166,10 +188,6 @@ angular.mock.$Browser.prototype = {
state: function() {
return this.$$state;
},
notifyWhenNoOutstandingRequests: function(fn) {
fn();
}
};
@@ -1295,9 +1313,8 @@ angular.mock.dump = function(object) {
});
```
*/
angular.mock.$HttpBackendProvider = function() {
this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
};
angular.mock.$httpBackendDecorator =
['$rootScope', '$timeout', '$delegate', createHttpBackendMock];
/**
* General factory function for $httpBackend mock.
@@ -1318,7 +1335,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
expectations = [],
responses = [],
responsesPush = angular.bind(responses, responses.push),
copy = angular.copy;
copy = angular.copy,
// We cache the original backend so that if both ngMock and ngMockE2E override the
// service the ngMockE2E version can pass through to the real backend
originalHttpBackend = $delegate.$$originalHttpBackend || $delegate;
function createResponse(status, data, headers, statusText) {
if (angular.isFunction(status)) return status;
@@ -1403,7 +1423,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
// if $browser specified, we do auto flush all requests
($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
} else if (definition.passThrough) {
$delegate(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
originalHttpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers);
} else throw new Error('No response defined !');
return;
}
@@ -1879,6 +1899,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
responses.length = 0;
};
$httpBackend.$$originalHttpBackend = originalHttpBackend;
return $httpBackend;
@@ -2376,7 +2398,6 @@ angular.module('ngMock', ['ng']).provider({
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider,
$componentController: angular.mock.$ComponentControllerProvider
}).config(['$provide', '$compileProvider', function($provide, $compileProvider) {
@@ -2384,6 +2405,7 @@ angular.module('ngMock', ['ng']).provider({
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', createControllerDecorator($compileProvider));
$provide.decorator('$httpBackend', angular.mock.$httpBackendDecorator);
}]);
/**
@@ -2398,7 +2420,6 @@ angular.module('ngMock', ['ng']).provider({
* the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
*/
angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
$provide.value('$httpBackend', angular.injector(['ng']).get('$httpBackend'));
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
}]);
@@ -2964,12 +2985,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
delete fn.$inject;
});
angular.forEach(currentSpec.$modules, function(module) {
if (module && module.$$hashKey) {
module.$$hashKey = undefined;
}
});
currentSpec.$injector = null;
currentSpec.$modules = null;
currentSpec.$providerInjector = null;
+16 -14
View File
@@ -590,11 +590,12 @@ angular.module('ngResource', ['ng']).
url = url.replace(/\/+$/, '') || '/';
}
// then replace collapse `/.` if found in the last URL path segment before the query
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
// Collapse `/.` if found in the last URL path segment before the query.
// E.g. `http://url.com/id/.format?q=x` becomes `http://url.com/id.format?q=x`.
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
// replace escaped `/\.` with `/.`
config.url = protocolAndIpv6 + url.replace(/\/\\\./, '/.');
// Replace escaped `/\.` with `/.`.
// (If `\.` comes from a param value, it will be encoded as `%5C.`.)
config.url = protocolAndIpv6 + url.replace(/\/(\\|%5C)\./, '/.');
// set params - delegate param encoding to $http
@@ -636,6 +637,7 @@ angular.module('ngResource', ['ng']).
var data = extend({}, this);
delete data.$promise;
delete data.$resolved;
delete data.$cancelRequest;
return data;
};
@@ -783,18 +785,18 @@ angular.module('ngResource', ['ng']).
return value;
},
(hasError || hasResponseErrorInterceptor) ?
function(response) {
if (hasError) error(response);
return hasResponseErrorInterceptor ?
function(response) {
if (hasError && !hasResponseErrorInterceptor) {
// Avoid `Possibly Unhandled Rejection` error,
// but still fulfill the returned promise with a rejection
promise.catch(noop);
}
if (hasError) error(response);
return hasResponseErrorInterceptor ?
responseErrorInterceptor(response) :
$q.reject(response);
} :
undefined);
if (hasError && !hasResponseErrorInterceptor) {
// Avoid `Possibly Unhandled Rejection` error,
// but still fulfill the returned promise with a rejection
promise.catch(noop);
}
} :
undefined);
if (!isInstanceCall) {
// we are creating instance / collection
+13 -1
View File
@@ -7,6 +7,7 @@
var isArray;
var isObject;
var isDefined;
var noop;
/**
* @ngdoc module
@@ -54,6 +55,7 @@ function $RouteProvider() {
isArray = angular.isArray;
isObject = angular.isObject;
isDefined = angular.isDefined;
noop = angular.noop;
function inherit(parent, extra) {
return angular.extend(Object.create(parent), extra);
@@ -350,7 +352,8 @@ function $RouteProvider() {
'$injector',
'$templateRequest',
'$sce',
function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) {
'$browser',
function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce, $browser) {
/**
* @ngdoc service
@@ -680,6 +683,8 @@ function $RouteProvider() {
var nextRoutePromise = $q.resolve(nextRoute);
$browser.$$incOutstandingRequestCount();
nextRoutePromise.
then(getRedirectionData).
then(handlePossibleRedirection).
@@ -700,6 +705,13 @@ function $RouteProvider() {
if (nextRoute === $route.current) {
$rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error);
}
}).finally(function() {
// Because `commitRoute()` is called from a `$rootScope.$evalAsync` block (see
// `$locationWatch`), this `$$completeOutstandingRequest()` call will not cause
// `outstandingRequestCount` to hit zero. This is important in case we are redirecting
// to a new route which also requires some asynchronous work.
$browser.$$completeOutstandingRequest(noop);
});
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2016 Google, Inc. http://angularjs.org
* (c) 2010-2017 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window){
+1 -1
View File
@@ -143,7 +143,7 @@
/* apis.js */
"hashKey": false,
"HashMap": false,
"NgMapShim": false,
/* urlUtils.js */
"urlResolve": false,
+23 -2
View File
@@ -1684,7 +1684,8 @@ describe('angular', function() {
});
it('should bootstrap from an extension into an extension document for same-origin documents only', function() {
if (msie) return; // IE does not support document.currentScript (nor extensions with protocol), so skip test.
// IE does not support `document.currentScript` (nor extensions with protocol), so skip test.
if (msie) return;
// Extension URLs are browser-specific, so we must choose a scheme that is supported by the browser to make
// sure that the URL is properly parsed.
@@ -1715,8 +1716,28 @@ describe('angular', function() {
expect(allowAutoBootstrap(fakeDoc)).toBe(false);
});
it('should bootstrap from a script with an empty or missing `src` attribute', function() {
// IE does not support `document.currentScript` (nor extensions with protocol), so skip test.
if (msie) return;
// Fake a minimal document object (the actual document.currentScript is readonly).
var src;
var fakeDoc = {
createElement: document.createElement.bind(document),
currentScript: {getAttribute: function() { return src; }},
location: {origin: 'some-value', protocol: 'http:'}
};
src = null;
expect(allowAutoBootstrap(fakeDoc)).toBe(true);
src = '';
expect(allowAutoBootstrap(fakeDoc)).toBe(true);
});
it('should not bootstrap from an extension into a non-extension document', function() {
if (msie) return; // IE does not support document.currentScript (nor extensions with protocol), so skip test.
// IE does not support `document.currentScript` (nor extensions with protocol), so skip test.
if (msie) return;
var src = 'resource://something';
// Fake a minimal document object (the actual document.currentScript is readonly).
+95 -42
View File
@@ -1,56 +1,109 @@
'use strict';
describe('api', function() {
describe('hashKey()', function() {
it('should use an existing `$$hashKey`', function() {
var obj = {$$hashKey: 'foo'};
expect(hashKey(obj)).toBe('foo');
});
describe('HashMap', function() {
it('should support a function as `$$hashKey` (and call it)', function() {
var obj = {$$hashKey: valueFn('foo')};
expect(hashKey(obj)).toBe('foo');
});
it('should create a new `$$hashKey` if none exists (and return it)', function() {
var obj = {};
expect(hashKey(obj)).toBe(obj.$$hashKey);
expect(obj.$$hashKey).toBeDefined();
});
it('should create appropriate `$$hashKey`s for primitive values', function() {
expect(hashKey(undefined)).toBe(hashKey(undefined));
expect(hashKey(null)).toBe(hashKey(null));
expect(hashKey(null)).not.toBe(hashKey(undefined));
expect(hashKey(true)).toBe(hashKey(true));
expect(hashKey(false)).toBe(hashKey(false));
expect(hashKey(false)).not.toBe(hashKey(true));
expect(hashKey(42)).toBe(hashKey(42));
expect(hashKey(1337)).toBe(hashKey(1337));
expect(hashKey(1337)).not.toBe(hashKey(42));
expect(hashKey('foo')).toBe(hashKey('foo'));
expect(hashKey('foo')).not.toBe(hashKey('bar'));
});
it('should create appropriate `$$hashKey`s for non-primitive values', function() {
var fn = function() {};
var arr = [];
var obj = {};
var date = new Date();
expect(hashKey(fn)).toBe(hashKey(fn));
expect(hashKey(fn)).not.toBe(hashKey(function() {}));
expect(hashKey(arr)).toBe(hashKey(arr));
expect(hashKey(arr)).not.toBe(hashKey([]));
expect(hashKey(obj)).toBe(hashKey(obj));
expect(hashKey(obj)).not.toBe(hashKey({}));
expect(hashKey(date)).toBe(hashKey(date));
expect(hashKey(date)).not.toBe(hashKey(new Date()));
});
it('should support a custom `nextUidFn`', function() {
var nextUidFn = jasmine.createSpy('nextUidFn').and.returnValues('foo', 'bar', 'baz', 'qux');
var fn = function() {};
var arr = [];
var obj = {};
var date = new Date();
hashKey(fn, nextUidFn);
hashKey(arr, nextUidFn);
hashKey(obj, nextUidFn);
hashKey(date, nextUidFn);
expect(fn.$$hashKey).toBe('function:foo');
expect(arr.$$hashKey).toBe('object:bar');
expect(obj.$$hashKey).toBe('object:baz');
expect(date.$$hashKey).toBe('object:qux');
});
});
describe('NgMapShim', function() {
it('should do basic crud', function() {
var map = new HashMap();
var key = {};
var value1 = {};
var value2 = {};
map.put(key, value1);
map.put(key, value2);
expect(map.get(key)).toBe(value2);
expect(map.get({})).toBeUndefined();
expect(map.remove(key)).toBe(value2);
expect(map.get(key)).toBeUndefined();
var map = new NgMapShim();
var keys = [{}, {}, {}];
var values = [{}, {}, {}];
map.set(keys[0], values[1]);
map.set(keys[0], values[0]);
expect(map.get(keys[0])).toBe(values[0]);
expect(map.get(keys[1])).toBeUndefined();
map.set(keys[1], values[1]);
map.set(keys[2], values[2]);
expect(map.delete(keys[0])).toBe(true);
expect(map.delete(keys[0])).toBe(false);
expect(map.get(keys[0])).toBeUndefined();
expect(map.get(keys[1])).toBe(values[1]);
expect(map.get(keys[2])).toBe(values[2]);
});
it('should init from an array', function() {
var map = new HashMap(['a','b']);
expect(map.get('a')).toBe(0);
expect(map.get('b')).toBe(1);
expect(map.get('c')).toBeUndefined();
});
it('should be able to deal with `NaN` keys', function() {
var map = new NgMapShim();
it('should maintain hashKey for object keys', function() {
var map = new HashMap();
var key = {};
map.get(key);
expect(key.$$hashKey).toBeDefined();
});
map.set('NaN', 'foo');
map.set(NaN, 'bar');
map.set(NaN, 'baz');
it('should maintain hashKey for function keys', function() {
var map = new HashMap();
var key = function() {};
map.get(key);
expect(key.$$hashKey).toBeDefined();
});
expect(map.get('NaN')).toBe('foo');
expect(map.get(NaN)).toBe('baz');
it('should share hashKey between HashMap by default', function() {
var map1 = new HashMap(), map2 = new HashMap();
var key1 = {}, key2 = {};
map1.get(key1);
map2.get(key2);
expect(key1.$$hashKey).not.toEqual(key2.$$hashKey);
});
expect(map.delete(NaN)).toBe(true);
expect(map.get(NaN)).toBeUndefined();
expect(map.get('NaN')).toBe('foo');
it('should maintain hashKey per HashMap if flag is passed', function() {
var map1 = new HashMap([], true), map2 = new HashMap([], true);
var key1 = {}, key2 = {};
map1.get(key1);
map2.get(key2);
expect(key1.$$hashKey).toEqual(key2.$$hashKey);
expect(map.delete(NaN)).toBe(false);
});
});
});
+7 -29
View File
@@ -46,14 +46,13 @@ describe('injector', function() {
it('should resolve dependency graph and instantiate all services just once', function() {
var log = [];
// s1
// / | \
// / s2 \
// / / | \ \
// /s3 < s4 > s5
// //
// s6
// s1
// / | \
// / s2 \
// / / | \ \
// /s3 < s4 > s5
// //
// s6
providers('s1', function() { log.push('s1'); return {}; }, {$inject: ['s2', 's5', 's6']});
providers('s2', function() { log.push('s2'); return {}; }, {$inject: ['s3', 's4', 's5']});
@@ -285,14 +284,6 @@ describe('injector', function() {
// eslint-disable-next-line no-eval
expect(annotate(eval('a => b => b'))).toEqual(['a']);
});
// Support: Chrome 50-51 only
// TODO (gkalpak): Remove when Chrome v52 is released.
// it('should be able to inject fat-arrow function', function() {
// inject(($injector) => {
// expect($injector).toBeDefined();
// });
// });
}
if (support.classes) {
@@ -325,19 +316,6 @@ describe('injector', function() {
expect(instance).toEqual(jasmine.any(Clazz));
});
}
// Support: Chrome 50-51 only
// TODO (gkalpak): Remove when Chrome v52 is released.
// it('should be able to invoke classes', function() {
// class Test {
// constructor($injector) {
// this.$injector = $injector;
// }
// }
// var instance = injector.invoke(Test, null, null, 'Test');
// expect(instance.$injector).toBe(injector);
// });
}
});
@@ -0,0 +1,9 @@
<html ng-app="lettersApp">
<body>
<div ng-view></div>
<script src="angular.js"></script>
<script src="angular-route.js"></script>
<script src="script.js"></script>
</body>
</html>
@@ -0,0 +1,43 @@
'use strict';
angular.
module('lettersApp', ['ngRoute']).
config(function($routeProvider) {
$routeProvider.
otherwise(resolveRedirectTo('/foo1')).
when('/foo1', resolveRedirectTo('/bar1')).
when('/bar1', resolveRedirectTo('/baz1')).
when('/baz1', resolveRedirectTo('/qux1')).
when('/qux1', {
template: '<ul><li ng-repeat="letter in $resolve.letters">{{ letter }}</li></ul>',
resolve: resolveLetters()
}).
when('/foo2', resolveRedirectTo('/bar2')).
when('/bar2', resolveRedirectTo('/baz2')).
when('/baz2', resolveRedirectTo('/qux2')).
when('/qux2', {
template: '{{ $resolve.letters.length }}',
resolve: resolveLetters()
});
// Helpers
function resolveLetters() {
return {
letters: function($q) {
return $q(function(resolve) {
window.setTimeout(resolve, 2000, ['a', 'b', 'c', 'd', 'e']);
});
}
};
}
function resolveRedirectTo(path) {
return {
resolveRedirectTo: function($q) {
return $q(function(resolve) {
window.setTimeout(resolve, 250, path);
});
}
};
}
});
+33
View File
@@ -0,0 +1,33 @@
'use strict';
describe('ngRoute promises', function() {
beforeEach(function() {
loadFixture('ng-route-promise');
});
it('should wait for route promises', function() {
expect(element.all(by.tagName('li')).count()).toBe(5);
});
it('should time out if the promise takes long enough', function() {
// Don't try this at home kids, I'm a protractor dev
browser.manage().timeouts().setScriptTimeout(1000);
browser.waitForAngular().then(function() {
fail('waitForAngular() should have timed out, but didn\'t');
}, function(error) {
expect(error.message).toContain('Timed out waiting for asynchronous Angular tasks to finish');
});
});
it('should wait for route promises when navigating to another route', function() {
browser.setLocation('/foo2');
expect(element(by.tagName('body')).getText()).toBe('5');
});
afterEach(function(done) {
// Restore old timeout limit
browser.getProcessedConfig().then(function(config) {
return browser.manage().timeouts().setScriptTimeout(config.allScriptsTimeout);
}).then(done);
});
});
+11 -13
View File
@@ -54,17 +54,16 @@ beforeEach(function() {
afterEach(function() {
var count, cache;
// both of these nodes are persisted across tests
// and therefore the hashCode may be cached
var node = window.document.querySelector('html');
if (node) {
node.$$hashKey = null;
}
var bod = window.document.body;
if (bod) {
bod.$$hashKey = null;
}
window.document.$$hashKey = null;
// These Nodes are persisted across tests.
// They used to be assigned a `$$hashKey` when animated, which we needed to clear after each test
// to avoid affecting other tests. This is no longer the case, so we are just ensuring that there
// is indeed no `$$hachKey` on them.
var doc = window.document;
var html = doc.querySelector('html');
var body = doc.body;
expect(doc.$$hashKey).toBeFalsy();
expect(html && html.$$hashKey).toBeFalsy();
expect(body && body.$$hashKey).toBeFalsy();
if (this.$injector) {
var $rootScope = this.$injector.get('$rootScope');
@@ -391,8 +390,7 @@ function generateInputCompilerHelper(helper) {
};
helper.changeInputValueTo = function(value) {
helper.inputElm.val(value);
browserTrigger(helper.inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
helper.changeGivenInputTo(helper.inputElm, value);
};
helper.changeGivenInputTo = function(inputElm, value) {
+8
View File
@@ -2182,6 +2182,14 @@ describe('jqLite', function() {
span.after('abc');
expect(root.html().toLowerCase()).toEqual('<span></span>abc');
});
it('should not throw when the element has no parent', function() {
var span = jqLite('<span></span>');
expect(function() { span.after('abc'); }).not.toThrow();
expect(span.length).toBe(1);
expect(span[0].outerHTML).toBe('<span></span>');
});
});
+2 -17
View File
@@ -87,15 +87,7 @@ describe('$anchorScroll', function() {
return function($window) {
forEach(elmSpy, function(spy, id) {
var count = map[id] || 0;
// TODO(gkalpak): `toHaveBeenCalledTimes()` works correctly with 0 since
// https://github.com/jasmine/jasmine/commit/342f0eb9a38194ecb8559e7df872c72afc0fe52e
// Fix when we upgrade to a version that contains the fix.
if (count > 0) {
expect(spy).toHaveBeenCalledTimes(count);
} else {
expect(spy).not.toHaveBeenCalled();
}
expect(spy).toHaveBeenCalledTimes(map[id] || 0);
});
expect($window.scrollTo).not.toHaveBeenCalled();
};
@@ -401,14 +393,7 @@ describe('$anchorScroll', function() {
return function($rootScope, $window) {
inject(expectScrollingTo(identifierCountMap));
// TODO(gkalpak): `toHaveBeenCalledTimes()` works correctly with 0 since
// https://github.com/jasmine/jasmine/commit/342f0eb9a38194ecb8559e7df872c72afc0fe52e
// Fix when we upgrade to a version that contains the fix.
if (list.length > 0) {
expect($window.scrollBy).toHaveBeenCalledTimes(list.length);
} else {
expect($window.scrollBy).not.toHaveBeenCalled();
}
expect($window.scrollBy).toHaveBeenCalledTimes(list.length);
forEach(list, function(offset, idx) {
// Due to sub-pixel rendering, there is a +/-1 error margin in the actual offset
var args = $window.scrollBy.calls.argsFor(idx);
+86 -47
View File
@@ -1881,15 +1881,14 @@ describe('$compile', function() {
it('should throw an error and clear element content if the template fails to load',
inject(function($compile, $exceptionHandler, $httpBackend, $rootScope) {
inject(function($compile, $httpBackend, $rootScope) {
$httpBackend.expect('GET', 'hello.html').respond(404, 'Not Found!');
element = $compile('<div><b class="hello">content</b></div>')($rootScope);
$httpBackend.flush();
expect(function() {
$httpBackend.flush();
}).toThrowMinErr('$compile', 'tpload', 'Failed to load template: hello.html');
expect(sortedHtml(element)).toBe('<div><b class="hello"></b></div>');
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'tpload',
'Failed to load template: hello.html');
})
);
@@ -1905,13 +1904,13 @@ describe('$compile', function() {
templateUrl: 'template.html'
}));
});
inject(function($compile, $exceptionHandler, $httpBackend) {
inject(function($compile, $httpBackend) {
$httpBackend.whenGET('template.html').respond('<p>template.html</p>');
$compile('<div><div class="sync async"></div></div>');
$httpBackend.flush();
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
expect(function() {
$compile('<div><div class="sync async"></div></div>');
$httpBackend.flush();
}).toThrowMinErr('$compile', 'multidir',
'Multiple directives [async, sync] asking for template on: ' +
'<div class="sync async">');
});
@@ -2122,15 +2121,15 @@ describe('$compile', function() {
'multiple root elements': '<div></div><div></div>'
}, function(directiveTemplate) {
inject(function($compile, $templateCache, $rootScope, $exceptionHandler) {
inject(function($compile, $templateCache, $rootScope) {
$templateCache.put('template.html', directiveTemplate);
$compile('<p template></p>')($rootScope);
$rootScope.$digest();
expect($exceptionHandler.errors.pop()).toEqualMinErr('$compile', 'tplrt',
'Template for directive \'template\' must have exactly one root element. ' +
'template.html'
);
expect(function() {
$compile('<p template></p>')($rootScope);
$rootScope.$digest();
}).toThrowMinErr('$compile', 'tplrt',
'Template for directive \'template\' must have exactly one root element. ' +
'template.html');
});
});
@@ -2657,13 +2656,13 @@ describe('$compile', function() {
);
it('should not allow more than one isolate/new scope creation per element regardless of `templateUrl`',
inject(function($exceptionHandler, $httpBackend) {
inject(function($httpBackend) {
$httpBackend.expect('GET', 'tiscope.html').respond('<div>Hello, world !</div>');
compile('<div class="tiscope-a; scope-b"></div>');
$httpBackend.flush();
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
expect(function() {
compile('<div class="tiscope-a; scope-b"></div>');
$httpBackend.flush();
}).toThrowMinErr('$compile', 'multidir',
'Multiple directives [scopeB, tiscopeA] asking for new/isolated scope on: ' +
'<div class="tiscope-a; scope-b ng-scope">');
})
@@ -4750,21 +4749,28 @@ describe('$compile', function() {
scope: {
attr: '@',
attrAlias: '@attr',
$attrAlias: '@$attr$',
ref: '=',
refAlias: '= ref',
$refAlias: '= $ref$',
reference: '=',
optref: '=?',
optrefAlias: '=? optref',
$optrefAlias: '=? $optref$',
optreference: '=?',
colref: '=*',
colrefAlias: '=* colref',
$colrefAlias: '=* $colref$',
owRef: '<',
owRefAlias: '< owRef',
$owRefAlias: '< $owRef$',
owOptref: '<?',
owOptrefAlias: '<? owOptref',
$owOptrefAlias: '<? $owOptref$',
expr: '&',
optExpr: '&?',
exprAlias: '&expr',
$exprAlias: '&$expr$',
constructor: '&?'
},
link: function(scope) {
@@ -5183,45 +5189,50 @@ describe('$compile', function() {
describe('attribute', function() {
it('should copy simple attribute', inject(function() {
compile('<div><span my-component attr="some text">');
compile('<div><span my-component attr="some text" $attr$="some other text">');
expect(componentScope.attr).toEqual('some text');
expect(componentScope.attrAlias).toEqual('some text');
expect(componentScope.$attrAlias).toEqual('some other text');
expect(componentScope.attrAlias).toEqual(componentScope.attr);
}));
it('should copy an attribute with spaces', inject(function() {
compile('<div><span my-component attr=" some text ">');
compile('<div><span my-component attr=" some text " $attr$=" some other text ">');
expect(componentScope.attr).toEqual(' some text ');
expect(componentScope.attrAlias).toEqual(' some text ');
expect(componentScope.$attrAlias).toEqual(' some other text ');
expect(componentScope.attrAlias).toEqual(componentScope.attr);
}));
it('should set up the interpolation before it reaches the link function', inject(function() {
$rootScope.name = 'misko';
compile('<div><span my-component attr="hello {{name}}">');
compile('<div><span my-component attr="hello {{name}}" $attr$="hi {{name}}">');
expect(componentScope.attr).toEqual('hello misko');
expect(componentScope.attrAlias).toEqual('hello misko');
expect(componentScope.$attrAlias).toEqual('hi misko');
}));
it('should update when interpolated attribute updates', inject(function() {
compile('<div><span my-component attr="hello {{name}}">');
compile('<div><span my-component attr="hello {{name}}" $attr$="hi {{name}}">');
$rootScope.name = 'igor';
$rootScope.$apply();
expect(componentScope.attr).toEqual('hello igor');
expect(componentScope.attrAlias).toEqual('hello igor');
expect(componentScope.$attrAlias).toEqual('hi igor');
}));
});
describe('object reference', function() {
it('should update local when origin changes', inject(function() {
compile('<div><span my-component ref="name">');
compile('<div><span my-component ref="name" $ref$="name">');
expect(componentScope.ref).toBeUndefined();
expect(componentScope.refAlias).toBe(componentScope.ref);
expect(componentScope.$refAlias).toBe(componentScope.ref);
$rootScope.name = 'misko';
$rootScope.$apply();
@@ -5229,16 +5240,18 @@ describe('$compile', function() {
expect($rootScope.name).toBe('misko');
expect(componentScope.ref).toBe('misko');
expect(componentScope.refAlias).toBe('misko');
expect(componentScope.$refAlias).toBe('misko');
$rootScope.name = {};
$rootScope.$apply();
expect(componentScope.ref).toBe($rootScope.name);
expect(componentScope.refAlias).toBe($rootScope.name);
expect(componentScope.$refAlias).toBe($rootScope.name);
}));
it('should update local when both change', inject(function() {
compile('<div><span my-component ref="name">');
compile('<div><span my-component ref="name" $ref$="name">');
$rootScope.name = {mark:123};
componentScope.ref = 'misko';
@@ -5246,6 +5259,7 @@ describe('$compile', function() {
expect($rootScope.name).toEqual({mark:123});
expect(componentScope.ref).toBe($rootScope.name);
expect(componentScope.refAlias).toBe($rootScope.name);
expect(componentScope.$refAlias).toBe($rootScope.name);
$rootScope.name = 'igor';
componentScope.ref = {};
@@ -5253,6 +5267,7 @@ describe('$compile', function() {
expect($rootScope.name).toEqual('igor');
expect(componentScope.ref).toBe($rootScope.name);
expect(componentScope.refAlias).toBe($rootScope.name);
expect(componentScope.$refAlias).toBe($rootScope.name);
}));
it('should not break if local and origin both change to the same value', inject(function() {
@@ -5382,19 +5397,22 @@ describe('$compile', function() {
describe('optional object reference', function() {
it('should update local when origin changes', inject(function() {
compile('<div><span my-component optref="name">');
compile('<div><span my-component optref="name" $optref$="name">');
expect(componentScope.optRef).toBeUndefined();
expect(componentScope.optRefAlias).toBe(componentScope.optRef);
expect(componentScope.$optRefAlias).toBe(componentScope.optRef);
$rootScope.name = 'misko';
$rootScope.$apply();
expect(componentScope.optref).toBe($rootScope.name);
expect(componentScope.optrefAlias).toBe($rootScope.name);
expect(componentScope.$optrefAlias).toBe($rootScope.name);
$rootScope.name = {};
$rootScope.$apply();
expect(componentScope.optref).toBe($rootScope.name);
expect(componentScope.optrefAlias).toBe($rootScope.name);
expect(componentScope.$optrefAlias).toBe($rootScope.name);
}));
it('should not throw exception when reference does not exist', inject(function() {
@@ -5402,6 +5420,7 @@ describe('$compile', function() {
expect(componentScope.optref).toBeUndefined();
expect(componentScope.optrefAlias).toBeUndefined();
expect(componentScope.$optrefAlias).toBeUndefined();
expect(componentScope.optreference).toBeUndefined();
}));
});
@@ -5419,16 +5438,18 @@ describe('$compile', function() {
$rootScope.query = '';
$rootScope.$apply();
compile('<div><span my-component colref="collection | filter:query">');
compile('<div><span my-component colref="collection | filter:query" $colref$="collection | filter:query">');
expect(componentScope.colref).toEqual($rootScope.collection);
expect(componentScope.colrefAlias).toEqual(componentScope.colref);
expect(componentScope.$colrefAlias).toEqual(componentScope.colref);
$rootScope.query = 'Gab';
$rootScope.$apply();
expect(componentScope.colref).toEqual([$rootScope.collection[0]]);
expect(componentScope.colrefAlias).toEqual([$rootScope.collection[0]]);
expect(componentScope.$colrefAlias).toEqual([$rootScope.collection[0]]);
}));
it('should update origin scope when isolate scope changes', inject(function() {
@@ -5456,10 +5477,11 @@ describe('$compile', function() {
describe('one-way binding', function() {
it('should update isolate when the identity of origin changes', inject(function() {
compile('<div><span my-component ow-ref="obj">');
compile('<div><span my-component ow-ref="obj" $ow-ref$="obj">');
expect(componentScope.owRef).toBeUndefined();
expect(componentScope.owRefAlias).toBe(componentScope.owRef);
expect(componentScope.$owRefAlias).toBe(componentScope.owRef);
$rootScope.obj = {value: 'initial'};
$rootScope.$apply();
@@ -5467,12 +5489,14 @@ describe('$compile', function() {
expect($rootScope.obj).toEqual({value: 'initial'});
expect(componentScope.owRef).toEqual({value: 'initial'});
expect(componentScope.owRefAlias).toBe(componentScope.owRef);
expect(componentScope.$owRefAlias).toBe(componentScope.owRef);
// This changes in both scopes because of reference
$rootScope.obj.value = 'origin1';
$rootScope.$apply();
expect(componentScope.owRef.value).toBe('origin1');
expect(componentScope.owRefAlias.value).toBe('origin1');
expect(componentScope.$owRefAlias.value).toBe('origin1');
componentScope.owRef = {value: 'isolate1'};
componentScope.$apply();
@@ -5483,6 +5507,7 @@ describe('$compile', function() {
$rootScope.$apply();
expect(componentScope.owRef.value).toBe('isolate1');
expect(componentScope.owRefAlias.value).toBe('origin2');
expect(componentScope.$owRefAlias.value).toBe('origin2');
// Change does propagate because object identity changes
$rootScope.obj = {value: 'origin3'};
@@ -5490,10 +5515,11 @@ describe('$compile', function() {
expect(componentScope.owRef.value).toBe('origin3');
expect(componentScope.owRef).toBe($rootScope.obj);
expect(componentScope.owRefAlias).toBe($rootScope.obj);
expect(componentScope.$owRefAlias).toBe($rootScope.obj);
}));
it('should update isolate when both change', inject(function() {
compile('<div><span my-component ow-ref="name">');
compile('<div><span my-component ow-ref="name" $ow-ref$="name">');
$rootScope.name = {mark:123};
componentScope.owRef = 'misko';
@@ -5502,6 +5528,7 @@ describe('$compile', function() {
expect($rootScope.name).toEqual({mark:123});
expect(componentScope.owRef).toBe($rootScope.name);
expect(componentScope.owRefAlias).toBe($rootScope.name);
expect(componentScope.$owRefAlias).toBe($rootScope.name);
$rootScope.name = 'igor';
componentScope.owRef = {};
@@ -5509,6 +5536,7 @@ describe('$compile', function() {
expect($rootScope.name).toEqual('igor');
expect(componentScope.owRef).toBe($rootScope.name);
expect(componentScope.owRefAlias).toBe($rootScope.name);
expect(componentScope.$owRefAlias).toBe($rootScope.name);
}));
describe('initialization', function() {
@@ -5705,17 +5733,19 @@ describe('$compile', function() {
it('should not update origin when identity of isolate changes', inject(function() {
$rootScope.name = {mark:123};
compile('<div><span my-component ow-ref="name">');
compile('<div><span my-component ow-ref="name" $ow-ref$="name">');
expect($rootScope.name).toEqual({mark:123});
expect(componentScope.owRef).toBe($rootScope.name);
expect(componentScope.owRefAlias).toBe($rootScope.name);
expect(componentScope.$owRefAlias).toBe($rootScope.name);
componentScope.owRef = 'martin';
$rootScope.$apply();
expect($rootScope.name).toEqual({mark: 123});
expect(componentScope.owRef).toBe('martin');
expect(componentScope.owRefAlias).toEqual({mark: 123});
expect(componentScope.$owRefAlias).toEqual({mark: 123});
}));
@@ -5862,20 +5892,23 @@ describe('$compile', function() {
describe('optional one-way binding', function() {
it('should update local when origin changes', inject(function() {
compile('<div><span my-component ow-optref="name">');
compile('<div><span my-component ow-optref="name" $ow-optref$="name">');
expect(componentScope.owOptref).toBeUndefined();
expect(componentScope.owOptrefAlias).toBe(componentScope.owOptref);
expect(componentScope.$owOptrefAlias).toBe(componentScope.owOptref);
$rootScope.name = 'misko';
$rootScope.$apply();
expect(componentScope.owOptref).toBe($rootScope.name);
expect(componentScope.owOptrefAlias).toBe($rootScope.name);
expect(componentScope.$owOptrefAlias).toBe($rootScope.name);
$rootScope.name = {};
$rootScope.$apply();
expect(componentScope.owOptref).toBe($rootScope.name);
expect(componentScope.owOptrefAlias).toBe($rootScope.name);
expect(componentScope.$owOptrefAlias).toBe($rootScope.name);
}));
it('should not throw exception when reference does not exist', inject(function() {
@@ -5883,6 +5916,7 @@ describe('$compile', function() {
expect(componentScope.owOptref).toBeUndefined();
expect(componentScope.owOptrefAlias).toBeUndefined();
expect(componentScope.$owOptrefAlias).toBeUndefined();
}));
});
});
@@ -5890,17 +5924,19 @@ describe('$compile', function() {
describe('executable expression', function() {
it('should allow expression execution with locals', inject(function() {
compile('<div><span my-component expr="count = count + offset">');
compile('<div><span my-component expr="count = count + offset" $expr$="count = count + offset">');
$rootScope.count = 2;
expect(typeof componentScope.expr).toBe('function');
expect(typeof componentScope.exprAlias).toBe('function');
expect(typeof componentScope.$exprAlias).toBe('function');
expect(componentScope.expr({offset: 1})).toEqual(3);
expect($rootScope.count).toEqual(3);
expect(componentScope.exprAlias({offset: 10})).toEqual(13);
expect($rootScope.count).toEqual(13);
expect(componentScope.$exprAlias({offset: 10})).toEqual(23);
expect($rootScope.count).toEqual(23);
}));
});
@@ -5918,17 +5954,21 @@ describe('$compile', function() {
expect(componentScope.$$isolateBindings.attr.mode).toBe('@');
expect(componentScope.$$isolateBindings.attr.attrName).toBe('attr');
expect(componentScope.$$isolateBindings.attrAlias.attrName).toBe('attr');
expect(componentScope.$$isolateBindings.$attrAlias.attrName).toBe('$attr$');
expect(componentScope.$$isolateBindings.ref.mode).toBe('=');
expect(componentScope.$$isolateBindings.ref.attrName).toBe('ref');
expect(componentScope.$$isolateBindings.refAlias.attrName).toBe('ref');
expect(componentScope.$$isolateBindings.$refAlias.attrName).toBe('$ref$');
expect(componentScope.$$isolateBindings.reference.mode).toBe('=');
expect(componentScope.$$isolateBindings.reference.attrName).toBe('reference');
expect(componentScope.$$isolateBindings.owRef.mode).toBe('<');
expect(componentScope.$$isolateBindings.owRef.attrName).toBe('owRef');
expect(componentScope.$$isolateBindings.owRefAlias.attrName).toBe('owRef');
expect(componentScope.$$isolateBindings.$owRefAlias.attrName).toBe('$owRef$');
expect(componentScope.$$isolateBindings.expr.mode).toBe('&');
expect(componentScope.$$isolateBindings.expr.attrName).toBe('expr');
expect(componentScope.$$isolateBindings.exprAlias.attrName).toBe('expr');
expect(componentScope.$$isolateBindings.$exprAlias.attrName).toBe('$expr$');
var firstComponentScope = componentScope,
first$$isolateBindings = componentScope.$$isolateBindings;
@@ -8956,18 +8996,17 @@ describe('$compile', function() {
}));
});
inject(function($compile, $exceptionHandler, $rootScope, $templateCache) {
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('noTransBar.html',
'<div>' +
// This ng-transclude is invalid. It should throw an error.
'<div class="bar" ng-transclude></div>' +
'</div>');
element = $compile('<div trans-foo>content</div>')($rootScope);
$rootScope.$digest();
expect($exceptionHandler.errors[0][1]).toBe('<div class="bar" ng-transclude="">');
expect($exceptionHandler.errors[0][0]).toEqualMinErr('ngTransclude', 'orphan',
expect(function() {
element = $compile('<div trans-foo>content</div>')($rootScope);
$rootScope.$digest();
}).toThrowMinErr('ngTransclude', 'orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: <div class="bar" ng-transclude="">');
@@ -9780,13 +9819,13 @@ describe('$compile', function() {
transclude: 'element'
}));
});
inject(function($compile, $exceptionHandler, $httpBackend) {
inject(function($compile, $httpBackend) {
$httpBackend.expectGET('template.html').respond('<p second>template.html</p>');
$compile('<div template first></div>');
$httpBackend.flush();
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
expect(function() {
$compile('<div template first></div>');
$httpBackend.flush();
}).toThrowMinErr('$compile', 'multidir',
'Multiple directives [first, second] asking for transclusion on: <p ');
});
});
+118 -86
View File
@@ -3,103 +3,135 @@
describe('$$cookieReader', function() {
var $$cookieReader, document;
function deleteAllCookies() {
var cookies = document.cookie.split(';');
var path = window.location.pathname;
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
var eqPos = cookie.indexOf('=');
var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
var parts = path.split('/');
while (parts.length) {
document.cookie = name + '=;path=' + (parts.join('/') || '/') + ';expires=Thu, 01 Jan 1970 00:00:00 GMT';
parts.pop();
describe('with access to `document.cookie`', function() {
function deleteAllCookies() {
var cookies = document.cookie.split(';');
var path = window.location.pathname;
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
var eqPos = cookie.indexOf('=');
var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
var parts = path.split('/');
while (parts.length) {
document.cookie = name + '=;path=' + (parts.join('/') || '/') + ';expires=Thu, 01 Jan 1970 00:00:00 GMT';
parts.pop();
}
}
}
}
beforeEach(function() {
document = window.document;
deleteAllCookies();
expect(document.cookie).toEqual('');
beforeEach(function() {
document = window.document;
deleteAllCookies();
expect(document.cookie).toEqual('');
inject(function(_$$cookieReader_) {
inject(function(_$$cookieReader_) {
$$cookieReader = _$$cookieReader_;
});
});
afterEach(function() {
deleteAllCookies();
expect(document.cookie).toEqual('');
});
describe('get via $$cookieReader()[cookieName]', function() {
it('should return undefined for nonexistent cookie', function() {
expect($$cookieReader().nonexistent).not.toBeDefined();
});
it('should return a value for an existing cookie', function() {
document.cookie = 'foo=bar=baz;path=/';
expect($$cookieReader().foo).toEqual('bar=baz');
});
it('should return the the first value provided for a cookie', function() {
// For a cookie that has different values that differ by path, the
// value for the most specific path appears first. $$cookieReader()
// should provide that value for the cookie.
document.cookie = 'foo="first"; foo="second"';
expect($$cookieReader()['foo']).toBe('"first"');
});
it('should decode cookie values that were encoded by puts', function() {
document.cookie = 'cookie2%3Dbar%3Bbaz=val%3Due;path=/';
expect($$cookieReader()['cookie2=bar;baz']).toEqual('val=ue');
});
it('should preserve leading & trailing spaces in names and values', function() {
document.cookie = '%20cookie%20name%20=%20cookie%20value%20';
expect($$cookieReader()[' cookie name ']).toEqual(' cookie value ');
expect($$cookieReader()['cookie name']).not.toBeDefined();
});
it('should decode special characters in cookie values', function() {
document.cookie = 'cookie_name=cookie_value_%E2%82%AC';
expect($$cookieReader()['cookie_name']).toEqual('cookie_value_€');
});
it('should not decode cookie values that do not appear to be encoded', function() {
// see #9211 - sometimes cookies contain a value that causes decodeURIComponent to throw
document.cookie = 'cookie_name=cookie_value_%XX';
expect($$cookieReader()['cookie_name']).toEqual('cookie_value_%XX');
});
});
describe('getAll via $$cookieReader()', function() {
it('should return cookies as hash', function() {
document.cookie = 'foo1=bar1;path=/';
document.cookie = 'foo2=bar2;path=/';
expect($$cookieReader()).toEqual({'foo1':'bar1', 'foo2':'bar2'});
});
it('should return empty hash if no cookies exist', function() {
expect($$cookieReader()).toEqual({});
});
});
it('should initialize cookie cache with existing cookies', function() {
document.cookie = 'existingCookie=existingValue;path=/';
expect($$cookieReader()).toEqual({'existingCookie':'existingValue'});
});
});
describe('without access to `document.cookie`', function() {
var cookieSpy;
beforeEach(module(function($provide) {
cookieSpy = jasmine.createSpy('cookie').and.throwError('Can\'t touch this!');
document = Object.create({}, {'cookie': {get: cookieSpy}});
$provide.value('$document', [document]);
}));
beforeEach(inject(function(_$$cookieReader_) {
$$cookieReader = _$$cookieReader_;
});
});
}));
afterEach(function() {
deleteAllCookies();
expect(document.cookie).toEqual('');
});
describe('get via $$cookieReader()[cookieName]', function() {
it('should return undefined for nonexistent cookie', function() {
expect($$cookieReader().nonexistent).not.toBeDefined();
});
it('should return a value for an existing cookie', function() {
document.cookie = 'foo=bar=baz;path=/';
expect($$cookieReader().foo).toEqual('bar=baz');
});
it('should return the the first value provided for a cookie', function() {
// For a cookie that has different values that differ by path, the
// value for the most specific path appears first. $$cookieReader()
// should provide that value for the cookie.
document.cookie = 'foo="first"; foo="second"';
expect($$cookieReader()['foo']).toBe('"first"');
});
it('should decode cookie values that were encoded by puts', function() {
document.cookie = 'cookie2%3Dbar%3Bbaz=val%3Due;path=/';
expect($$cookieReader()['cookie2=bar;baz']).toEqual('val=ue');
});
it('should preserve leading & trailing spaces in names and values', function() {
document.cookie = '%20cookie%20name%20=%20cookie%20value%20';
expect($$cookieReader()[' cookie name ']).toEqual(' cookie value ');
expect($$cookieReader()['cookie name']).not.toBeDefined();
});
it('should decode special characters in cookie values', function() {
document.cookie = 'cookie_name=cookie_value_%E2%82%AC';
expect($$cookieReader()['cookie_name']).toEqual('cookie_value_€');
});
it('should not decode cookie values that do not appear to be encoded', function() {
// see #9211 - sometimes cookies contain a value that causes decodeURIComponent to throw
document.cookie = 'cookie_name=cookie_value_%XX';
expect($$cookieReader()['cookie_name']).toEqual('cookie_value_%XX');
});
});
describe('getAll via $$cookieReader()', function() {
it('should return cookies as hash', function() {
document.cookie = 'foo1=bar1;path=/';
document.cookie = 'foo2=bar2;path=/';
expect($$cookieReader()).toEqual({'foo1':'bar1', 'foo2':'bar2'});
});
it('should return empty hash if no cookies exist', function() {
it('should return an empty object', function() {
expect($$cookieReader()).toEqual({});
expect(cookieSpy).toHaveBeenCalled();
});
});
it('should initialize cookie cache with existing cookies', function() {
document.cookie = 'existingCookie=existingValue;path=/';
expect($$cookieReader()).toEqual({'existingCookie':'existingValue'});
});
});
+37 -1
View File
@@ -2787,6 +2787,13 @@ describe('input', function() {
helper.changeInputValueTo('3.5');
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(3.5);
// 1.16 % 0.01 === 0.009999999999999896
// 1.16 * 100 === 115.99999999999999
$rootScope.step = 0.01;
helper.changeInputValueTo('1.16');
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(1.16);
}
);
});
@@ -3656,7 +3663,9 @@ describe('input', function() {
it('should correctly validate even in cases where the JS floating point arithmetic fails',
function() {
var inputElm = helper.compileInput('<input type="range" ng-model="value" step="0.1" />');
$rootScope.step = 0.1;
var inputElm = helper.compileInput(
'<input type="range" ng-model="value" step="{{step}}" />');
var ngModel = inputElm.controller('ngModel');
expect(inputElm.val()).toBe('');
@@ -3681,6 +3690,13 @@ describe('input', function() {
helper.changeInputValueTo('3.5');
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(3.5);
// 1.16 % 0.01 === 0.009999999999999896
// 1.16 * 100 === 115.99999999999999
$rootScope.step = 0.01;
helper.changeInputValueTo('1.16');
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(1.16);
}
);
}
@@ -4195,6 +4211,26 @@ describe('input', function() {
expect(inputElm[0].getAttribute('value')).toBe('something');
});
it('should clear the "dom" value property and attribute when the value is undefined', function() {
var inputElm = helper.compileInput('<input type="text" ng-value="value">');
$rootScope.$apply('value = "something"');
expect(inputElm[0].value).toBe('something');
expect(inputElm[0].getAttribute('value')).toBe('something');
$rootScope.$apply(function() {
delete $rootScope.value;
});
expect(inputElm[0].value).toBe('');
// Support: IE 9-11
// In IE it is not possible to remove the `value` attribute from an input element.
if (!msie) {
expect(inputElm[0].getAttribute('value')).toBeNull();
}
});
they('should update the $prop "value" property and attribute after the bound expression changes', {
input: '<input type="text" ng-value="value">',
textarea: '<textarea ng-value="value"></textarea>'
+254 -42
View File
@@ -244,21 +244,34 @@ describe('ngClass', function() {
}));
it('should allow ngClassOdd/Even on the same element with overlapping classes', inject(function($rootScope, $compile, $animate) {
var className;
element = $compile('<ul><li ng-repeat="i in [0,1,2]" ng-class-odd="\'same odd\'" ng-class-even="\'same even\'"></li><ul>')($rootScope);
it('should allow ngClassOdd/Even on the same element with overlapping classes',
inject(function($compile, $rootScope) {
element = $compile(
'<ul>' +
'<li ng-repeat="i in [0,1,2]" ' +
'ng-class-odd="\'same odd\'" ' +
'ng-class-even="\'same even\'">' +
'</li>' +
'<ul>')($rootScope);
$rootScope.$digest();
var e1 = jqLite(element[0].childNodes[1]);
var e2 = jqLite(element[0].childNodes[5]);
expect(e1.hasClass('same')).toBeTruthy();
expect(e1.hasClass('odd')).toBeTruthy();
expect(e2.hasClass('same')).toBeTruthy();
expect(e2.hasClass('odd')).toBeTruthy();
var e1 = element.children().eq(0);
var e2 = element.children().eq(1);
var e3 = element.children().eq(2);
expect(e1).toHaveClass('same');
expect(e1).toHaveClass('odd');
expect(e1).not.toHaveClass('even');
expect(e2).toHaveClass('same');
expect(e2).not.toHaveClass('odd');
expect(e2).toHaveClass('even');
expect(e3).toHaveClass('same');
expect(e3).toHaveClass('odd');
expect(e3).not.toHaveClass('even');
})
);
it('should allow ngClass with overlapping classes', inject(function($rootScope, $compile, $animate) {
it('should allow ngClass with overlapping classes', inject(function($rootScope, $compile) {
element = $compile('<div ng-class="{\'same yes\': test, \'same no\': !test}"></div>')($rootScope);
$rootScope.$digest();
@@ -266,9 +279,7 @@ describe('ngClass', function() {
expect(element).not.toHaveClass('yes');
expect(element).toHaveClass('no');
$rootScope.$apply(function() {
$rootScope.test = true;
});
$rootScope.$apply('test = true');
expect(element).toHaveClass('same');
expect(element).toHaveClass('yes');
@@ -299,38 +310,79 @@ describe('ngClass', function() {
expect(e2.hasClass('D')).toBeFalsy();
}));
it('should reapply ngClass when interpolated class attribute changes',
inject(function($compile, $rootScope) {
element = $compile(
'<div>' +
'<div class="one {{two}} three" ng-class="{five: five}"></div>' +
'<div class="one {{two}} three {{four}}" ng-class="{five: five}"></div>' +
'</div>')($rootScope);
var e1 = element.children().eq(0);
var e2 = element.children().eq(1);
it('should reapply ngClass when interpolated class attribute changes', inject(function($rootScope, $compile) {
element = $compile('<div class="one {{cls}} three" ng-class="{four: four}"></div>')($rootScope);
$rootScope.$apply('two = "two"; five = true');
$rootScope.$apply(function() {
$rootScope.cls = 'two';
$rootScope.four = true;
});
expect(element).toHaveClass('one');
expect(element).toHaveClass('two'); // interpolated
expect(element).toHaveClass('three');
expect(element).toHaveClass('four');
expect(e1).toHaveClass('one');
expect(e1).toHaveClass('two');
expect(e1).toHaveClass('three');
expect(e1).not.toHaveClass('four');
expect(e1).toHaveClass('five');
expect(e2).toHaveClass('one');
expect(e2).toHaveClass('two');
expect(e2).toHaveClass('three');
expect(e2).not.toHaveClass('four');
expect(e2).toHaveClass('five');
$rootScope.$apply(function() {
$rootScope.cls = 'too';
});
expect(element).toHaveClass('one');
expect(element).toHaveClass('too'); // interpolated
expect(element).toHaveClass('three');
expect(element).toHaveClass('four'); // should still be there
expect(element.hasClass('two')).toBeFalsy();
$rootScope.$apply('two = "another-two"');
$rootScope.$apply(function() {
$rootScope.cls = 'to';
});
expect(element).toHaveClass('one');
expect(element).toHaveClass('to'); // interpolated
expect(element).toHaveClass('three');
expect(element).toHaveClass('four'); // should still be there
expect(element.hasClass('two')).toBeFalsy();
expect(element.hasClass('too')).toBeFalsy();
}));
expect(e1).toHaveClass('one');
expect(e1).not.toHaveClass('two');
expect(e1).toHaveClass('another-two');
expect(e1).toHaveClass('three');
expect(e1).not.toHaveClass('four');
expect(e1).toHaveClass('five');
expect(e2).toHaveClass('one');
expect(e2).not.toHaveClass('two');
expect(e2).toHaveClass('another-two');
expect(e2).toHaveClass('three');
expect(e2).not.toHaveClass('four');
expect(e2).toHaveClass('five');
$rootScope.$apply('two = "two-more"; four = "four"');
expect(e1).toHaveClass('one');
expect(e1).not.toHaveClass('two');
expect(e1).not.toHaveClass('another-two');
expect(e1).toHaveClass('two-more');
expect(e1).toHaveClass('three');
expect(e1).not.toHaveClass('four');
expect(e1).toHaveClass('five');
expect(e2).toHaveClass('one');
expect(e2).not.toHaveClass('two');
expect(e2).not.toHaveClass('another-two');
expect(e2).toHaveClass('two-more');
expect(e2).toHaveClass('three');
expect(e2).toHaveClass('four');
expect(e2).toHaveClass('five');
$rootScope.$apply('five = false');
expect(e1).toHaveClass('one');
expect(e1).not.toHaveClass('two');
expect(e1).not.toHaveClass('another-two');
expect(e1).toHaveClass('two-more');
expect(e1).toHaveClass('three');
expect(e1).not.toHaveClass('four');
expect(e1).not.toHaveClass('five');
expect(e2).toHaveClass('one');
expect(e2).not.toHaveClass('two');
expect(e2).not.toHaveClass('another-two');
expect(e2).toHaveClass('two-more');
expect(e2).toHaveClass('three');
expect(e2).toHaveClass('four');
expect(e2).not.toHaveClass('five');
})
);
it('should not mess up class value due to observing an interpolated class attribute', inject(function($rootScope, $compile) {
@@ -409,6 +461,47 @@ describe('ngClass', function() {
expect(e2.hasClass('odd')).toBeFalsy();
}));
it('should add/remove the correct classes when the expression and `$index` change simultaneously',
inject(function($compile, $rootScope) {
element = $compile(
'<div>' +
'<div ng-class-odd="foo"></div>' +
'<div ng-class-even="foo"></div>' +
'</div>')($rootScope);
var odd = element.children().eq(0);
var even = element.children().eq(1);
$rootScope.$apply('$index = 0; foo = "class1"');
expect(odd).toHaveClass('class1');
expect(odd).not.toHaveClass('class2');
expect(even).not.toHaveClass('class1');
expect(even).not.toHaveClass('class2');
$rootScope.$apply('$index = 1; foo = "class2"');
expect(odd).not.toHaveClass('class1');
expect(odd).not.toHaveClass('class2');
expect(even).not.toHaveClass('class1');
expect(even).toHaveClass('class2');
$rootScope.$apply('foo = "class1"');
expect(odd).not.toHaveClass('class1');
expect(odd).not.toHaveClass('class2');
expect(even).toHaveClass('class1');
expect(even).not.toHaveClass('class2');
$rootScope.$apply('$index = 2');
expect(odd).toHaveClass('class1');
expect(odd).not.toHaveClass('class2');
expect(even).not.toHaveClass('class1');
expect(even).not.toHaveClass('class2');
})
);
it('should support mixed array/object variable with a mutating object',
inject(function($rootScope, $compile) {
element = $compile('<div ng-class="classVar"></div>')($rootScope);
@@ -424,6 +517,125 @@ describe('ngClass', function() {
})
);
it('should do value stabilization as expected when one-time binding',
inject(function($rootScope, $compile) {
element = $compile('<div ng-class="::className"></div>')($rootScope);
$rootScope.$apply('className = "foo"');
expect(element).toHaveClass('foo');
$rootScope.$apply('className = "bar"');
expect(element).toHaveClass('foo');
})
);
it('should remove the watcher when static array one-time binding',
inject(function($rootScope, $compile) {
element = $compile('<div ng-class="::[className]"></div>')($rootScope);
$rootScope.$apply('className = "foo"');
expect(element).toHaveClass('foo');
$rootScope.$apply('className = "bar"');
expect(element).toHaveClass('foo');
expect(element).not.toHaveClass('bar');
})
);
it('should remove the watcher when static map one-time binding',
inject(function($rootScope, $compile) {
element = $compile('<div ng-class="::{foo: fooPresent}"></div>')($rootScope);
$rootScope.$apply('fooPresent = true');
expect(element).toHaveClass('foo');
$rootScope.$apply('fooPresent = false');
expect(element).toHaveClass('foo');
})
);
it('should track changes of mutating object inside an array',
inject(function($rootScope, $compile) {
$rootScope.classVar = [{orange: true}];
element = $compile('<div ng-class="classVar"></div>')($rootScope);
$rootScope.$digest();
expect(element).toHaveClass('orange');
$rootScope.$apply('classVar[0].orange = false');
expect(element).not.toHaveClass('orange');
})
);
describe('large objects', function() {
var getProp;
var veryLargeObj;
beforeEach(function() {
getProp = jasmine.createSpy('getProp');
veryLargeObj = {};
Object.defineProperty(veryLargeObj, 'prop', {
get: getProp,
enumerable: true
});
});
it('should not be copied when using an expression', inject(function($compile, $rootScope) {
element = $compile('<div ng-class="fooClass"></div>')($rootScope);
$rootScope.fooClass = {foo: veryLargeObj};
$rootScope.$digest();
expect(element).toHaveClass('foo');
expect(getProp).not.toHaveBeenCalled();
}));
it('should not be copied when using a literal', inject(function($compile, $rootScope) {
element = $compile('<div ng-class="{foo: veryLargeObj}"></div>')($rootScope);
$rootScope.veryLargeObj = veryLargeObj;
$rootScope.$digest();
expect(element).toHaveClass('foo');
expect(getProp).not.toHaveBeenCalled();
}));
it('should not be copied when inside an array', inject(function($compile, $rootScope) {
element = $compile('<div ng-class="[{foo: veryLargeObj}]"></div>')($rootScope);
$rootScope.veryLargeObj = veryLargeObj;
$rootScope.$digest();
expect(element).toHaveClass('foo');
expect(getProp).not.toHaveBeenCalled();
}));
it('should not be copied when using one-time binding', inject(function($compile, $rootScope) {
element = $compile('<div ng-class="::{foo: veryLargeObj, bar: bar}"></div>')($rootScope);
$rootScope.veryLargeObj = veryLargeObj;
$rootScope.$digest();
expect(element).toHaveClass('foo');
expect(element).not.toHaveClass('bar');
expect(getProp).not.toHaveBeenCalled();
$rootScope.$apply('veryLargeObj.bar = "bar"');
expect(element).toHaveClass('foo');
expect(element).not.toHaveClass('bar');
expect(getProp).not.toHaveBeenCalled();
$rootScope.$apply('bar = "bar"');
expect(element).toHaveClass('foo');
expect(element).toHaveClass('bar');
expect(getProp).not.toHaveBeenCalled();
$rootScope.$apply('veryLargeObj.bar = "qux"');
expect(element).toHaveClass('foo');
expect(element).toHaveClass('bar');
expect(getProp).not.toHaveBeenCalled();
}));
});
});
describe('ngClass animations', function() {
File diff suppressed because it is too large Load Diff
+42
View File
@@ -1337,6 +1337,48 @@ describe('ngModel', function() {
});
});
describe('override ModelOptions', function() {
it('should replace the previous model options', function() {
var $options = ctrl.$options;
ctrl.$overrideModelOptions({});
expect(ctrl.$options).not.toBe($options);
});
it('should set the given options', function() {
var $options = ctrl.$options;
ctrl.$overrideModelOptions({ debounce: 1000, updateOn: 'blur' });
expect(ctrl.$options.getOption('debounce')).toEqual(1000);
expect(ctrl.$options.getOption('updateOn')).toEqual('blur');
expect(ctrl.$options.getOption('updateOnDefault')).toBe(false);
});
it('should inherit from a parent model options if specified', inject(function($compile, $rootScope) {
var element = $compile(
'<form name="form" ng-model-options="{debounce: 1000, updateOn: \'blur\'}">' +
' <input ng-model="value" name="input">' +
'</form>')($rootScope);
var ctrl = $rootScope.form.input;
ctrl.$overrideModelOptions({ debounce: 2000, '*': '$inherit' });
expect(ctrl.$options.getOption('debounce')).toEqual(2000);
expect(ctrl.$options.getOption('updateOn')).toEqual('blur');
expect(ctrl.$options.getOption('updateOnDefault')).toBe(false);
dealoc(element);
}));
it('should not inherit from a parent model options if not specified', inject(function($compile, $rootScope) {
var element = $compile(
'<form name="form" ng-model-options="{debounce: 1000, updateOn: \'blur\'}">' +
' <input ng-model="value" name="input">' +
'</form>')($rootScope);
var ctrl = $rootScope.form.input;
ctrl.$overrideModelOptions({ debounce: 2000 });
expect(ctrl.$options.getOption('debounce')).toEqual(2000);
expect(ctrl.$options.getOption('updateOn')).toEqual('');
expect(ctrl.$options.getOption('updateOnDefault')).toBe(true);
dealoc(element);
}));
});
});
+81
View File
@@ -2827,6 +2827,7 @@ describe('ngOptions', function() {
expect(scope.selected).toEqual(['0']);
});
it('should deselect all options when model is emptied', function() {
createMultiSelect();
scope.$apply(function() {
@@ -2841,6 +2842,86 @@ describe('ngOptions', function() {
expect(element.find('option')[0].selected).toEqual(false);
});
it('should not re-set the `selected` property if it already has the correct value', function() {
scope.values = [{name: 'A'}, {name: 'B'}];
createMultiSelect();
var options = element.find('option');
var optionsSetSelected = [];
var _selected = [];
// Set up spies
forEach(options, function(option, i) {
optionsSetSelected[i] = jasmine.createSpy('optionSetSelected' + i);
_selected[i] = option.selected;
Object.defineProperty(option, 'selected', {
get: function() { return _selected[i]; },
set: optionsSetSelected[i].and.callFake(function(value) { _selected[i] = value; })
});
});
// Select `optionA`
scope.$apply('selected = [values[0]]');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(true);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Select `optionB` (`optionA` remains selected)
scope.$apply('selected.push(values[1])');
expect(optionsSetSelected[0]).not.toHaveBeenCalled();
expect(optionsSetSelected[1]).toHaveBeenCalledOnceWith(true);
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionA` (`optionB` remains selected)
scope.$apply('selected.shift()');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(false);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(false);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Reselect `optionA` (`optionB` remains selected)
scope.$apply('selected.push(values[0])');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(true);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(true);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionB` (`optionA` remains selected)
scope.$apply('selected.shift()');
expect(optionsSetSelected[0]).not.toHaveBeenCalled();
expect(optionsSetSelected[1]).toHaveBeenCalledOnceWith(false);
expect(options[0].selected).toBe(true);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
// Unselect `optionA`
scope.$apply('selected.length = 0');
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(false);
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
expect(options[0].selected).toBe(false);
expect(options[1].selected).toBe(false);
optionsSetSelected[0].calls.reset();
optionsSetSelected[1].calls.reset();
});
});
+44 -10
View File
@@ -1529,8 +1529,8 @@ describe('select', function() {
'number:1',
'boolean:true',
'object:null',
'object:3',
'object:4',
'object:5',
'number:NaN'
);
@@ -1555,7 +1555,7 @@ describe('select', function() {
browserTrigger(element, 'change');
var arrayVal = ['a'];
arrayVal.$$hashKey = 'object:5';
arrayVal.$$hashKey = 'object:4';
expect(scope.selected).toEqual([
'string',
@@ -1563,7 +1563,7 @@ describe('select', function() {
1,
true,
null,
{prop: 'value', $$hashKey: 'object:4'},
{prop: 'value', $$hashKey: 'object:3'},
arrayVal,
NaN
]);
@@ -1876,10 +1876,10 @@ describe('select', function() {
scope.$digest();
optionElements = element.find('option');
expect(element.val()).toBe(prop === 'ngValue' ? 'object:4' : 'C');
expect(element.val()).toBe(prop === 'ngValue' ? 'object:3' : 'C');
expect(optionElements.length).toEqual(3);
expect(optionElements[2].selected).toBe(true);
expect(scope.obj.value).toEqual(prop === 'ngValue' ? {name: 'C', $$hashKey: 'object:4'} : 'C');
expect(scope.obj.value).toEqual(prop === 'ngValue' ? {name: 'C', $$hashKey: 'object:3'} : 'C');
});
@@ -2188,9 +2188,9 @@ describe('select', function() {
expect(optionElements.length).toEqual(4);
expect(scope.obj.value).toEqual(prop === 'ngValue' ?
[
{name: 'A', $$hashKey: 'object:4', disabled: true},
{name: 'C', $$hashKey: 'object:6'},
{name: 'D', $$hashKey: 'object:7', disabled: true}
{name: 'A', $$hashKey: 'object:3', disabled: true},
{name: 'C', $$hashKey: 'object:5'},
{name: 'D', $$hashKey: 'object:6', disabled: true}
] :
['A', 'C', 'D']
);
@@ -2242,13 +2242,13 @@ describe('select', function() {
scope.$digest();
optionElements = element.find('option');
expect(element.val()).toEqual(prop === 'ngValue' ? ['object:4', 'object:5'] : ['B', 'C']);
expect(element.val()).toEqual(prop === 'ngValue' ? ['object:4', 'object:7'] : ['B', 'C']);
expect(optionElements.length).toEqual(3);
expect(optionElements[1].selected).toBe(true);
expect(optionElements[2].selected).toBe(true);
expect(scope.obj.value).toEqual(prop === 'ngValue' ?
[{ name: 'B', $$hashKey: 'object:4'},
{name: 'C', $$hashKey: 'object:5'}] :
{name: 'C', $$hashKey: 'object:7'}] :
['B', 'C']);
});
@@ -2316,6 +2316,40 @@ describe('select', function() {
});
it('should keep the ngModel value when the selected option is recreated by ngRepeat', function() {
scope.options = [{ name: 'A'}, { name: 'B'}, { name: 'C'}];
scope.obj = {
value: 'B'
};
compile(
'<select ng-model="obj.value">' +
'<option ng-repeat="option in options" value="{{option.name}}">{{option.name}}</option>' +
'</select>'
);
var optionElements = element.find('option');
expect(optionElements.length).toEqual(3);
expect(optionElements[0].value).toBe('A');
expect(optionElements[1]).toBeMarkedAsSelected();
expect(scope.obj.value).toBe('B');
scope.$apply(function() {
// Only when new objects are used, ngRepeat re-creates the element from scratch
scope.options = [{ name: 'B'}, { name: 'C'}, { name: 'D'}];
});
var previouslySelectedOptionElement = optionElements[1];
optionElements = element.find('option');
expect(optionElements.length).toEqual(3);
expect(optionElements[0].value).toBe('B');
expect(optionElements[0]).toBeMarkedAsSelected();
expect(scope.obj.value).toBe('B');
// Ensure the assumption that the element is re-created is true
expect(previouslySelectedOptionElement).not.toBe(optionElements[0]);
});
});
+71 -5
View File
@@ -710,6 +710,7 @@ describe('$location', function() {
);
});
it('should not infinitely digest when using a semicolon in initial path', function() {
initService({html5Mode:true,supportHistory:true});
mockUpBrowser({initialUrl:'http://localhost:9876/;jsessionid=foo', baseHref:'/'});
@@ -721,6 +722,63 @@ describe('$location', function() {
});
describe('when changing the browser URL/history directly during a `$digest`', function() {
beforeEach(function() {
initService({supportHistory: true});
mockUpBrowser({initialUrl: 'http://foo.bar/', baseHref: '/'});
});
it('should correctly update `$location` from history and not digest infinitely', inject(
function($browser, $location, $rootScope, $window) {
$location.url('baz');
$rootScope.$digest();
var originalUrl = $window.location.href;
$rootScope.$apply(function() {
$rootScope.$evalAsync(function() {
$window.history.pushState({}, null, originalUrl + '/qux');
});
});
expect($browser.url()).toBe('http://foo.bar/#!/baz/qux');
expect($location.absUrl()).toBe('http://foo.bar/#!/baz/qux');
$rootScope.$apply(function() {
$rootScope.$evalAsync(function() {
$window.history.replaceState({}, null, originalUrl + '/quux');
});
});
expect($browser.url()).toBe('http://foo.bar/#!/baz/quux');
expect($location.absUrl()).toBe('http://foo.bar/#!/baz/quux');
})
);
it('should correctly update `$location` from URL and not digest infinitely', inject(
function($browser, $location, $rootScope, $window) {
$location.url('baz');
$rootScope.$digest();
$rootScope.$apply(function() {
$rootScope.$evalAsync(function() {
$window.location.href += '/qux';
});
});
jqLite($window).triggerHandler('hashchange');
expect($browser.url()).toBe('http://foo.bar/#!/baz/qux');
expect($location.absUrl()).toBe('http://foo.bar/#!/baz/qux');
})
);
});
function updatePathOnLocationChangeSuccessTo(newPath) {
inject(function($rootScope, $location) {
$rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) {
@@ -1039,11 +1097,21 @@ describe('$location', function() {
});
it('should update $location when browser state changes', function() {
initService({html5Mode:true, supportHistory: true});
mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'});
inject(function($location, $window) {
initService({html5Mode: true, supportHistory: true});
mockUpBrowser({initialUrl: 'http://new.com/a/b/', baseHref: '/a/b/'});
inject(function($location, $rootScope, $window) {
$window.history.pushState({b: 3});
$rootScope.$digest();
expect($location.state()).toEqual({b: 3});
$window.history.pushState({b: 4}, null, $window.location.href + 'c?d=e#f');
$rootScope.$digest();
expect($location.path()).toBe('/c');
expect($location.search()).toEqual({d: 'e'});
expect($location.hash()).toBe('f');
expect($location.state()).toEqual({b: 4});
});
});
@@ -2666,12 +2734,10 @@ describe('$location', function() {
replaceState: function(state, title, url) {
win.history.state = copy(state);
if (url) win.location.href = url;
jqLite(win).triggerHandler('popstate');
},
pushState: function(state, title, url) {
win.history.state = copy(state);
if (url) win.location.href = url;
jqLite(win).triggerHandler('popstate');
}
};
win.addEventListener = angular.noop;
+101 -19
View File
@@ -2635,25 +2635,6 @@ describe('parser', function() {
expect(log).toEqual('');
}));
it('should work with expensive checks', inject(function($parse, $rootScope, log) {
var fn = $parse('::foo', null, true);
$rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
$rootScope.$digest();
expect($rootScope.$$watchers.length).toBe(1);
$rootScope.foo = 'bar';
$rootScope.$digest();
expect($rootScope.$$watchers.length).toBe(0);
expect(log).toEqual('bar');
log.reset();
$rootScope.foo = 'man';
$rootScope.$digest();
expect($rootScope.$$watchers.length).toBe(0);
expect(log).toEqual('');
}));
it('should have a stable value if at the end of a $digest it has a defined value', inject(function($parse, $rootScope, log) {
var fn = $parse('::foo');
$rootScope.$watch(fn, function(value, old) { if (value !== old) log(value); });
@@ -3118,6 +3099,107 @@ describe('parser', function() {
scope.$digest();
expect(objB.value).toBe(scope.input);
}));
it('should watch ES6 object computed property changes', function() {
var count = 0;
var values = [];
scope.$watch('{[a]: true}', function(val) {
count++;
values.push(val);
}, true);
scope.$digest();
expect(count).toBe(1);
expect(values[0]).toEqual({'undefined': true});
scope.$digest();
expect(count).toBe(1);
expect(values[0]).toEqual({'undefined': true});
scope.a = true;
scope.$digest();
expect(count).toBe(2);
expect(values[1]).toEqual({'true': true});
scope.a = 'abc';
scope.$digest();
expect(count).toBe(3);
expect(values[2]).toEqual({'abc': true});
scope.a = undefined;
scope.$digest();
expect(count).toBe(4);
expect(values[3]).toEqual({'undefined': true});
});
it('should support watching literals', inject(function($parse) {
var lastVal = NaN;
var callCount = 0;
var listener = function(val) { callCount++; lastVal = val; };
scope.$watch('{val: val}', listener);
scope.$apply('val = 1');
expect(callCount).toBe(1);
expect(lastVal).toEqual({val: 1});
scope.$apply('val = []');
expect(callCount).toBe(2);
expect(lastVal).toEqual({val: []});
scope.$apply('val = []');
expect(callCount).toBe(3);
expect(lastVal).toEqual({val: []});
scope.$apply('val = {}');
expect(callCount).toBe(4);
expect(lastVal).toEqual({val: {}});
}));
it('should only watch the direct inputs to literals', inject(function($parse) {
var lastVal = NaN;
var callCount = 0;
var listener = function(val) { callCount++; lastVal = val; };
scope.$watch('{val: val}', listener);
scope.$apply('val = 1');
expect(callCount).toBe(1);
expect(lastVal).toEqual({val: 1});
scope.$apply('val = [2]');
expect(callCount).toBe(2);
expect(lastVal).toEqual({val: [2]});
scope.$apply('val.push(3)');
expect(callCount).toBe(2);
scope.$apply('val.length = 0');
expect(callCount).toBe(2);
}));
it('should only watch the direct inputs to nested literals', inject(function($parse) {
var lastVal = NaN;
var callCount = 0;
var listener = function(val) { callCount++; lastVal = val; };
scope.$watch('[{val: [val]}]', listener);
scope.$apply('val = 1');
expect(callCount).toBe(1);
expect(lastVal).toEqual([{val: [1]}]);
scope.$apply('val = [2]');
expect(callCount).toBe(2);
expect(lastVal).toEqual([{val: [[2]]}]);
scope.$apply('val.push(3)');
expect(callCount).toBe(2);
scope.$apply('val.length = 0');
expect(callCount).toBe(2);
}));
});
describe('locals', function() {
+90 -39
View File
@@ -181,16 +181,14 @@ describe('q', function() {
};
function exceptionHandler(reason) {
exceptionHandlerCalls.push(reason);
function exceptionHandler(exception, reason) {
if (typeof reason === 'undefined') {
exceptionHandlerCalls.push({ reason: exception });
} else {
exceptionHandlerCalls.push({ reason: reason, exception: exception });
}
}
function exceptionHandlerStr() {
return exceptionHandlerCalls.join('; ');
}
beforeEach(function() {
q = qFactory(mockNextTick.nextTick, exceptionHandler, true);
q_no_error = qFactory(mockNextTick.nextTick, exceptionHandler, false);
@@ -2167,45 +2165,98 @@ describe('q', function() {
describe('when exceptionHandler is called', function() {
it('should log an unhandled rejected promise', function() {
var defer = q.defer();
defer.reject('foo');
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('Possibly unhandled rejection: foo');
});
function CustomError() { }
CustomError.prototype = Object.create(Error.prototype);
var errorEg = new Error('Fail');
var errorStr = toDebugString(errorEg);
var customError = new CustomError('Custom');
var customErrorStr = toDebugString(customError);
var nonErrorObj = { isATest: 'this is' };
var nonErrorObjStr = toDebugString(nonErrorObj);
var fixtures = [
{
type: 'Error object',
value: errorEg,
expected: {
exception: errorEg,
reason: 'Possibly unhandled rejection: ' + errorStr
}
},
{
type: 'custom Error object',
value: customError,
expected: {
exception: customError,
reason: 'Possibly unhandled rejection: ' + customErrorStr
}
},
{
type: 'non-Error object',
value: nonErrorObj,
expected: {
reason: 'Possibly unhandled rejection: ' + nonErrorObjStr
}
},
{
type: 'string primitive',
value: 'foo',
expected: {
reason: 'Possibly unhandled rejection: foo'
}
}
];
forEach(fixtures, function(fixture) {
var type = fixture.type;
var value = fixture.value;
var expected = fixture.expected;
describe('with ' + type, function() {
it('should log an unhandled rejected promise', function() {
var defer = q.defer();
defer.reject(value);
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([expected]);
});
it('should not log an unhandled rejected promise if disabled', function() {
var defer = q_no_error.defer();
defer.reject('foo');
expect(exceptionHandlerStr()).toBe('');
});
it('should not log an unhandled rejected promise if disabled', function() {
var defer = q_no_error.defer();
defer.reject(value);
expect(exceptionHandlerCalls).toEqual([]);
});
it('should log a handled rejected promise on a promise without rejection callbacks', function() {
var defer = q.defer();
defer.promise.then(noop);
defer.reject('foo');
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('Possibly unhandled rejection: foo');
});
it('should log a handled rejected promise on a promise without rejection callbacks', function() {
var defer = q.defer();
defer.promise.then(noop);
defer.reject(value);
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([expected]);
});
it('should not log a handled rejected promise', function() {
var defer = q.defer();
defer.promise.catch(noop);
defer.reject('foo');
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('');
});
it('should not log a handled rejected promise', function() {
var defer = q.defer();
defer.promise.catch(noop);
defer.reject(value);
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([]);
});
it('should not log a handled rejected promise that is handled in a future tick', function() {
var defer = q.defer();
defer.promise.catch(noop);
defer.resolve(q.reject('foo'));
mockNextTick.flush();
expect(exceptionHandlerStr()).toBe('');
it('should not log a handled rejected promise that is handled in a future tick', function() {
var defer = q.defer();
defer.promise.catch(noop);
defer.resolve(q.reject(value));
mockNextTick.flush();
expect(exceptionHandlerCalls).toEqual([]);
});
});
});
});
});
+74 -3
View File
@@ -498,6 +498,78 @@ describe('Scope', function() {
expect(watch2).toHaveBeenCalled();
}));
it('should not skip watchers when adding new watchers during digest',
inject(function($rootScope) {
var log = [];
var watchFn1 = function() { log.push(1); };
var watchFn2 = function() { log.push(2); };
var watchFn3 = function() { log.push(3); };
var addWatcherOnce = function(newValue, oldValue) {
if (newValue === oldValue) {
$rootScope.$watch(watchFn3);
}
};
$rootScope.$watch(watchFn1, addWatcherOnce);
$rootScope.$watch(watchFn2);
$rootScope.$digest();
expect(log).toEqual([1, 2, 3, 1, 2, 3]);
})
);
it('should not run the current watcher twice when removing a watcher during digest',
inject(function($rootScope) {
var log = [];
var removeWatcher3;
var watchFn3 = function() { log.push(3); };
var watchFn2 = function() { log.push(2); };
var watchFn1 = function() { log.push(1); };
var removeWatcherOnce = function(newValue, oldValue) {
if (newValue === oldValue) {
removeWatcher3();
}
};
$rootScope.$watch(watchFn1, removeWatcherOnce);
$rootScope.$watch(watchFn2);
removeWatcher3 = $rootScope.$watch(watchFn3);
$rootScope.$digest();
expect(log).toEqual([1, 2, 1, 2]);
})
);
it('should not skip watchers when removing itself during digest',
inject(function($rootScope) {
var log = [];
var removeWatcher1;
var watchFn3 = function() { log.push(3); };
var watchFn2 = function() { log.push(2); };
var watchFn1 = function() { log.push(1); };
var removeItself = function() {
removeWatcher1();
};
removeWatcher1 = $rootScope.$watch(watchFn1, removeItself);
$rootScope.$watch(watchFn2);
$rootScope.$watch(watchFn3);
$rootScope.$digest();
expect(log).toEqual([1, 2, 3, 2, 3]);
})
);
it('should not infinitely digest when current value is NaN', inject(function($rootScope) {
$rootScope.$watch(function() { return NaN;});
@@ -598,7 +670,7 @@ describe('Scope', function() {
$rootScope.$digest();
expect(log).toEqual(['watch1', 'watchAction1', 'watch1', 'watch3', 'watchAction3',
expect(log).toEqual(['watch1', 'watchAction1', 'watch3', 'watchAction3',
'watch1', 'watch3']);
scope.$destroy();
log.reset();
@@ -895,8 +967,7 @@ describe('Scope', function() {
$rootScope.$watch(log.fn('w5'), log.fn('w5action'));
});
$rootScope.$digest();
expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action',
'w1', 'w2', 'w3', 'w4', 'w5', 'w5action',
expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action', 'w5', 'w5action',
'w1', 'w2', 'w3', 'w4', 'w5']);
}));
+19
View File
@@ -45,6 +45,25 @@ describe('$sniffer', function() {
});
it('should be true on NW.js apps (which look similar to Chrome Packaged Apps)', function() {
var mockWindow = {
history: {
pushState: noop
},
chrome: {
app: {
runtime: {}
}
},
nw: {
process: {}
}
};
expect(sniffer(mockWindow).history).toBe(true);
});
it('should be false on Chrome Packaged Apps', function() {
// Chrome Packaged Apps are not allowed to access `window.history.pushState`.
// In Chrome, `window.app` might be available in "normal" webpages, but `window.app.runtime`
+67 -47
View File
@@ -168,7 +168,7 @@ describe('animations', function() {
inject(function($animate, $rootScope) {
$animate.enter(element, parent);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
expect(element[0].parentNode).toEqual(parent[0]);
hidden = false;
@@ -188,7 +188,7 @@ describe('animations', function() {
$animate.enter(element, parent);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
element.addClass('only-allow-this-animation');
@@ -208,7 +208,7 @@ describe('animations', function() {
$animate.enter(svgElement, parent);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
svgElement.attr('class', 'element only-allow-this-animation-svg');
@@ -290,7 +290,7 @@ describe('animations', function() {
$animate.leave(element);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
expect(element[0].parentNode).toBeFalsy();
});
});
@@ -314,9 +314,9 @@ describe('animations', function() {
$animate.enter(element, parent);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('should disable all animations on the given element',
@@ -328,15 +328,15 @@ describe('animations', function() {
expect($animate.enabled(element)).toBeFalsy();
$animate.addClass(element, 'red');
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.enabled(element, true);
expect($animate.enabled(element)).toBeTruthy();
$animate.addClass(element, 'blue');
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
}));
@@ -347,14 +347,14 @@ describe('animations', function() {
$animate.enabled(parent, false);
$animate.enter(element, parent);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.enabled(parent, true);
$animate.enter(element, parent);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$rootScope.$digest();
expect(capturedAnimation).toBeTruthy();
}));
@@ -370,11 +370,11 @@ describe('animations', function() {
$animate.addClass(element, 'red');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.addClass(child, 'red');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.enabled(element, true);
@@ -402,7 +402,7 @@ describe('animations', function() {
$rootScope.items = [1,2,3,4,5];
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('should not attempt to perform an animation on a text node element',
@@ -414,7 +414,7 @@ describe('animations', function() {
$animate.addClass(textNode, 'some-class');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('should not attempt to perform an animation on an empty jqLite collection',
@@ -426,7 +426,7 @@ describe('animations', function() {
$animate.addClass(emptyNode, 'some-class');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
})
);
@@ -439,7 +439,7 @@ describe('animations', function() {
$animate.leave(textNode);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
expect(textNode[0].parentNode).not.toBe(parentNode);
}));
@@ -455,10 +455,32 @@ describe('animations', function() {
$animate.leave(commentNode);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
expect(commentNode[0].parentNode).not.toBe(parentNode);
}));
it('enter() should animate a transcluded clone with `templateUrl`', function() {
module(function($compileProvider) {
$compileProvider.directive('foo', function() {
return {templateUrl: 'foo.html'};
});
});
inject(function($animate, $compile, $rootScope, $templateCache) {
parent.append(jqLite('<foo ng-if="showFoo"></foo>'));
$templateCache.put('foo.html', '<div>FOO</div>');
$compile(parent)($rootScope);
expect(capturedAnimation).toBeNull();
$rootScope.$apply('showFoo = true');
expect(parent.text()).toBe('parentFOO');
expect(capturedAnimation[0].html()).toBe('<div>FOO</div>');
expect(capturedAnimation[1]).toBe('enter');
});
});
it('enter() should issue an enter animation and fire the DOM operation right away before the animation kicks off', inject(function($animate, $rootScope) {
expect(parent.children().length).toBe(0);
@@ -667,13 +689,13 @@ describe('animations', function() {
$animate.removeClass(element, 'something-to-remove');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
element.addClass('something-to-add');
$animate.addClass(element, 'something-to-add');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
});
@@ -689,7 +711,7 @@ describe('animations', function() {
parent.append(element);
$animate.animate(element, null, toStyle);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
});
});
@@ -700,7 +722,7 @@ describe('animations', function() {
parent.append(element);
$animate.animate(element, fromStyle);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('should perform an animation if only from styles are provided as well as any valid classes',
@@ -712,7 +734,7 @@ describe('animations', function() {
var options = { removeClass: 'goop' };
$animate.animate(element, fromStyle, null, null, options);
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
fromStyle = { color: 'blue' };
options = { addClass: 'goop' };
@@ -816,11 +838,11 @@ describe('animations', function() {
var elm1 = $compile('<div class="animated"></div>')($rootScope);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.addClass(elm1, 'klass2');
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('should skip animations if the element is attached to the $rootElement, but not apart of the body',
@@ -834,22 +856,22 @@ describe('animations', function() {
newParent.append($rootElement);
$rootElement.append(elm1);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.addClass(elm1, 'klass2');
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('should skip the animation if the element is removed from the DOM before the post digest kicks in',
inject(function($animate, $rootScope) {
$animate.enter(element, parent);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
element.remove();
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('should be blocked when there is an ongoing structural parent animation occurring',
@@ -857,7 +879,7 @@ describe('animations', function() {
parent.append(element);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.move(parent, parent2);
$rootScope.$digest();
@@ -867,7 +889,7 @@ describe('animations', function() {
$animate.addClass(element, 'blue');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('should disable all child animations for atleast one turn when a structural animation is issued',
@@ -917,7 +939,7 @@ describe('animations', function() {
parent.append(element);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.addClass(parent, 'rogers');
$rootScope.$digest();
@@ -940,7 +962,7 @@ describe('animations', function() {
$animate.addClass(element, 'rumlow');
$animate.move(parent, null, parent2);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
expect(capturedAnimationHistory.length).toBe(0);
$rootScope.$digest();
@@ -1193,12 +1215,12 @@ describe('animations', function() {
inject(function($animate, $rootScope) {
$animate.enter(element, parent);
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.addClass(element, 'red');
expect(element).not.toHaveClass('red');
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$rootScope.$digest();
expect(capturedAnimation[1]).toBe('enter');
@@ -1227,7 +1249,7 @@ describe('animations', function() {
$animate.removeClass(element, 'red');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.addClass(element, 'blue');
$rootScope.$digest();
@@ -1344,7 +1366,7 @@ describe('animations', function() {
$animate.removeClass(element, 'four');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
}));
it('but not skip the animation if it is a structural animation and if there are no classes to be animated',
@@ -1385,7 +1407,7 @@ describe('animations', function() {
expect(capturedAnimation[1]).toBe('leave');
// $$hashKey causes comparison issues
expect(element.parent()[0]).toEqual(parent[0]);
expect(element.parent()[0]).toBe(parent[0]);
options = capturedAnimation[2];
expect(options.addClass).toEqual('pink');
@@ -1632,7 +1654,7 @@ describe('animations', function() {
$animate.addClass(animateElement, 'red');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
// Pin the element to the app root to enable animations
$animate.pin(pinElement, $rootElement);
@@ -1676,13 +1698,13 @@ describe('animations', function() {
$animate.addClass(animateElement, 'red');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.pin(pinElement, pinTargetElement);
$animate.addClass(animateElement, 'blue');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
dealoc(pinElement);
});
@@ -1720,7 +1742,7 @@ describe('animations', function() {
$animate.addClass(animateElement, 'blue');
$rootScope.$digest();
expect(capturedAnimation).toBeFalsy();
expect(capturedAnimation).toBeNull();
$animate.enabled(pinHostElement, true);
@@ -2437,7 +2459,6 @@ describe('animations', function() {
return function($rootElement, $q, $animate, $$AnimateRunner, $document) {
defaultFakeAnimationRunner = new $$AnimateRunner();
$animate.enabled(true);
element = jqLite('<div class="element">element</div>');
parent = jqLite('<div class="parent1">parent</div>');
@@ -2445,7 +2466,6 @@ describe('animations', function() {
$rootElement.append(parent);
$rootElement.append(parent2);
jqLite($document[0].body).append($rootElement);
};
}));
+12 -19
View File
@@ -795,23 +795,6 @@ describe('ngMock', function() {
});
});
describe('module cleanup', function() {
function testFn() {
}
it('should add hashKey to module function', function() {
module(testFn);
inject(function() {
expect(testFn.$$hashKey).toBeDefined();
});
});
it('should cleanup hashKey after previous test', function() {
expect(testFn.$$hashKey).toBeUndefined();
});
});
describe('$inject cleanup', function() {
function testFn() {
@@ -2432,13 +2415,15 @@ describe('ngMock', function() {
describe('ngMockE2E', function() {
describe('$httpBackend', function() {
var hb, realHttpBackend, callback;
var hb, realHttpBackend, realHttpBackendBrowser, callback;
beforeEach(function() {
callback = jasmine.createSpy('callback');
angular.module('ng').config(function($provide) {
realHttpBackend = jasmine.createSpy('real $httpBackend');
$provide.value('$httpBackend', realHttpBackend);
$provide.factory('$httpBackend', ['$browser', function($browser) {
return realHttpBackend.and.callFake(function() { realHttpBackendBrowser = $browser; });
}]);
});
module('ngMockE2E');
inject(function($injector) {
@@ -2477,6 +2462,14 @@ describe('ngMockE2E', function() {
expect(realHttpBackend).not.toHaveBeenCalled();
expect(callback).toHaveBeenCalledOnceWith(200, 'passThrough override', '', '');
}));
it('should pass through to an httpBackend that uses the same $browser service', inject(function($browser) {
hb.when('GET', /\/passThrough\/.*/).passThrough();
hb('GET', '/passThrough/23');
expect(realHttpBackend).toHaveBeenCalledOnce();
expect(realHttpBackendBrowser).toBe($browser);
}));
});
+100
View File
@@ -777,6 +777,24 @@ describe('basic usage', function() {
expect(json).toEqual({id: 123, number: '9876', $myProp: 'still here'});
});
it('should not include $cancelRequest when resource is toJson\'ed', function() {
$httpBackend.whenGET('/CreditCard').respond({});
var CreditCard = $resource('/CreditCard', {}, {
get: {
method: 'GET',
cancellable: true
}
});
var card = CreditCard.get();
var json = card.toJSON();
expect(card.$cancelRequest).toBeDefined();
expect(json.$cancelRequest).toBeUndefined();
});
describe('promise api', function() {
var $rootScope;
@@ -1438,6 +1456,18 @@ describe('basic usage', function() {
$httpBackend.expect('POST', '/users/.json').respond();
$resource('/users/\\.json').save({});
});
it('should work with save() if dynamic params', function() {
$httpBackend.expect('POST', '/users/.json').respond();
$resource('/users/:json', {json: '\\.json'}).save({});
});
it('should work with query() if dynamic params', function() {
$httpBackend.expect('GET', '/users/.json').respond();
$resource('/users/:json', {json: '\\.json'}).query();
});
it('should work with get() if dynamic params', function() {
$httpBackend.expect('GET', '/users/.json').respond();
$resource('/users/:json', {json: '\\.json'}).get();
});
});
});
@@ -1691,6 +1721,76 @@ describe('handling rejections', function() {
expect($exceptionHandler.errors[0]).toMatch(/^Possibly unhandled rejection/);
})
);
it('should not swallow exceptions in success callback when error callback is provided',
function() {
$httpBackend.expectGET('/CreditCard/123').respond(null);
var CreditCard = $resource('/CreditCard/:id');
var cc = CreditCard.get({id: 123},
function(res) { throw new Error('should be caught'); },
function() {});
$httpBackend.flush();
expect($exceptionHandler.errors.length).toBe(1);
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
}
);
it('should not swallow exceptions in success callback when error callback is not provided',
function() {
$httpBackend.expectGET('/CreditCard/123').respond(null);
var CreditCard = $resource('/CreditCard/:id');
var cc = CreditCard.get({id: 123},
function(res) { throw new Error('should be caught'); });
$httpBackend.flush();
expect($exceptionHandler.errors.length).toBe(1);
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
}
);
it('should not swallow exceptions in success callback when error callback is provided and has responseError interceptor',
function() {
$httpBackend.expectGET('/CreditCard/123').respond(null);
var CreditCard = $resource('/CreditCard/:id', null, {
get: {
method: 'GET',
interceptor: {responseError: function() {}}
}
});
var cc = CreditCard.get({id: 123},
function(res) { throw new Error('should be caught'); },
function() {});
$httpBackend.flush();
expect($exceptionHandler.errors.length).toBe(1);
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
}
);
it('should not swallow exceptions in success callback when error callback is not provided and has responseError interceptor',
function() {
$httpBackend.expectGET('/CreditCard/123').respond(null);
var CreditCard = $resource('/CreditCard/:id', null, {
get: {
method: 'GET',
interceptor: {responseError: function() {}}
}
});
var cc = CreditCard.get({id: 123},
function(res) { throw new Error('should be caught'); });
$httpBackend.flush();
expect($exceptionHandler.errors.length).toBe(1);
expect($exceptionHandler.errors[0]).toMatch(/^Error: should be caught/);
}
);
});
describe('cancelling requests', function() {
+184
View File
@@ -2082,4 +2082,188 @@ describe('$route', function() {
expect(function() { $route.updateParams(); }).toThrowMinErr('ngRoute', 'norout');
}));
});
describe('testability', function() {
it('should wait for $resolve promises before calling callbacks', function() {
var deferred;
module(function($provide, $routeProvider) {
$routeProvider.when('/path', {
template: '',
resolve: {
a: function($q) {
deferred = $q.defer();
return deferred.promise;
}
}
});
});
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
$location.path('/path');
$rootScope.$digest();
var callback = jasmine.createSpy('callback');
$$testability.whenStable(callback);
expect(callback).not.toHaveBeenCalled();
deferred.resolve();
$rootScope.$digest();
expect(callback).toHaveBeenCalled();
});
});
it('should call callback after $resolve promises are rejected', function() {
var deferred;
module(function($provide, $routeProvider) {
$routeProvider.when('/path', {
template: '',
resolve: {
a: function($q) {
deferred = $q.defer();
return deferred.promise;
}
}
});
});
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
$location.path('/path');
$rootScope.$digest();
var callback = jasmine.createSpy('callback');
$$testability.whenStable(callback);
expect(callback).not.toHaveBeenCalled();
deferred.reject();
$rootScope.$digest();
expect(callback).toHaveBeenCalled();
});
});
it('should wait for resolveRedirectTo promises before calling callbacks', function() {
var deferred;
module(function($provide, $routeProvider) {
$routeProvider.when('/path', {
resolveRedirectTo: function($q) {
deferred = $q.defer();
return deferred.promise;
}
});
});
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
$location.path('/path');
$rootScope.$digest();
var callback = jasmine.createSpy('callback');
$$testability.whenStable(callback);
expect(callback).not.toHaveBeenCalled();
deferred.resolve();
$rootScope.$digest();
expect(callback).toHaveBeenCalled();
});
});
it('should call callback after resolveRedirectTo promises are rejected', function() {
var deferred;
module(function($provide, $routeProvider) {
$routeProvider.when('/path', {
resolveRedirectTo: function($q) {
deferred = $q.defer();
return deferred.promise;
}
});
});
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
$location.path('/path');
$rootScope.$digest();
var callback = jasmine.createSpy('callback');
$$testability.whenStable(callback);
expect(callback).not.toHaveBeenCalled();
deferred.reject();
$rootScope.$digest();
expect(callback).toHaveBeenCalled();
});
});
it('should wait for all route promises before calling callbacks', function() {
var deferreds = {};
module(function($provide, $routeProvider) {
// While normally `$browser.defer()` modifies the `outstandingRequestCount`, the mocked
// version (provided by `ngMock`) does not. This doesn't matter in most tests, but in this
// case we need the `outstandingRequestCount` logic to ensure that we don't call the
// `$$testability.whenStable()` callbacks part way through a `$rootScope.$evalAsync` block.
// See ngRoute's commitRoute()'s finally() block for details.
$provide.decorator('$browser', function($delegate) {
var oldDefer = $delegate.defer;
var newDefer = function(fn, delay) {
var requestCountAwareFn = function() { $delegate.$$completeOutstandingRequest(fn); };
$delegate.$$incOutstandingRequestCount();
return oldDefer.call($delegate, requestCountAwareFn, delay);
};
$delegate.defer = angular.extend(newDefer, oldDefer);
return $delegate;
});
addRouteWithAsyncRedirect('/foo', '/bar');
addRouteWithAsyncRedirect('/bar', '/baz');
addRouteWithAsyncRedirect('/baz', '/qux');
$routeProvider.when('/qux', {
template: '',
resolve: {
a: function($q) {
var deferred = deferreds['/qux'] = $q.defer();
return deferred.promise;
}
}
});
// Helpers
function addRouteWithAsyncRedirect(fromPath, toPath) {
$routeProvider.when(fromPath, {
resolveRedirectTo: function($q) {
var deferred = deferreds[fromPath] = $q.defer();
return deferred.promise.then(function() { return toPath; });
}
});
}
});
inject(function($browser, $location, $rootScope, $route, $$testability) {
$location.path('/foo');
$rootScope.$digest();
var callback = jasmine.createSpy('callback');
$$testability.whenStable(callback);
expect(callback).not.toHaveBeenCalled();
deferreds['/foo'].resolve();
$browser.defer.flush();
expect(callback).not.toHaveBeenCalled();
deferreds['/bar'].resolve();
$browser.defer.flush();
expect(callback).not.toHaveBeenCalled();
deferreds['/baz'].resolve();
$browser.defer.flush();
expect(callback).not.toHaveBeenCalled();
deferreds['/qux'].resolve();
$browser.defer.flush();
expect(callback).toHaveBeenCalled();
});
});
});
});
+308 -232
View File
File diff suppressed because it is too large Load Diff