Compare commits

...

83 Commits

Author SHA1 Message Date
Peter Bacon Darwin 38fb542838 chore(release): cut 1.2.3 unicorn-zapper release 2013-11-27 10:04:59 +00:00
Peter Bacon Darwin 7ab5098c14 docs(CHANGELOG): add v1.2.3 changes 2013-11-27 09:58:59 +00:00
Jeff Cross bcca80548d feat($attrs): add $attrs.$attr to externs so that it isn't renamed
This fixes the issue that any usage of $attr is broken after js compilation.
2013-11-26 18:34:11 -08:00
Jeff Cross 736c8fbbae refactor($location): move file://+win path fix to $location
The urlResolve method was fixed to automatically remove the
volume label from path names to fix issues with the file
protocol on windows where $location.path() was returning
paths where the first segment would be the volume name,
such as "/C:/mypath". See #4942 and #4928

However, the solution was specific to the $location non-
HTML5 mode, and was implemented at a lower level of
abstraction than it should have been. This refactor moves
the fix to inside of the LocationHashBangUrl $$parse method.

Closes #5041
2013-11-26 18:31:27 -08:00
Igor Minar 947562220d chore(release): fix cdn version in package.json 2013-11-26 17:38:23 -08:00
Tobias Bosch 333523483f fix($sanitize): Use same whitelist mechanism as $compile does.
`$sanitize` now uses the same mechanism as `$compile` to validate uris.
By this, the validation in `$sanitize` is more general and can be
configured in the same way as the one in `$compile`.

Changes
- Creates the new private service `$$sanitizeUri`.
- Moves related specs from `compileSpec.js` into `sanitizeUriSpec.js`.
- Refactors the `linky` filter to be less dependent on `$sanitize`
  internal functions.

Fixes #3748.
2013-11-26 14:29:38 -08:00
corrupt 68ceb17272 chore($httpBackend): preserve original non-zero http status code for file:// apps
Previously if an app was running from file:// origin we would always return either
http 200 or 404 depending on whether the response was present.

This changes the behavior so that we do this only if the protocol of the request
(not the origin) is file:// and only if the status code is 0.

Closes #4436
Closes #4587
Closes #4514
2013-11-26 12:36:41 -08:00
David Mosher 5bd6596856 chore(mocks): wrap angular-mocks.js in closure
Closes #5080
2013-11-26 13:22:29 +00:00
Peter Bacon Darwin b3f2a20832 chore(changelog): remove tmp file 2013-11-26 09:36:13 +00:00
adam77 e8d8c7a8d7 docs(compile): fix typo
Closes #5133
2013-11-26 06:56:38 +00:00
Deepak Kapoor 7a91d7fa7e docs(CONTRIBUTING): fix broken link to GitHub PR Helper
Closes #5134
2013-11-26 06:46:39 +00:00
smarigowda c6bd58eb58 docs(guide/scope): access the current element's scope in the console.
Closes #4884
2013-11-26 06:45:48 +00:00
sunnylost c2e45c769e refactor(angular.js): improve trim performance
According to Flagrant Badassery's blog
http://blog.stevenlevithan.com/archives/faster-trim-javascript
and this comparison http://jsperf.com/trim-function, this trim method is faster.

Closes #4406
2013-11-26 06:45:47 +00:00
Vojta Jina b08427dde9 chore(travis): add some more info for BrowserStack sessions 2013-11-25 18:04:35 -08:00
Vojta Jina ffd075b440 chore(travis): let's give BrowserStack a try
Switch the build to use BrowserStack instead of SauceLabs.

This also adds IE11 to our build.
2013-11-25 15:19:28 -08:00
Brian Ford 3fcd228441 chore: add script for updating bower repos 2013-11-25 13:09:50 -08:00
Pete Bacon Darwin 8383ecfcdf docs(CONTRIBUTING): add link to github-pr-helper 2013-11-25 20:18:36 +00:00
Matias Niemelä eed2333298 fix(ngAnimate): ensure animations are disabled upon bootstrap for structrual animations
Closes #5130
2013-11-25 15:00:50 -05:00
Pete Bacon Darwin a2809dacc4 docs(CONTRIBUTING): fix typo 2013-11-25 15:55:24 +00:00
Pete Bacon Darwin b837a31afa docs(CONTRIBUTING): highlight what makes a good issue submission 2013-11-25 14:33:51 +00:00
Pete Bacon Darwin 66b0fcd3c0 docs(CONTRIBUTING): consolidated submitting PRs sections 2013-11-25 14:15:24 +00:00
Matias Niemelä 2efe82309a fix($animate): ensure blocked keyframe animations are unblocked before the DOM operation
Closes #5106
2013-11-23 22:05:04 -05:00
Tobias Bosch a090400f09 fix(input): Support form auto complete on modern browser
Although modern browser support the "input" event, they still only fire
the "change" event when they auto complete form elements
other than the currently selected one.

Related to #1460
2013-11-22 17:02:21 -08:00
Brian Ford 84e0eea164 chore(docs): remove Disqus comments
We don't actively moderate these comments, and they range from
out of date, to inflammatory, to spam. Going forward, improvements
to the docs should be done via a PR, and questions should go on
StackOverflow where they can be curated and kept up to date by
AngularJS developers who help out there.
2013-11-22 16:27:05 -08:00
Igor Minar bcf12e70e5 chore: update copyright year in file headers 2013-11-22 13:16:23 -08:00
Igor Minar 1ca98b2c09 chore(release): start 1.2.3 unicorn-zapper iteration 2013-11-22 12:43:52 -08:00
Igor Minar 3efdeebcb7 chore(release): cut 1.2.2 consciousness-inertia release 2013-11-22 09:05:42 -08:00
Igor Minar 16febf8357 docs(CHANGELOG): add release notes for 1.2.2 consciousness-inertia 2013-11-22 09:03:40 -08:00
Caitlin Potter 0f7c4ca671 chore(style): fix missing indentation in httpBackend from a3172a2 2013-11-22 15:03:12 +00:00
Pete Bacon Darwin a3172a285f fix($httpBackend): only IE8 and below can't use script.onload for JSONP
IE8, IE9 and IE10 can use `script.onreadystate` so up till now we have been using this
if the sniffer says we are on IE.
But IE11 now does not support `script.onreadystate` and only supports the more standard
`script.onload` and `script.onerror`.
IE9 and IE10 do support `script.onload` and `script.onerror`. So now we only test whether
we are on IE8 or earlier before using `script.onreadystate`.
See http://pieisgood.org/test/script-link-events/

jQuery just uses all these handlers at once and hopes for the best, but since IE9 and IE10
support both sets of handlers, this could cause the handlers to be run more than once.

jQuery also notes that there is a potential memory leak in IE unless we remove the handlers
from the script object once they are run.  So we are doing this too, now.

Closes #4523
Closes #4527
Closes #4922
2013-11-22 13:45:55 +00:00
Igor Minar 84c408ce63 test($compile): correct the assertion to make test pass on IE11 2013-11-22 00:52:57 -08:00
rodyhaddad 40647b179c fix($parse): allow for new lines in expr when promise unwrapping is on
Previously, when unwrapping promises was set to `true`,
an error would occur if a parsed expression had a
new line in it.
This was because when generating the `evaledFnGetter` code,
a new line in an parsed expression would create a new line
in a JS string in that code, which is illegal. That is:
```js
pw("A+
B")
```

Closes #4718
2013-11-22 00:19:08 -08:00
Chirayu Krishnappa 0421cb4200 fix($compile): secure form[action] & iframe[srcdoc]
Require bindings to form[action] to be $sce.RESOURCE_URL and bindings to
iframe[srcdoc] to be $sce.HTML

Closes #4927
Closes #4933
2013-11-21 23:15:15 -08:00
Gonzalo Ruiz de Villa 6f1050df4f fix(httpBackend): should not read response data when request is aborted
When a request is aborted, it makes no sense to read the response headers or text.
Also in IE9, trying to read data (either response headers or text) from an aborted request
throws an Error c00c023f.

Fixes #4913
Closes #4940
2013-11-21 23:09:46 -08:00
Corey Burrows 4d16472b91 fix(ngMock): fixes httpBackend expectation with body object
Fixes an issue with httpBackend expectations where a given body object
may not match the actual request body if its keys are serialized in a
different order.

Closes #4956
2013-11-21 22:57:15 -08:00
Pete Bacon Darwin 9e89a31b12 fix(loader): expose $$minErr to modules such asngResource
This is highlighted in angular-phonecat when you try to use the index-async.html
which needs to load the ngResource module asynchronously but fails when it tries
to call `angular.$$minErr` to create the $resourceMinErr object.

Closes #5050
2013-11-21 22:51:26 -08:00
Tobias Bosch e6521e7491 fix(ngView): Don't throw when the ngView element contains content with directives.
Fixes #5069
2013-11-21 22:20:11 -08:00
Tobias Bosch 0a7cbb33b0 fix(ngInclude): Don't throw when the ngInclude element contains content with directives.
Related to #5069
2013-11-21 22:20:11 -08:00
Tobias Bosch 579242346c fix(tests): Correct tests for IE11
Some tests were wrong. However, src/* did not contain problems.

Fixes #5046
2013-11-21 21:53:09 -08:00
Peter Deak c42d0a0418 fix(ngAnimate): correctly retain and restore existing styles during and after animation
Closes #4869
2013-11-21 23:37:01 -05:00
Matias Niemelä 3fbb25e25c chore($animate): remove unnecessary reflective dereferencing 2013-11-21 20:48:21 -05:00
Matias Niemelä 6760d7a315 fix($animate): ensure keyframe animations are blocked around the reflow
Keyframe animations trigger on the first CSS class and not the second.
This may cause a slight flicker during a stagger animation since the
animation has already started before the stagger delay is considered.
This fix ensures that the animation is blocked until the active animation
starts which allows for staggering animations to take over properly.

Closes #5018
2013-11-21 20:48:15 -05:00
Matias Niemelä 062fbed8fc fix($animate): ensure transition animations are unblocked before the dom operation occurs
Transitions are blocked when the base CSS class is added at the start of the animation. This
causes an issue if the followup CSS class contains animatable-styles. Now, once the animation
active state is triggered (when the animation CSS dom operation occurs) the animation itself
will always trigger an animate without a quick jump.

Closes #5014
Closes #4265
2013-11-21 20:48:07 -05:00
Matias Niemelä 76e4db6f3d fix($animate): ensure addClass/removeClass animations do not snap during reflow
Closes #4892
2013-11-21 20:47:55 -05:00
Matias Niemelä 0cd7e8f227 fix($compile): ensure CSS classes are added and removed only when necessary
When $compile interpolates a CSS class attribute expression it will
do so by comparing the CSS class value already present on the element.
This may lead to unexpected results when dealing with ngClass values being
added and removed therefore it is best that both compile and ngClass delegate
addClass/removeClass operations to the same block of code.
2013-11-21 20:47:44 -05:00
Vojta Jina ba1b47f85b test(docs): fix the failing specs
Because Grunt was not failing the build, we didn't noticed these failing specs.
2013-11-21 12:41:57 -08:00
Vojta Jina 0a3481e23a chore: use temporary version of grunt-jasmine-node
This should be reverted once https://github.com/jasmine-contrib/grunt-jasmine-node/pull/33 gets merged in the upstream repo.

It fixes the problem where Grunt does not fail the build, even though there are failures.
See https://travis-ci.org/angular/angular.js/builds/14329011#L2366
2013-11-21 12:41:48 -08:00
jody tate e33c365144 docs(guide/unit-testing): minor style and grammar changes
Closes #5057
2013-11-21 20:37:57 +00:00
gdi2290 e3ceb50b73 docs(faq): update compressed and minified file size
Closes #5058
2013-11-21 20:27:27 +00:00
scottywakefield 6b5772bbbd docs(guide/ie): add info about what IE versions are supported
Added text from https://github.com/angular/angular.js/issues/4974

Closes #5070
2013-11-21 20:25:41 +00:00
Andrew Silluron-Gonzalez 6288cf5ca4 fix(ngController): fix issue with ngInclude on the same element
This changes the priority of ngController to 500 so that it takes precedence
over ngInclude.

Closes #4431, #4521
2013-11-21 09:52:34 -08:00
Tobias Bosch f6ecf9a3c9 fix($resource): Always return a resource instance when calling class methods on resources.
Previously, calling `MyResource.save(myResourceInstance)`returned
a promise, in contrast to the docs for `$resource`. However,
calling `MyResource.save({name: 'Tobias"})`already correctly
returned a resource instance.

Fixes #4545.
Closes #5061.
2013-11-21 09:51:02 -08:00
Chia-liang Kao a4e6d962d7 feat(input): hold listener during text composition
When composing text in CJKV, intermediate buffer for unfinished text should not
be updating the bound scope variables.

Closes #4684
2013-11-21 09:46:33 -08:00
Igor Minar 7874a4d007 docs(guide/migration): fix typo ngHtmlBind -> ngBindHtml 2013-11-21 07:29:18 -08:00
Matias Niemelä 1d50663b38 fix(ngAnimate): use a fallback CSS property that doesn't break existing styles
The clip property seems to remove the box-shadow property when an absolute
positioned animation is ongoing. This fix changes the property to be border-spacing
which is also very underused. The border-spacing CSS property is only visible
when border-collapse is set to separate.

Closes #4902
Closes #5030
2013-11-20 20:54:07 -05:00
Tobias Bosch ec3c4f94c7 refactor($sce): Use $sniffer instead of $document for feature detection.
Also adds `$sniffer.msieDocumentMode` property.

Closes #4931
Closes #5045
2013-11-20 23:12:39 +00:00
Matias Niemelä 6b8bbe4d90 fix(ngClass): ensure that ngClass only adds/removes the changed classes
ngClass works by removing all the former classes and then adding all the
new classes to the element during each watch change operation. This may
cause transition animations to never render. The ngClass directive will
now only add and remove the classes that change during each watch operation.

Closes #4960
Closes #4944
2013-11-20 17:15:56 -05:00
Matias Niemelä 7067a8fb0b fix($animate): ensure the DOM operation isn't run twice
Depending on the animations placed on ngClass, the DOM operation may
run twice causing a race condition between addClass and removeClass.
Depending on what classes are removed and added via $compile this may
cause all CSS classes to be removed accidentally from the element
being animated.

Closes #4949
2013-11-20 17:08:03 -05:00
Brian Ford c47abd0dd7 fix(ngInclude): allow ngInclude to load scripts when jQuery is included
In 1.2, the behavior of ngInclude was modified to use DOM APIs rather than jqLite. This means that
even when jQuery was loaded, ngInclude was not calling into it, and thus scripts were not eval'd
as they had been before. Although the use of ngInclude to eval scripts as a lazy-loading strategy
was never an intentional feature, this patch restores the ability to do so.

Closes #3756
2013-11-20 13:58:54 -08:00
Michel Salib 68d71bbc01 docs($log): the documented default log behavior was incorrect
Closes #4953
2013-11-20 21:02:02 +00:00
Jarrett Harris 334303e485 docs(ngAnimate): fixed two small typos
Line 162: 'defiend' should be 'defined'
Line 225: 'callback function be excuted' should be 'callback function will be executed'.

Closes #5048
2013-11-20 14:32:30 +00:00
gdennie b95fd53c6e docs(booleanAtts): explain the motivation for boolean attributes
It was not explicitly and consistently stated that the transient nature of boolean
attributes precludes them from hosting binding expressions.
This change make that more clear and reinforces the simplicity and elegance of the solution.

Closes #5031
2013-11-20 14:30:37 +00:00
Ashutosh Das c77dd040b4 docs(tutorial/step-2): correct the link to jasmine docs
Closes #5029
2013-11-20 14:21:49 +00:00
Stéphane Reynaud dc027f22e5 docs(ngRepeat): fix typo
Replace "ian" in "in"

Closes #5027
2013-11-20 14:10:23 +00:00
Stéphane Reynaud 043500fb27 docs(ngPluralize): Fix missing space before parentheses
There should be a space between "braces" and "(`{}`)"

Closes #5026
2013-11-20 11:38:29 +00:00
Maksim 3ceb6ab477 docs(guide/directive): use hideDialog handler in example
The handler is in the controller but was not being used in the template.

Closes #5020
2013-11-20 11:37:00 +00:00
gipsy86147 999fa44616 docs(guide/ie): fix typo
Closes #5006
2013-11-20 11:31:56 +00:00
Dave Gaeddert 5f9121ad56 docs(guide/providers): remove extra closing parenthesis in example
Closes #5005
2013-11-20 11:30:45 +00:00
Tyler Eich b4cf8483d7 docs(guide/migration): fix typo
Closes #5002
2013-11-20 11:26:57 +00:00
Yves Richard 8a9816e06b docs(guide/compiler): fix typo in isolate scope def
Closes #4999
2013-11-20 11:25:20 +00:00
jbnizet c0e10683a6 docs(api): example for $provide.value() uses $provide.value()
The example code for `$provide.value()` actually used `$provide.constant()`.
It now uses `$provide.value()`.

Closes #4983
Closes #4990
2013-11-20 11:21:09 +00:00
Pete Bacon Darwin cad5a367c3 docs(ngRoute): make it easier to find the example
Closes #4975
2013-11-20 11:18:21 +00:00
Stéphane Reynaud f2453eabb3 docs(tutorial): minimum node.js version is 0.10 (Windows too)
The doc has been modified by the following commit: bcc6e8d4f6
But the change was not made ​​for the part of Windows.

Closes #4967
2013-11-20 11:02:30 +00:00
Jayson Harshbarger aa0b11d794 docs(guide/migration): fix typo
Closes #4965
2013-11-20 11:00:01 +00:00
Matias Niemelä b9fa5c5a67 docs($animate): update the docs explaining enable/disable for specific elements 2013-11-19 17:50:24 -05:00
Vojta Jina 751f058f30 chore(travis): increase disconnect timeout
I still see some disconnection issues with IE9, hopefully this will help a bit.
2013-11-18 16:14:09 -08:00
Pete Bacon Darwin 29274e1d8d docs(ngApp): improve description and example 2013-11-18 16:21:59 +00:00
Jens Berthold 23ba287897 docs(guide/directive): clarify code example for isolated scopes bindings
Use different names for the attribute on the element (`info`) and the property (`customerInfo`)
on the isolate scope. Before `customer` was used for both which made it harder to understand.

Closes #4825
2013-11-18 15:03:35 +00:00
Pete Bacon Darwin 8f1e3606dd docs(guide/directive): add note about HTML case-insensitivity
Closes #4719
2013-11-18 12:58:53 +00:00
Chance ac56d1c9d9 docs(tutorial/step-4): controllers are no longer global functions
The docs did not line up with the codebase / previous steps of the tutorial.

Closes #4988
2013-11-18 12:49:19 +00:00
Pete Bacon Darwin de2919cb9a docs(guide/i18n): fix link to i18n files in the project
Closes #4998
2013-11-18 11:55:30 +00:00
Christoph Burgdorf 61943276f0 chore(*): remove accidentally created file
Closes #4963
2013-11-15 21:46:13 +00:00
Vojta Jina 88ce00a3cf chore(release): start a new release iteration 2013-11-15 00:03:03 -08:00
85 changed files with 5615 additions and 4215 deletions
+1
View File
@@ -17,3 +17,4 @@ angular.xcodeproj
libpeerconnection.log
npm-debug.log
/tmp/
/scripts/bower/bower-*
+3 -1
View File
@@ -7,11 +7,13 @@ env:
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
- SAUCE_CONNECT_READY_FILE=/tmp/sauce-connect-ready
- BROWSER_STACK_USERNAME=VojtaJina
- BROWSER_STACK_ACCESS_KEY=HAfHZaypxAc3PEUrUU9a
- LOGS_DIR=/tmp/angular-build/logs
before_script:
- mkdir -p $LOGS_DIR
- ./lib/sauce/sauce_connect_setup.sh
- ./lib/browser-stack/start-tunnel.sh
- npm install -g grunt-cli
- grunt bower
- grunt bower
+116
View File
@@ -1,3 +1,119 @@
<a name="1.2.3"></a>
# 1.2.3 unicorn-zapper (2013-11-27)
## Bug Fixes
- **$animate:**
- ensure blocked keyframe animations are unblocked before the DOM operation
([2efe8230](https://github.com/angular/angular.js/commit/2efe82309ac8ff4f67df8b6e40a539ea31e15804),
[#5106](https://github.com/angular/angular.js/issues/5106))
- ensure animations are disabled during bootstrap to prevent unwanted structural animations
([eed23332](https://github.com/angular/angular.js/commit/eed2333298412fbad04eda97ded3487c845b9eb9),
[#5130](https://github.com/angular/angular.js/issues/5130))
- **$sanitize:** use the same whitelist mechanism as `$compile` does
([33352348](https://github.com/angular/angular.js/commit/333523483f3ce6dd3177b697a5e5a7177ca364c8),
[#3748](https://github.com/angular/angular.js/issues/3748))
- **input:** react to form auto completion, through the `change` event, on modern browsers
([a090400f](https://github.com/angular/angular.js/commit/a090400f09d7993d102f527609879cdc74abae60),
[#1460](https://github.com/angular/angular.js/issues/1460))
- **$attrs:** add `$attrs.$attr` to externs so that it isn't renamed on js minification
([bcca8054](https://github.com/angular/angular.js/commit/bcca80548dde85ffe3838c943ba8e5c2deb1c721))
## Features
No new features in this release
## Breaking Changes
There are no breaking changes in this release (promise!)
<a name="1.2.2"></a>
# 1.2.2 consciousness-inertia (2013-11-22)
## Bug Fixes
- **$animate:**
- ensure keyframe animations are blocked around the reflow
([6760d7a3](https://github.com/angular/angular.js/commit/6760d7a315d7ea5cbd4f8ab74b200f754a2041f4),
[#5018](https://github.com/angular/angular.js/issues/5018))
- ensure transition animations are unblocked before the dom operation occurs
([062fbed8](https://github.com/angular/angular.js/commit/062fbed8fc3f7bc55433f8c6915c27520e6f63c5),
[#5014](https://github.com/angular/angular.js/issues/5014),
[#4265](https://github.com/angular/angular.js/issues/4265))
- ensure addClass/removeClass animations do not snap during reflow
([76e4db6f](https://github.com/angular/angular.js/commit/76e4db6f3d15199ac1fbe85f9cfa6079a1c4fa56),
[#4892](https://github.com/angular/angular.js/issues/4892))
- ensure the DOM operation isn't run twice
([7067a8fb](https://github.com/angular/angular.js/commit/7067a8fb0b18d5b5489006e1960cee721a88b4d2),
[#4949](https://github.com/angular/angular.js/issues/4949))
- **$compile:**
- secure form[action] & iframe[srcdoc]
([0421cb42](https://github.com/angular/angular.js/commit/0421cb4200e672818ed10996e92311404c150c3a),
[#4927](https://github.com/angular/angular.js/issues/4927),
[#4933](https://github.com/angular/angular.js/issues/4933))
- ensure CSS classes are added and removed only when necessary
([0cd7e8f2](https://github.com/angular/angular.js/commit/0cd7e8f22721f62b62440bb059ae764ebbe7b42a))
- **$httpBackend:** only IE8 and below can't use `script.onload` for JSONP
([a3172a28](https://github.com/angular/angular.js/commit/a3172a285fd74b5aa6c8d68a4988c767c06f549c),
[#4523](https://github.com/angular/angular.js/issues/4523),
[#4527](https://github.com/angular/angular.js/issues/4527),
[#4922](https://github.com/angular/angular.js/issues/4922))
- **$parse:** allow for new lines in expr when promise unwrapping is on
([40647b17](https://github.com/angular/angular.js/commit/40647b179c473f3f470bb1b3237d6f006269582f),
[#4718](https://github.com/angular/angular.js/issues/4718))
- **$resource:** Always return a resource instance when calling class methods on resources.
([f6ecf9a3](https://github.com/angular/angular.js/commit/f6ecf9a3c9090593faf5fa50586c99a56b51c776),
[#4545](https://github.com/angular/angular.js/issues/4545),
[#5061](https://github.com/angular/angular.js/issues/5061))
- **httpBackend:** should not read response data when request is aborted
([6f1050df](https://github.com/angular/angular.js/commit/6f1050df4fa885bd59ce85adbef7350ea93911a3),
[#4913](https://github.com/angular/angular.js/issues/4913),
[#4940](https://github.com/angular/angular.js/issues/4940))
- **loader:** expose `$$minErr` to modules such as`ngResource`
([9e89a31b](https://github.com/angular/angular.js/commit/9e89a31b129e40c805178535c244899ffafb77d8),
[#5050](https://github.com/angular/angular.js/issues/5050))
- **ngAnimate:**
- correctly retain and restore existing styles during and after animation
([c42d0a04](https://github.com/angular/angular.js/commit/c42d0a041890b39fc98afd357ec1307a3a36208d),
[#4869](https://github.com/angular/angular.js/issues/4869))
- use a fallback CSS property that doesn't break existing styles
([1d50663b](https://github.com/angular/angular.js/commit/1d50663b38ba042e8d748ffa6d48cfb5e93cfd7e),
[#4902](https://github.com/angular/angular.js/issues/4902),
[#5030](https://github.com/angular/angular.js/issues/5030))
- **ngClass:** ensure that ngClass only adds/removes the changed classes
([6b8bbe4d](https://github.com/angular/angular.js/commit/6b8bbe4d90640542eed5607a8c91f6b977b1d6c0),
[#4960](https://github.com/angular/angular.js/issues/4960),
[#4944](https://github.com/angular/angular.js/issues/4944))
- **ngController:** fix issue with ngInclude on the same element
([6288cf5c](https://github.com/angular/angular.js/commit/6288cf5ca471b0615a026fdb4db3ba242c9d8f88),
[#4431](https://github.com/angular/angular.js/issues/4431))
- **ngInclude:**
- Don't throw when the ngInclude element contains content with directives.
([0a7cbb33](https://github.com/angular/angular.js/commit/0a7cbb33b06778833a4d99b1868cc07690a827a7))
- allow ngInclude to load scripts when jQuery is included
([c47abd0d](https://github.com/angular/angular.js/commit/c47abd0dd7490576f4b84ee51ebaca385c1036da),
[#3756](https://github.com/angular/angular.js/issues/3756))
- **ngMock:** fixes httpBackend expectation with body object
([4d16472b](https://github.com/angular/angular.js/commit/4d16472b918a3482942d76f1e273a5aa01f65e83),
[#4956](https://github.com/angular/angular.js/issues/4956))
- **ngView:** Don't throw when the ngView element contains content with directives.
([e6521e74](https://github.com/angular/angular.js/commit/e6521e7491242504250b57dd0ee66af49e653c33),
[#5069](https://github.com/angular/angular.js/issues/5069))
- **tests:** Correct tests for IE11
([57924234](https://github.com/angular/angular.js/commit/579242346c4202ea58fc2cae6df232289cbea0bb),
[#5046](https://github.com/angular/angular.js/issues/5046))
- **input:** hold listener during text composition
([a4e6d962](https://github.com/angular/angular.js/commit/a4e6d962d78b26f5112d48c4f88c1e6234d0cae7),
[#4684](https://github.com/angular/angular.js/issues/4684))
<a name="1.2.1"></a>
# 1.2.1 underscore-empathy (2013-11-14)
+94 -64
View File
@@ -32,29 +32,108 @@ project.
## Submission Guidelines
### Submitting an Issue
Before you submit your issue follow the following guidelines:
* Search the archive first, it's likely that your question was already answered.
* A live example demonstrating the issue, will get an answer faster.
* Create one using [Plunker][plunker] or [JSFiddle][jsfiddle].
* If you get help, help others. Good karma rulez!
Before you submit your issue search the archive, maybe your question was already answered.
If your issue appears to be a bug, and hasn't been reported, open a new issue.
Help us to maximize the effort we can spend fixing issues and adding new
features, by not reporting duplicate issues.
features, by not reporting duplicate issues. Providing the following information will increase the
chances of your issue being dealt with quickly:
* **Overview of the issue** - if an error is being thrown a non-minified stack trace helps
* **Motivation for or Use Case** - explain why this is a bug for you
* **Angular Version(s)** - is it a regression?
* **Browsers and Operating System** - is this a problem with all browsers or only IE8?
* **Reproduce the error** - provide a live example (using [Plunker][plunker] or
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
* **Related issues** - has a similar issue been reported before?
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
causing the problem (line of code or commit)
Here is a great example of a well defined issue: https://github.com/angular/angular.js/issues/5069
**If you get help, help others. Good karma rulez!**
### Submitting a Pull Request
Before you submit your pull request follow the following guidelines:
Before you submit your pull request consider the following guidelines:
* Search GitHub for an open or closed Pull Request that relates to your submission. You don't want
to duplicate effort.
* Search [GitHub](https://github.com/angular/angular.js/pulls) for an open or closed Pull Request
that relates to your submission. You don't want to duplicate effort.
* Please sign our [Contributor License Agreement (CLA)](#signing-the-cla) before sending pull
requests. We cannot accept code without this.
* Make your changes in a new git branch
```shell
git checkout -b my-fix-branch master
```
* Create your patch, including appropriate test cases.
* Follow our Coding Rules
* Follow our Git Commit Guidelines
* Build your changes locally and on Travis (by pushing to GitHub) to ensure all the tests pass.
* Sign the Contributor License Agreement (CLA). We cannot accept code without this.
* Commit your changes and create a descriptive commit message (the
commit message is used to generate release notes, please check out our
[commit message conventions](#commit-message-format) and our commit message presubmit hook
`validate-commit-msg.js`):
```shell
git commit -a
```
* Build your changes locally to ensure all the tests pass
```shell
grunt test
```
* Push your branch to Github:
```shell
git push origin my-fix-branch
```
* In Github, send a pull request to `angular:master`.
* If we suggest changes then you can modify your branch, rebase and force a new push to your GitHub
repository to update the Pull Request.
repository to update the Pull Request:
```shell
git rebase master -i
git push -f
```
That's it! Thank you for your contribution!
When the patch is reviewed and merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
* Delete the remote branch on Github:
```shell
git push origin --delete my-fix-branch
```
* Check out the master branch:
```shell
git checkout master -f
```
* Delete the local branch:
```shell
git branch -D my-fix-branch
```
* Update your master with the latest upstream version:
```shell
git pull --ff upstream master
```
### GitHub Pull Request Helper
We track Pull Requests by attaching labels and assigning to milestones. For some reason GitHub
does not provide a good UI for managing labels on Pull Requests (unlike Issues). We have developed
a simple Chrome Extension that enables you to view (and manage if you have permission) the labels
on Pull Requests. You can get the extension from the Chrome WebStore -
[GitHub PR Helper][github-pr-helper]
## Coding Rules
To ensure consistency throughout the source code, keep these rules in mind as you are working:
@@ -146,56 +225,6 @@ You can find out more detailed information about contributing in the
[AngularJS documentation][contributing].
## Submitting Your Changes
To create and submit a change:
1. Please sign our [Contributor License Agreement (CLA)](#signing-the-cla) before sending pull
requests.
2. Create and checkout a new branch off the master branch for your changes:
```shell
git checkout -b my-fix-branch master
```
3. Create your patch, including appropriate test cases.
4. Commit your changes and create a descriptive commit message (the commit message is used to
generate release notes, please check out our [commit message conventions](#commit-message-format)
and our commit message presubmit hook `validate-commit-msg.js`):
```shell
git commit -a
```
5. Push your branch to Github:
```shell
git push origin my-fix-branch
```
6. In Github, send a pull request to `angular:master`.
That's it! Thank you for your contribution!
When the patch is reviewed and merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
```shell
# Delete the remote branch on Github:
git push origin :my-fix-branch
# Check out the master branch:
git checkout master
# Delete the local branch:
git branch -D my-fix-branch
# Update your master with the latest upstream version:
git pull --ff upstream master
```
[github]: https://github.com/angular/angular.js
[Google Closure I18N library]: https://code.google.com/p/closure-library/source/browse/closure/goog/i18n/
@@ -214,3 +243,4 @@ git pull --ff upstream master
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[github-pr-helper]: https://chrome.google.com/webstore/detail/github-pr-helper/mokbklfnaddkkbolfldepnkfmanfhpen
+1 -1
View File
@@ -178,7 +178,7 @@ module.exports = function(grunt) {
},
mocks: {
dest: 'build/angular-mocks.js',
src: files['angularModules']['ngMock'],
src: util.wrap(files['angularModules']['ngMock'], 'module'),
strict: false
},
sanitize: {
+1
View File
@@ -27,6 +27,7 @@ angularFiles = {
'src/ng/parse.js',
'src/ng/q.js',
'src/ng/rootScope.js',
'src/ng/sanitizeUri.js',
'src/ng/sce.js',
'src/ng/sniffer.js',
'src/ng/timeout.js',
-80
View File
@@ -1,80 +0,0 @@
<a name="v1.0.0rc3"></a>
# v1.0.0rc3 (2012-03-27)
## Bug Fixes
- **$compile:**
- create new (isolate) scopes for directives on root elements ([5390fb37](https://github.com/angular/angular.js/commit/5390fb37d2c01937922613fc57df4986af521787), closes [#817](https://github.com/angular/angular.js/issues/817))
- don't touch static element attributes ([9cb2195e](https://github.com/angular/angular.js/commit/9cb2195e61a78e99020ec19d687a221ca88b5900))
- Merge interpolated css class when replacing an element ([f49eaf8b](https://github.com/angular/angular.js/commit/f49eaf8bf2df5f4e0e82d6c89e849a4f82c8d414))
- **$http:**
- don't send Content-Type header when no data ([1a5bebd9](https://github.com/angular/angular.js/commit/1a5bebd927ecd22f9c34617642fdf58fe3f62efb), closes [#749](https://github.com/angular/angular.js/issues/749))
- **$log:**
- avoid console.log.apply calls in IE ([15213ec2](https://github.com/angular/angular.js/commit/15213ec212769837cb2b7e781ffc5bfd598d27ca), closes [#805](https://github.com/angular/angular.js/issues/805))
- **$resource:**
- support escaping of ':' in resource url ([6d6f8753](https://github.com/angular/angular.js/commit/6d6f875345e01f2c6c63ef95164f6f39e923da15))
- **compiler:**
- allow transclusion of root elements ([9918b748](https://github.com/angular/angular.js/commit/9918b748be01266eb10db39d51b4d3098d54ab66))
- **e2e runner:**
- fix typo that caused errors on IE8 ([ee5a5352](https://github.com/angular/angular.js/commit/ee5a5352fd4b94cedee6ef20d4bf2d43ce77e00b), closes [#806](https://github.com/angular/angular.js/issues/806))
- **forEach:**
- should ignore prototypically inherited properties ([8d7e6948](https://github.com/angular/angular.js/commit/8d7e6948496ff26ef1da8854ba02fcb8eebfed61), closes [#813](https://github.com/angular/angular.js/issues/813))
- **forms:**
- Remove double registering of form ([1faafa31](https://github.com/angular/angular.js/commit/1faafa31582c4e9413f48dc7d12f5b681f9fe9fd))
- Set ng-valid/ng-invalid correctly ([08bfea18](https://github.com/angular/angular.js/commit/08bfea183a850b29da270eac47f80b598cbe600f))
- **init:**
- use jQuery#ready for init if available ([cb2ad9ab](https://github.com/angular/angular.js/commit/cb2ad9abf24e6f855cc749efe3155bd7987ece9d), closes [#818](https://github.com/angular/angular.js/issues/818))
- **json:**
- added support for iso8061 timezone ([5ac14f63](https://github.com/angular/angular.js/commit/5ac14f633a69f49973b5512780c6ec7752405967))
- **matchers.toHaveClass:**
- Correct reference to angular.mock.dump ([f701ce08](https://github.com/angular/angular.js/commit/f701ce08f9d63be05fc3b92f57ad473e1e749b2d))
- **ng-switch:**
- properly destroy child scopes ([2315d9b3](https://github.com/angular/angular.js/commit/2315d9b3610994b36c44e4a97fb1427d59471ce8))
- **ngDocSpec:**
- fix broken tests ([53b6f522](https://github.com/angular/angular.js/commit/53b6f522a56eea314cbd084816e08f24b2c7879f))
- **ngForm:**
- alias name||ngForm ([823adb23](https://github.com/angular/angular.js/commit/823adb231995e917bc060bfa49453e2a96bac2b6))
- **ngRepeat:**
- correct variable reference in error message ([935c1018](https://github.com/angular/angular.js/commit/935c1018da05dbf3124b2dd33619c4a3c82d7a2a))
- **ngView:**
- controller not published ([21e74c2d](https://github.com/angular/angular.js/commit/21e74c2d2e8e985b23711785287feb59965cbd90))
- **q:**
- resolve all of nothing to nothing ([ac75079e](https://github.com/angular/angular.js/commit/ac75079e2113949d5d64adbcf23d56f3cf295d41))
- **select:**
- multiselect failes to update view on selection insert ([6ecac8e7](https://github.com/angular/angular.js/commit/6ecac8e71a84792a434d21db2c245b3648c55f18))
## Features
- **$compile:**
- do not interpolate boolean attributes, rather evaluate them ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- **$controller:**
- support controller registration via $controllerProvider ([d54dfecb](https://github.com/angular/angular.js/commit/d54dfecb00fba41455536c5ddd55310592fdaf84))
- **$route:**
- when matching consider trailing slash as optional ([a4fe51da](https://github.com/angular/angular.js/commit/a4fe51da3ba0dc297ecd389e230d6664f250c9a6), closes [#784](https://github.com/angular/angular.js/issues/784))
- **assertArgFn:**
- should support array annotated fns ([4b8d9260](https://github.com/angular/angular.js/commit/4b8d926062eb4d4483555bdbdec4656f585ab40b))
- **http:**
- added params parameter ([73c85930](https://github.com/angular/angular.js/commit/73c8593077155a9f2e8ef42efd4c497eba0bef4f))
- **injector:**
- infer _foo_ as foo ([f13dd339](https://github.com/angular/angular.js/commit/f13dd3393dfb7a33565c9360342c193bc0bddcb6))
- **input.radio:**
- Allow value attribute to be interpolated ([ade6c452](https://github.com/angular/angular.js/commit/ade6c452753145c84884d17027a7865bf4b34b0c))
- **jqLite:**
- make injector() and scope() work with the document object ([5fdab52d](https://github.com/angular/angular.js/commit/5fdab52dd7c269f99839f4fa6b5854d9548269fa))
- add .controller() method ([6c5a05ad](https://github.com/angular/angular.js/commit/6c5a05ad49a1e083570c3dfe331403398f899dbe))
- **ngValue:**
- allow radio inputs to have non string values ([09e175f0](https://github.com/angular/angular.js/commit/09e175f02cca0f4a295fd0c9b980cd8f432e722b), closes [#816](https://github.com/angular/angular.js/issues/816))
- **scope:**
- broadcast $destroy event on scope destruction ([9b1aff90](https://github.com/angular/angular.js/commit/9b1aff905b638aa274a5fc8f88662df446d374bd))
- **scope.$eval:**
- Allow passing locals to the expression ([192ff61f](https://github.com/angular/angular.js/commit/192ff61f5d61899e667c6dbce4d3e6e399429d8b))
## Breaking Changes
- boolean attrs are evaluated rather than interpolated ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- ng-bind-attr directive removed ([55027132](https://github.com/angular/angular.js/commit/55027132f3d57e5dcf94683e6e6bd7b0aae0087d))
- any app that depends on this service and its fallback to Modernizr, please ([aaedefb9](https://github.com/angular/angular.js/commit/aaedefb92e6bec6626e173e5155072c91471596a))
+9
View File
@@ -225,6 +225,11 @@ angular.uppercase = function(s) {};
*/
angular.Attributes;
/**
* @type {Object.<string, string>}
*/
angular.Attributes.$attr;
/**
* @param {string} name
* @return {string}
@@ -1578,6 +1583,7 @@ angular.$q.when = function(value) {};
* @typedef {{
* resolve: function(*=),
* reject: function(*=),
* notify: function(*=),
* promise: angular.$q.Promise
* }}
*/
@@ -1589,6 +1595,9 @@ angular.$q.Deferred.resolve = function(opt_value) {};
/** @param {*=} opt_reason */
angular.$q.Deferred.reject = function(opt_reason) {};
/** @param {*=} opt_value */
angular.$q.Deferred.notify = function(opt_value) {};
/** @type {angular.$q.Promise} */
angular.$q.Deferred.promise;
+4 -4
View File
@@ -15,8 +15,8 @@ ng\:form {
* when the active class isn't set, or if the active class doesn't
* contain any styles to transition to, then, if ngAnimate is used,
* it will appear as if the webpage is broken due to the forever hanging
* animations. The clip (!ie) and zoom (ie) CSS properties are used
* since they trigger a transition without making the browser
* animations. The border-spacing (!ie) and zoom (ie) CSS properties are
* used below since they trigger a transition without making the browser
* animate anything and they're both highly underused CSS properties */
.ng-animate-start { clip:rect(0, auto, auto, 0); -ms-zoom:1.0001; }
.ng-animate-active { clip:rect(-1px, auto, auto, 0); -ms-zoom:1; }
.ng-animate-start { border-spacing:1px 1px; -ms-zoom:1.0001; }
.ng-animate-active { border-spacing:0px 0px; -ms-zoom:1; }
+1 -1
View File
@@ -350,7 +350,7 @@ Creating local properties on widget scope creates two problems:
To solve the issue of lack of isolation, the directive declares a new `isolated` scope. An
isolated scope does not prototypically inherit from the child scope, and therefore we don't have
isolated scope does not prototypically inherit from the parent scope, and therefore we don't have
to worry about accidentally clobbering any properties.
However `isolated` scope creates a new problem: if a transcluded DOM is a child of the widget
+49 -50
View File
@@ -13,45 +13,44 @@ Unit testing as the name implies is about testing individual units of code. Unit
answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list
in the right order?"
In order to answer such question it is very important that we can isolate the unit of code under test.
In order to answer such a question it is very important that we can isolate the unit of code under test.
That is because when we are testing the sort function we don't want to be forced into creating
related pieces such as the DOM elements, or making any XHR calls in getting the data to sort.
While
this may seem obvious it usually is very difficult to be able to call an individual function on a
typical project. The reason is that the developers often mix concerns, and they end up with a
piece of code which does everything. It reads the data from XHR, it sorts it and then it
While this may seem obvious it can be very difficult to call an individual function on a
typical project. The reason is that the developers often mix concerns resulting in a
piece of code which does everything. It makes an XHR request, it sorts the response data and then it
manipulates the DOM.
With Angular we try to make it easy for you to do the right thing, and so we
provide dependency injection for your XHR (which you can mock out) and we created abstraction which
provide dependency injection for your XHR (which you can mock out) and we created abstractions which
allow you to sort your model without having to resort to manipulating the DOM. So that in the end,
it is easy to write a sort function which sorts some data, so that your test can create a data set,
apply the function, and assert that the resulting model is in the correct order. The test does not
have to wait for XHR, or create the right kind of DOM, or assert that your function has mutated the
DOM in the right way.
have to wait for the XHR response to arrive, create the right kind of test DOM, nor assert that your
function has mutated the DOM in the right way.
## With great power comes great responsibility
Angular is written with testability in mind, but it still requires that you
do the right thing. We tried to make the right thing easy, but Angular is not magic, which means if
you don't follow these guidelines you may very well end up with an untestable application.
Angular is written with testability in mind, but it still requires that you do the right thing.
We tried to make the right thing easy, but Angular is not magic. If you don't follow these guidelines
you may very well end up with an untestable application.
## Dependency Injection
There are several ways in which you can get a hold of a dependency:
1. You could create it using the `new` operator.
2. You could look for it in a well known place, also known as global singleton.
3. You could ask a registry (also known as service registry) for it. (But how do you get a hold of
the registry? Most likely by looking it up in a well known place. See #2)
4. You could expect that it be handed to you.
There are several ways in which you can get a hold of a dependency. You can:
1. Create it using the `new` operator.
2. Look for it in a well-known place, also known as a global singleton.
3. Ask a registry (also known as service registry) for it. (But how do you get a hold of
the registry? Most likely by looking it up in a well known place. See #2.)
4. Expect it to be handed to you.
Out of the four options in the list above, only the last one is testable. Let's look at why:
### Using the `new` operator
While there is nothing wrong with the `new` operator fundamentally the issue is that calling a new
on a constructor permanently binds the call site to the type. For example lets say that we are
trying to instantiate an `XHR` so that we can get some data from the server.
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to
instantiate an `XHR` that will retrieve data from the server.
<pre>
function MyClass() {
@@ -64,12 +63,12 @@ function MyClass() {
}
</pre>
The issue becomes that in tests, we would very much like to instantiate a `MockXHR` which would
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
permanently bound to the actual XHR, and there is no good way to replace it. Yes there is monkey
patching. That is a bad idea for many reasons which are outside the scope of this document.
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
patch, but that is a bad idea for many reasons which are outside the scope of this document.
The class above is hard to test since we have to resort to monkey patching:
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
<pre>
var oldXHR = XHR;
XHR = function MockXHR() {};
@@ -81,7 +80,7 @@ XHR = oldXHR; // if you forget this bad things will happen
### Global look-up:
Another way to approach the problem is to look for the service in a well known location.
Another way to approach the problem is to look for the service in a well-known location.
<pre>
function MyClass() {
@@ -95,14 +94,14 @@ function MyClass() {
}
</pre>
While no new instance of the dependency is being created, it is fundamentally the same as `new`, in
that there is no good way to intercept the call to `global.xhr` for testing purposes, other then
While no new dependency instance is created, it is fundamentally the same as `new` in
that no way exists to intercept the call to `global.xhr` for testing purposes, other then
through monkey patching. The basic issue for testing is that a global variable needs to be mutated in
order to replace it with call to a mock method. For further explanation why this is bad see: {@link
order to replace it with call to a mock method. For further explanation of why this is bad see: {@link
http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global
State & Singletons}
The class above is hard to test since we have to change global state:
The class above is hard to test since we have to change the global state:
<pre>
var oldXHR = global.xhr;
global.xhr = function mockXHR() {};
@@ -115,7 +114,7 @@ global.xhr = oldXHR; // if you forget this bad things will happen
### Service Registry:
It may seem as that this can be solved by having a registry for all of the services, and then
It may seem that this can be solved by having a registry of all the services and then
having the tests replace the services as needed.
<pre>
@@ -131,12 +130,12 @@ function MyClass() {
}
</pre>
However, where does the serviceRegistry come from? if it is:
* `new`-ed up, the test has no chance to reset the services for testing
* global look-up, then the service returned is global as well (but resetting is easier, since
there is only one global variable to be reset).
However, where does the serviceRegistry come from? If it is:
* `new`-ed up, the test has no chance to reset the services for testing.
* a global look-up then the service returned is global as well (but resetting is easier, since
only one global variable exists to be reset).
The class above is hard to test since we have to change global state:
The class above is hard to test since we have to change the global state:
<pre>
var oldServiceLocator = global.serviceLocator;
global.serviceLocator.set('xhr', function mockXHR() {});
@@ -148,7 +147,7 @@ global.serviceLocator = oldServiceLocator; // if you forget this bad things will
### Passing in Dependencies:
Lastly the dependency can be passed in.
Last, the dependency can be passed in.
<pre>
function MyClass(xhr) {
@@ -161,12 +160,12 @@ function MyClass(xhr) {
}
</pre>
This is the preferred way since the code makes no assumptions as to where the `xhr` comes from,
rather that whoever created the class was responsible for passing it in. Since the creator of the
This is the preferred method since the code makes no assumptions about the origin of `xhr` and cares
instead about whoever created the class responsible for passing it in. Since the creator of the
class should be different code than the user of the class, it separates the responsibility of
creation from the logic, and that is what dependency-injection is in a nutshell.
creation from the logic. This is dependency-injection is in a nutshell.
The class above is very testable, since in the test we can write:
The class above is testable, since in the test we can write:
<pre>
function xhrMock(args) {...}
var myClass = new MyClass(xhrMock);
@@ -176,12 +175,12 @@ myClass.doWork();
Notice that no global variables were harmed in the writing of this test.
Angular comes with {@link di dependency injection} built in which makes the right thing
Angular comes with {@link di dependency injection} built-in, making the right thing
easy to do, but you still need to do it if you wish to take advantage of the testability story.
## Controllers
What makes each application unique is its logic, which is what we would like to test. If the logic
for your application is mixed in with DOM manipulation, it will be hard to test as in the example
What makes each application unique is its logic, and the logic is what we would like to test. If the logic
for your application contains DOM manipulation, it will be hard to test. See the example
below:
<pre>
@@ -209,7 +208,7 @@ function PasswordCtrl() {
}
</pre>
The code above is problematic from a testability point of view, since it requires your test to have the right kind
The code above is problematic from a testability point of view since it requires your test to have the right kind
of DOM present when the code executes. The test would look like this:
<pre>
@@ -226,8 +225,8 @@ expect(span.text()).toEqual('weak');
$('body').html('');
</pre>
In angular the controllers are strictly separated from the DOM manipulation logic which results in
a much easier testability story as can be seen in this example:
In angular the controllers are strictly separated from the DOM manipulation logic and this results in
a much easier testability story as the following example shows:
<pre>
function PasswordCtrl($scope) {
@@ -245,7 +244,7 @@ function PasswordCtrl($scope) {
}
</pre>
and the test is straight forward
and the test is straight forward:
<pre>
var $scope = {};
@@ -255,11 +254,11 @@ $scope.grade();
expect($scope.strength).toEqual('weak');
</pre>
Notice that the test is not only much shorter but it is easier to follow what is going on. We say
Notice that the test is not only much shorter, it is also easier to follow what is happening. We say
that such a test tells a story, rather then asserting random bits which don't seem to be related.
## Filters
{@link api/ng.$filterProvider Filters} are functions which transform the data into user readable
{@link api/ng.$filterProvider Filters} are functions which transform the data into a user readable
format. They are important because they remove the formatting responsibility from the application
logic, further simplifying the application logic.
@@ -282,7 +281,7 @@ you create with directives may be used throughout your application and in many d
### Simple HTML Element Directive
Lets start with an angular app with no dependencies.
Let's start with an angular app with no dependencies.
<pre>
var app = angular.module('myApp', []);
+25 -22
View File
@@ -56,9 +56,9 @@ The following also **matches** `ngModel`:
Angular **normalizes** an element's tag and attribute name to determine which elements match which
directives. We typically refer to directives by their case-sensitive
{@link http://en.wikipedia.org/wiki/CamelCase camelCase} **normalized** name (e.g. `ngModel`).
However, in the DOM, we refer to directives by case-insensitive forms, typically using
{@link http://en.wikipedia.org/wiki/Letter_case#Computers dash-delimited} attributes on DOM elements
(e.g. `ng-model`).
However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case
forms, typically using {@link http://en.wikipedia.org/wiki/Letter_case#Computers dash-delimited}
attributes on DOM elements (e.g. `ng-model`).
The **normalization** process is as follows:
@@ -404,40 +404,43 @@ we call an **isolate scope**. To do this, we can use a directive's `scope` optio
return {
restrict: 'E',
scope: {
customer: '=customer'
customerInfo: '=info'
},
templateUrl: 'my-customer.html'
templateUrl: 'my-customer-iso.html'
};
});
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<my-customer customer="naomi"></my-customer>
<my-customer info="naomi"></my-customer>
<hr>
<my-customer customer="igor"></my-customer>
<my-customer info="igor"></my-customer>
</div>
</file>
<file name="my-customer.html">
Name: {{customer.name}} Address: {{customer.address}}
<file name="my-customer-iso.html">
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
</file>
</example>
Looking at `index.html`, the first `<my-customer>` element binds the inner scope's `customer` to
`naomi`, which we have exposed on our controller's scope. The second binds `customer` to `igor`.
Looking at `index.html`, the first `<my-customer>` element binds the `info` attribute to `naomi`,
which we have exposed on our controller's scope. The second binds `info` to `igor`.
Let's take a closer look at the scope option:
```javascript
//...
scope: {
customer: '=customer'
customerInfo: '=info'
},
//...
```
The property name (`customer`) corresponds to the variable name of the `myCustomer` directive's
isolated scope. The value of the property (`=customer`) tells `$compile` to bind to the `customer`
attribute.
The **scope option** is an object that contains a property for each isolate scope binding. In this
case it has just one property:
- Its name (`customerInfo`) corresponds to the
directive's **isolate scope** property `customerInfo`.
- Its value (`=info`) tells `$compile` to bind to the `info` attribute.
<div class="alert alert-warning">
**Note:** These `=attr` attributes in the `scope` option of directives are normalized just like
@@ -449,12 +452,12 @@ For cases where the attribute name is the same as the value you want to bind to
directive's scope, you can use this shorthand syntax:
```javascript
//...
...
scope: {
// same as '=customer'
// same as '=customer'
customer: '='
},
//...
...
```
Besides making it possible to bind different data to the scope inside a directive, using an isolated
@@ -475,7 +478,7 @@ within our directive's template:
return {
restrict: 'E',
scope: {
customer: '=customer'
customerInfo: '=info'
},
templateUrl: 'my-customer-plus-vojta.html'
};
@@ -483,11 +486,11 @@ within our directive's template:
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<my-customer customer="naomi"></my-customer>
<my-customer info="naomi"></my-customer>
</div>
</file>
<file name="my-customer-plus-vojta.html">
Name: {{customer.name}} Address: {{customer.address}}
Name: {{customerInfo.name}} Address: {{customerInfo.address}}
<hr>
Name: {{vojta.name}} Address: {{vojta.address}}
</file>
@@ -717,7 +720,7 @@ own behavior to it.
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<my-dialog ng-hide="dialogIsHidden" on-close="dialogIsHidden = true">
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
Check out the contents, {{name}}!
</my-dialog>
</div>
+1 -1
View File
@@ -43,7 +43,7 @@ http://userguide.icu-project.org/locale ICU } website for more information about
**Supported locales in Angular**
Angular separates number and datetime format rule sets into different files, each file for a
particular locale. You can find a list of currently supported locales {@link
https://github.com/angular/angular.js/tree/master/i18n/locale here}
https://github.com/angular/angular.js/tree/master/src/ngLocale here}
# Providing locale rules to Angular
There are two approaches to providing locale rules to Angular:
+12 -1
View File
@@ -8,6 +8,17 @@ This document describes the Internet Explorer (IE) idiosyncrasies when dealing w
attributes and tags. Read this document if you are planning on deploying your Angular application
on IE v8.0 or earlier.
The project currently supports and will attempt to fix bugs for IE8 and above. The continuous
integration server runs all the tests against IE8. See http://ci.angularjs.org.
IE7 and below are not tested and the project makes no guarantee that Angular will work on it.
A subset of the AngularJS functionality may work. It is up to you to test and decide whether
it works for your particular app.
It is very unlikely that issues specific to IE7 or earlier will be given any time by the core team.
[GitHub](https://github.com/angular/angular.js/issues/4974)
# Short Version
To make your Angular application work on IE please make sure that:
@@ -80,7 +91,7 @@ The **important** parts are:
IE has issues with element tag names which are not standard HTML tag names. These fall into two
categories, and each category has its own fix.
* If the tag name starts with `my:` prefix than it is considered an XML namespace and must
* If the tag name starts with `my:` prefix then it is considered an XML namespace and must
have corresponding namespace declaration on `<html xmlns:my="ignored">`
* If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using
+7 -7
View File
@@ -3,7 +3,7 @@
@description
AngularJS version 1.2 introduces several breaking changes that will may require changes to your
AngularJS version 1.2 introduces several breaking changes that may require changes to your
application's source code.
Although we try to avoid breaking changes, there are some cases where it is unavoidable.
@@ -35,7 +35,7 @@ below should still apply, but you may want to consult the
<li>{@link guide/migration#resource-methods-return-the-promise Resource methods return the promise}</li>
<li>{@link guide/migration#resource-promises-are-resolved-with-the-resource-instance Resource promises are resolved with the resource instance}</li>
<li>{@link guide/migration#$locationsearch-supports-multiple-keys $location.search supports multiple keys}</li>
<li>{@link guide/migration#nghtmlbindunsafe-has-been-removed-and-replaced-by-nghtmlbind ngHtmlBindUnsafe has been removed and replaced by ngHtmlBind}</li>
<li>{@link guide/migration#ngbindhtmlunsafe-has-been-removed-and-replaced-by-ngbindhtml ngBindHtmlUnsafe has been removed and replaced by ngBindHtml}</li>
<li>{@link guide/migration#form-names-that-are-expressions-are-evaluated Form names that are expressions are evaluated}</li>
<li>{@link guide/migration#hasownproperty-disallowed-as-an-input-name hasOwnProperty disallowed as an input name}</li>
<li>{@link guide/migration#directives-order-of-postlink-functions-reversed Directives: Order of postLink functions reversed}</li>
@@ -45,7 +45,7 @@ below should still apply, but you may want to consult the
<li>{@link guide/migration#urls-are-now-sanitized-against-a-whitelist URLs are now sanitized against a whitelist}</li>
<li>{@link guide/migration#isolate-scope-only-exposed-to-directives-with-property Isolate scope only exposed to directives with <code>scope</code> property}</li>
<li>{@link guide/migration#change-to-interpolation-priority Change to interpolation priority}</li>
<li>{@link guide/migration#underscore-prefixed/suffixed-prorperties-are-non-bindable Underscore-prefixed/suffixed prorperties are non-bindable}</li>
<li>{@link guide/migration#underscore-prefixed/suffixed-properties-are-non-bindable Underscore-prefixed/suffixed properties are non-bindable}</li>
<li>{@link guide/migration#you-cannot-bind-to-select[multiple] You cannot bind to select[multiple]}</li>
<li>{@link guide/migration#uncommon-region-specific-local-files-were-removed-from-i18n Uncommon region-specific local files were removed from i18n}</li>
</ul>
@@ -376,11 +376,11 @@ passing it to `$location`.
See [80739409](https://github.com/angular/angular.js/commit/807394095b991357225a03d5fed81fea5c9a1abe).
## ngHtmlBindUnsafe has been removed and replaced by ngHtmlBind
## ngBindHtmlUnsafe has been removed and replaced by ngBindHtml
`ngHtmlBind` which has been moved from `ngSanitize` module to the core `ng` module.
`ngBindHtml` which has been moved from `ngSanitize` module to the core `ng` module.
`ngBindHtml` provides `ngHtmlBindUnsafe` like
`ngBindHtml` provides `ngBindHtmlUnsafe` like
behavior (evaluate an expression and innerHTML the result into the DOM) when bound to the result
of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanitized via
`$sanitize` before being innerHTML'd. If the `$sanitize` service isn't available (`ngSanitize`
@@ -588,7 +588,7 @@ See [79223eae](https://github.com/angular/angular.js/commit/79223eae502283889334
[#4528](https://github.com/angular/angular.js/issues/4528), and
[#4649](https://github.com/angular/angular.js/issues/4649)
## Underscore-prefixed/suffixed prorperties are non-bindable
## Underscore-prefixed/suffixed properties are non-bindable
This change introduces the notion of "private" properties (properties
whose names begin and/or end with an underscore) on the scope chain.
+1 -1
View File
@@ -228,7 +228,7 @@ myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
// let's assume that the UnicornLauncher constructor was also changed to
// accept and use the useTinfoilShielding argument
return new UnicornLauncher(apiToken, useTinfoilShielding);
}]);
}];
});
```
+1 -1
View File
@@ -177,7 +177,7 @@ To examine the scope in the debugger:
2. The debugger allows you to access the currently selected element in the console as `$0`
variable.
3. To retrieve the associated scope in console execute: `angular.element($0).scope()`
3. To retrieve the associated scope in console execute: `angular.element($0).scope()` or just type $scope
## Scope Events Propagation
+1 -1
View File
@@ -67,7 +67,7 @@ illustration we typically build snappy apps with hundreds or thousands of active
### How big is the angular.js file that I need to include?
The size of the file is < 29KB compressed and minified.
The size of the file is < 36KB compressed and minified.
### Can I use the open-source Closure Library with Angular?
+1 -1
View File
@@ -85,7 +85,7 @@ to run <code>scripts/web-server.js</code>, a simple bundled http server.</p></l
<div class="tab-pane well" id="git-win" title="Git on Windows">
<ol>
<li><p>You will need Node.js and Karma to run unit tests, so please verify that you have
<a href="http://nodejs.org/">Node.js</a> v0.8 or better installed
<a href="http://nodejs.org/">Node.js</a> v0.10 or better installed
and that the <code>node</code> executable is on your <code>PATH</code> by running the following
command in a terminal window:</p>
<pre>node --version</pre>
+2 -2
View File
@@ -180,8 +180,8 @@ is available to be injected.
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the {@link
http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
http://pivotal.github.com/jasmine/ Jasmine home page} and at the {@link
http://pivotal.github.io/jasmine/ Jasmine docs}.
The angular-seed project is pre-configured to run all unit tests using {@link
http://karma-runner.github.io/ Karma}. To run the test, do the following:
+6 -5
View File
@@ -112,11 +112,12 @@ describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl;
beforeEach(function() {
scope = {},
ctrl = new PhoneListCtrl(scope);
});
beforeEach(module('phonecatApp'));
beforeEach(inject(function($controller) {
scope = {};
ctrl = $controller('PhoneListCtrl', {$scope:scope});
}));
it('should create "phones" model with 3 phones', function() {
expect(scope.phones.length).toBe(3);
+2 -2
View File
@@ -34,8 +34,8 @@ describe('ngdoc', function() {
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng-c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng-c');
expect(ngdoc.metadata([d1])[0].shortName).toEqual('a.b.c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('a.b.ng-c');
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
});
+1 -1
View File
@@ -20,7 +20,7 @@ describe('Docs Links', function() {
});
it('should have an "view source" button', function() {
spyOn(gruntUtil, 'getVersion').andReturn({cdn: '1.2.299'});
spyOn(gruntUtil, 'getVersion').andReturn({full: '1.2.299'});
expect(doc.html()).
toContain('<a href="http://github.com/angular/angular.js/tree/v1.2.299/test.js#L42" class="view-source btn btn-action"><i class="icon-zoom-in"> </i> View source</a>');
+2 -3
View File
@@ -335,9 +335,8 @@
<div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content slide-reveal"></div>
<div id="disqus" class="disqus">
<h2>Discussion</h2>
<div id="disqus_thread" class="content-panel-content"></div>
<div class="alert alert-info">
<a href="http://blog.angularjs.org/2013/11/farewell-disqus.html">Where did Disqus go?</a>
</div>
</div>
</div>
-24
View File
@@ -680,7 +680,6 @@ docsApp.controller.DocsController = function($scope, $location, $window, $cookie
var currentPageId = $location.path();
$scope.partialTitle = $scope.currentPage.shortName;
$window._gaq.push(['_trackPageview', currentPageId]);
loadDisqus(currentPageId);
};
/** stores a cookie that is used by apache to decide which manifest ot send */
@@ -892,29 +891,6 @@ docsApp.controller.DocsController = function($scope, $location, $window, $cookie
return namespace;
}
}
function loadDisqus(currentPageId) {
// http://docs.disqus.com/help/2/
window.disqus_shortname = 'angularjs-next';
window.disqus_identifier = currentPageId;
window.disqus_url = 'http://docs.angularjs.org' + currentPageId;
if ($location.host() == 'localhost') {
return; // don't display disqus on localhost, comment this out if needed
//window.disqus_developer = 1;
}
// http://docs.disqus.com/developers/universal/
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://angularjs.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
angular.element(document.getElementById('disqus_thread')).html('');
}
};
+56 -5
View File
@@ -5,7 +5,7 @@ module.exports = function(config, specificOptions) {
logLevel: config.LOG_INFO,
logColors: true,
browsers: ['Chrome'],
browserDisconnectTimeout: 5000,
browserDisconnectTimeout: 10000,
// config for Travis CI
@@ -15,6 +15,14 @@ module.exports = function(config, specificOptions) {
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
},
// BrowserStack config for Travis CI
browserStack: {
startTunnel: false,
project: 'AngularJS',
name: specificOptions.testName,
build: process.env.TRAVIS_BUILD_NUMBER
},
// For more browsers on Sauce Labs see:
// https://saucelabs.com/docs/platforms/webdriver
customLaunchers: {
@@ -49,16 +57,59 @@ module.exports = function(config, specificOptions) {
browserName: 'internet explorer',
platform: 'Windows 2012',
version: '10'
},
'BS_Chrome': {
base: 'BrowserStack',
browser: 'chrome',
os: 'OS X',
os_version: 'Mountain Lion'
},
'BS_Safari': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mountain Lion'
},
'BS_Firefox': {
base: 'BrowserStack',
browser: 'firefox',
os: 'Windows',
os_version: '8'
},
'BS_IE_8': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '8.0',
os: 'Windows',
os_version: '7'
},
'BS_IE_9': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '9.0',
os: 'Windows',
os_version: '7'
},
'BS_IE_10': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '10.0',
os: 'Windows',
os_version: '8'
},
'BS_IE_11': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '11.0',
os: 'Windows',
os_version: '8.1'
}
}
});
if (process.env.TRAVIS) {
// TODO(vojta): remove once SauceLabs supports websockets.
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
config.transports = ['xhr-polling'];
// Debug logging into a file, that we print out at the end of the build.
config.loggers.push({
type: 'file',
+47
View File
@@ -0,0 +1,47 @@
var fs = require('fs');
var http = require('http');
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
var HOSTNAME = 'localhost';
var PORTS = [9090, 9876];
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
// We need to start fake servers, otherwise the tunnel does not start.
var fakeServers = [];
var hosts = [];
PORTS.forEach(function(port) {
fakeServers.push(http.createServer(function() {}).listen(port));
hosts.push({
name: HOSTNAME,
port: port,
sslFlag: 0
});
});
var tunnel = new BrowserStackTunnel({
key: ACCESS_KEY,
hosts: hosts
});
tunnel.start(function(error) {
console.log('** callback **')
if (error) {
console.error('Can not establish the tunnel', error);
} else {
console.log('Tunnel established.');
fakeServers.forEach(function(server) {
server.close();
});
if (READY_FILE) {
fs.writeFile(READY_FILE, '');
}
}
});
tunnel.on('error', function(error) {
console.error(error);
});
+1
View File
@@ -0,0 +1 @@
node ./lib/browser-stack/start-tunnel.js &
+7 -5
View File
@@ -1,8 +1,8 @@
{
"name": "angularjs",
"version": "1.2.1",
"cdnVersion": "1.2.0",
"codename": "underscore-empathy",
"version": "1.2.3",
"cdnVersion": "1.2.2",
"codename": "unicorn-zapper",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
@@ -30,14 +30,16 @@
"yaml-js": "~0.0.8",
"marked": "0.2.9",
"rewire": "1.1.3",
"grunt-jasmine-node": "~0.1.0",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-parallel": "~0.3.1",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-merge-conflict": "~0.0.1",
"promises-aplus-tests": "~1.3.2",
"grunt-shell": "~0.4.0",
"semver": "~2.1.0",
"lodash": "~2.1.0"
"lodash": "~2.1.0",
"karma-browserstack-launcher": "~0.0.4",
"browserstacktunnel-wrapper": "~1.1.1"
},
"licenses": [
{
+23
View File
@@ -0,0 +1,23 @@
# Angular Bower Script
Script for updating the Angular bower repos from a code.angularjs.org package
Requires `node` (for parsing `bower.json`) and `wget` (for fetching the `angular.zip` from `code.angularjs.org`)
## Instructions
You need to run `./init.sh` the first time you use this script to clone all the repos.
For subsequent updates:
```shell
./publish.sh NEW_VERSION
```
Where `NEW_VERSION` is a version number like `1.2.3`.
## License
MIT
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
#
# init all of the bower repos
#
set -e # fail if any command fails
REPOS=(
angular \
angular-animate \
angular-cookies \
angular-i18n \
angular-loader \
angular-mocks \
angular-route \
angular-resource \
angular-sanitize \
angular-scenario \
angular-touch \
)
cd `dirname $0`
for repo in "${REPOS[@]}"
do
git clone git@github.com:angular/bower-$repo.git
done
+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
#
# update all the things
#
set -e # fail if any command fails
cd `dirname $0`
NEW_VERSION=$1
ZIP_FILE=angular-$NEW_VERSION.zip
ZIP_FILE_URL=http://code.angularjs.org/$NEW_VERSION/angular-$NEW_VERSION.zip
ZIP_DIR=angular-$NEW_VERSION
REPOS=(
angular \
angular-animate \
angular-cookies \
angular-i18n \
angular-loader \
angular-mocks \
angular-route \
angular-resource \
angular-sanitize \
angular-scenario \
angular-touch \
)
#
# download and unzip the file
#
#wget $ZIP_FILE_URL
unzip $ZIP_FILE
#
# move the files from the zip
#
for repo in "${REPOS[@]}"
do
if [ -f $ZIP_DIR/$repo.js ] # ignore i18l
then
cd bower-$repo
git checkout master
git reset --hard HEAD
cd ..
mv $ZIP_DIR/$repo.* bower-$repo/
fi
done
# move i18n files
mv $ZIP_DIR/i18n/*.js bower-angular-i18n/
# move csp.css
mv $ZIP_DIR/angular-csp.css bower-angular
#
# get the old version number
#
OLD_VERSION=$(node -e "console.log(require('./bower-angular/bower').version)" | sed -e 's/\r//g')
echo $OLD_VERSION
echo $NEW_VERSION
#
# update bower.json
# tag each repo
#
for repo in "${REPOS[@]}"
do
cd bower-$repo
pwd
sed -i '' -e "s/$OLD_VERSION/$NEW_VERSION/g" bower.json
git add -A
git commit -m "v$NEW_VERSION"
git tag v$NEW_VERSION
git push origin master
git push origin v$NEW_VERSION
cd ..
done
+1 -1
View File
@@ -162,4 +162,4 @@
"nullFormCtrl": false
}
}
}
+30 -18
View File
@@ -80,7 +80,7 @@
-assertArgFn,
-assertNotHasOwnProperty,
-getter,
-getBlockElements
-getBlockElements,
*/
@@ -544,7 +544,7 @@ var trim = (function() {
// TODO: we should move this into IE/ES5 polyfill
if (!String.prototype.trim) {
return function(value) {
return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value;
return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value;
};
}
return function(value) {
@@ -1102,26 +1102,38 @@ function encodeUriQuery(val, pctEncodeSpaces) {
*
* @description
*
* Use this directive to auto-bootstrap an application. Only
* one ngApp directive can be used per HTML document. The directive
* designates the root of the application and is typically placed
* at the root of the page.
* Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
* designates the **root element** of the application and is typically placed near the root element
* of the page - e.g. on the `<body>` or `<html>` tags.
*
* The first ngApp found in the document will be auto-bootstrapped. To use multiple applications in
* an HTML document you must manually bootstrap them using {@link angular.bootstrap}.
* Applications cannot be nested.
* Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
* found in the document will be used to define the root element to auto-bootstrap as an
* application. To run multiple applications in an HTML document you must manually bootstrap them using
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
*
* In the example below if the `ngApp` directive were not placed
* on the `html` element then the document would not be compiled
* and the `{{ 1+2 }}` would not be resolved to `3`.
* You can specify an **AngularJS module** to be used as the root module for the application. This
* module will be loaded into the {@link AUTO.$injector} when the application is bootstrapped and
* should contain the application code needed or have dependencies on other modules that will
* contain the code. See {@link angular.module} for more information.
*
* `ngApp` is the easiest way to bootstrap an application.
* In the example below if the `ngApp` directive were not placed on the `html` element then the
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
* would not be resolved to `3`.
*
<doc:example>
<doc:source>
I can add: 1 + 2 = {{ 1+2 }}
</doc:source>
</doc:example>
* `ngApp` is the easiest, and most common, way to bootstrap an application.
*
<example module="ngAppDemo">
<file name="index.html">
<div ng-controller="ngAppDemoController">
I can add: {{a}} + {{b}} = {{ a+b }}
</file>
<file name="script.js">
angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
$scope.a = 1;
$scope.b = 2;
});
</file>
</example>
*
*/
function angularInit(element, bootstrap) {
+5
View File
@@ -65,6 +65,7 @@
$ParseProvider,
$RootScopeProvider,
$QProvider,
$$SanitizeUriProvider,
$SceProvider,
$SceDelegateProvider,
$SnifferProvider,
@@ -136,6 +137,10 @@ function publishExternalAPI(angular){
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
// $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
$provide.provider({
$$sanitizeUri: $$SanitizeUriProvider
});
$provide.provider('$compile', $CompileProvider).
directive({
a: htmlAnchorDirective,
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {
+3 -3
View File
@@ -501,11 +501,11 @@ function annotate(fn) {
* @example
* Here are some examples of creating value services.
* <pre>
* $provide.constant('ADMIN_USER', 'admin');
* $provide.value('ADMIN_USER', 'admin');
*
* $provide.constant('RoleLookup', { admin: 0, writer: 1, reader: 2 });
* $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
*
* $provide.constant('halfOf', function(value) {
* $provide.value('halfOf', function(value) {
* return value / 2;
* });
* </pre>
+6 -1
View File
@@ -17,7 +17,12 @@ function setupModuleLoader(window) {
return obj[name] || (obj[name] = factory());
}
return ensure(ensure(window, 'angular', Object), 'module', function() {
var angular = ensure(window, 'angular', Object);
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
/** @type {Object.<string, angular.Module>} */
var modules = {};
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* (c) 2010-2014 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-2012 Google, Inc. http://angularjs.org
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
+8 -7
View File
@@ -99,13 +99,14 @@ var $AnimateProvider = ['$provide', function($provide) {
* inserted into the DOM
*/
enter : function(element, parent, after, done) {
var afterNode = after && after[after.length - 1];
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
// IE does not like undefined so we have to pass null.
var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
forEach(element, function(node) {
parentNode.insertBefore(node, afterNextSibling);
});
if (after) {
after.after(element);
} else {
if (!parent || !parent[0]) {
parent = after.parent();
}
parent.append(element);
}
done && $timeout(done, 0, false);
},
+99 -82
View File
@@ -283,7 +283,7 @@
* </div>
*
* <div class="alert alert-error">
* **Note:** The `transclude` function that is passed to the compile function is deperecated, as it
* **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
* e.g. does not know about the right outer scope. Please use the transclude function that is passed
* to the link function instead.
* </div>
@@ -493,14 +493,12 @@ var $compileMinErr = minErr('$compile');
*
* @description
*/
$CompileProvider.$inject = ['$provide'];
function $CompileProvider($provide) {
$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -584,10 +582,11 @@ function $CompileProvider($provide) {
*/
this.aHrefSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
aHrefSanitizationWhitelist = regexp;
$$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
return this;
} else {
return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
}
return aHrefSanitizationWhitelist;
};
@@ -614,18 +613,18 @@ function $CompileProvider($provide) {
*/
this.imgSrcSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
imgSrcSanitizationWhitelist = regexp;
$$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
return this;
} else {
return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
}
return imgSrcSanitizationWhitelist;
};
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
'$controller', '$rootScope', '$document', '$sce', '$animate',
'$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
$controller, $rootScope, $document, $sce, $animate) {
$controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
var Attributes = function(element, attr) {
this.$$element = element;
@@ -672,6 +671,24 @@ function $CompileProvider($provide) {
}
},
/**
* @ngdoc function
* @name ng.$compile.directive.Attributes#$updateClass
* @methodOf ng.$compile.directive.Attributes
* @function
*
* @description
* Adds and removes the appropriate CSS class values to the element based on the difference
* between the new and old CSS class values (specified as newClasses and oldClasses).
*
* @param {string} newClasses The current CSS className value
* @param {string} oldClasses The former CSS className value
*/
$updateClass : function(newClasses, oldClasses) {
this.$removeClass(tokenDifference(oldClasses, newClasses));
this.$addClass(tokenDifference(newClasses, oldClasses));
},
/**
* Set a normalized attribute on the element in a way such that all directives
* can share the attribute. This function properly handles boolean attributes.
@@ -682,59 +699,44 @@ function $CompileProvider($provide) {
* @param {string=} attrName Optional none normalized name. Defaults to key.
*/
$set: function(key, value, writeAttr, attrName) {
//special case for class attribute addition + removal
//so that class changes can tap into the animation
//hooks provided by the $animate service
if(key == 'class') {
value = value || '';
var current = this.$$element.attr('class') || '';
this.$removeClass(tokenDifference(current, value).join(' '));
this.$addClass(tokenDifference(value, current).join(' '));
// TODO: decide whether or not to throw an error if "class"
//is set through this function since it may cause $updateClass to
//become unstable.
var booleanKey = getBooleanAttrName(this.$$element[0], key),
normalizedVal,
nodeName;
if (booleanKey) {
this.$$element.prop(key, value);
attrName = booleanKey;
}
this[key] = value;
// translate normalized key to actual key
if (attrName) {
this.$attr[key] = attrName;
} else {
var booleanKey = getBooleanAttrName(this.$$element[0], key),
normalizedVal,
nodeName;
if (booleanKey) {
this.$$element.prop(key, value);
attrName = booleanKey;
attrName = this.$attr[key];
if (!attrName) {
this.$attr[key] = attrName = snake_case(key, '-');
}
}
this[key] = value;
nodeName = nodeName_(this.$$element);
// translate normalized key to actual key
if (attrName) {
this.$attr[key] = attrName;
// sanitize a[href] and img[src] values
if ((nodeName === 'A' && key === 'href') ||
(nodeName === 'IMG' && key === 'src')) {
this[key] = value = $$sanitizeUri(value, key === 'src');
}
if (writeAttr !== false) {
if (value === null || value === undefined) {
this.$$element.removeAttr(attrName);
} else {
attrName = this.$attr[key];
if (!attrName) {
this.$attr[key] = attrName = snake_case(key, '-');
}
}
nodeName = nodeName_(this.$$element);
// sanitize a[href] and img[src] values
if ((nodeName === 'A' && key === 'href') ||
(nodeName === 'IMG' && key === 'src')) {
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
if (!msie || msie >= 8 ) {
normalizedVal = urlResolve(value).href;
if (normalizedVal !== '') {
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
this[key] = value = 'unsafe:' + normalizedVal;
}
}
}
}
if (writeAttr !== false) {
if (value === null || value === undefined) {
this.$$element.removeAttr(attrName);
} else {
this.$$element.attr(attrName, value);
}
this.$$element.attr(attrName, value);
}
}
@@ -747,22 +749,6 @@ function $CompileProvider($provide) {
$exceptionHandler(e);
}
});
function tokenDifference(str1, str2) {
var values = [],
tokens1 = str1.split(/\s+/),
tokens2 = str2.split(/\s+/);
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;
}
},
@@ -1784,10 +1770,15 @@ function $CompileProvider($provide) {
function getTrustedContext(node, attrNormalizedName) {
if (attrNormalizedName == "srcdoc") {
return $sce.HTML;
}
var tag = nodeName_(node);
// maction[xlink:href] can source SVG. It's not limited to <maction>.
if (attrNormalizedName == "xlinkHref" ||
(nodeName_(node) != "IMG" && (attrNormalizedName == "src" ||
attrNormalizedName == "ngSrc"))) {
(tag == "FORM" && attrNormalizedName == "action") ||
(tag != "IMG" && (attrNormalizedName == "src" ||
attrNormalizedName == "ngSrc"))) {
return $sce.RESOURCE_URL;
}
}
@@ -1832,9 +1823,19 @@ function $CompileProvider($provide) {
attr[name] = interpolateFn(scope);
($$observers[name] || ($$observers[name] = [])).$$inter = true;
(attr.$$observers && attr.$$observers[name].$$scope || scope).
$watch(interpolateFn, function interpolateFnWatchAction(value) {
attr.$set(name, value);
});
$watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
//special case for class attribute addition + removal
//so that class changes can tap into the animation
//hooks provided by the $animate service. Be sure to
//skip animations when the first digest occurs (when
//both the new and the old values are the same) since
//the CSS classes are the non-interpolated values
if(name === 'class' && newValue != oldValue) {
attr.$updateClass(newValue, oldValue);
} else {
attr.$set(name, newValue);
}
});
}
};
}
@@ -1974,3 +1975,19 @@ function directiveLinkingFn(
/* Element */ rootElement,
/* function(Function) */ boundTranscludeFn
){}
function tokenDifference(str1, str2) {
var values = '',
tokens1 = str1.split(/\s+/),
tokens2 = str2.split(/\s+/);
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 += (values.length > 0 ? ' ' : '') + token;
}
return values;
}
+22 -5
View File
@@ -149,8 +149,11 @@
*
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as disabled. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngDisabled` directive solves this problem for the `disabled` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* @example
<doc:example>
@@ -181,8 +184,11 @@
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as checked. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngChecked` directive solves this problem for the `checked` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
<doc:example>
<doc:source>
@@ -212,8 +218,12 @@
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as readonly. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngReadonly` directive solves this problem for the `readonly` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
<doc:example>
<doc:source>
@@ -243,8 +253,11 @@
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as selected. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngSelected` directive solves this problem for the `selected` atttribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
<doc:example>
<doc:source>
@@ -276,8 +289,12 @@
* @description
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as open. (Their presence means true and their absence means false.)
* This prevents the Angular compiler from retrieving the binding expression.
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngOpen` directive solves this problem for the `open` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* @example
<doc:example>
+16 -3
View File
@@ -392,8 +392,21 @@ var inputType = {
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
// In composition mode, users are still inputing intermediate text buffer,
// hold the listener until composition is done.
// More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
var composing = false;
element.on('compositionstart', function() {
composing = true;
});
element.on('compositionend', function() {
composing = false;
});
var listener = function() {
if (composing) return;
var value = element.val();
// By default we will trim the value
@@ -436,15 +449,15 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
deferListener();
});
// if user paste into input using mouse, we need "change" event to catch it
element.on('change', listener);
// if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
if ($sniffer.hasEvent('paste')) {
element.on('paste cut', deferListener);
}
}
// if user paste into input using mouse on older browser
// or form autocomplete on newer browser, we need "change" event to catch it
element.on('change', listener);
ctrl.$render = function() {
element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
+9 -17
View File
@@ -20,11 +20,10 @@ function classDirective(name, selector) {
// jshint bitwise: false
var mod = $index & 1;
if (mod !== old$index & 1) {
if (mod === selector) {
addClass(scope.$eval(attr[name]));
} else {
removeClass(scope.$eval(attr[name]));
}
var classes = flattenClasses(scope.$eval(attr[name]));
mod === selector ?
attr.$addClass(classes) :
attr.$removeClass(classes);
}
});
}
@@ -32,24 +31,17 @@ function classDirective(name, selector) {
function ngClassWatchAction(newVal) {
if (selector === true || scope.$index % 2 === selector) {
if (oldVal && !equals(newVal,oldVal)) {
removeClass(oldVal);
var newClasses = flattenClasses(newVal || '');
if(!oldVal) {
attr.$addClass(newClasses);
} else if(!equals(newVal,oldVal)) {
attr.$updateClass(newClasses, flattenClasses(oldVal));
}
addClass(newVal);
}
oldVal = copy(newVal);
}
function removeClass(classVal) {
attr.$removeClass(flattenClasses(classVal));
}
function addClass(classVal) {
attr.$addClass(flattenClasses(classVal));
}
function flattenClasses(classVal) {
if(isArray(classVal)) {
return classVal.join(' ');
+2 -1
View File
@@ -167,6 +167,7 @@
var ngControllerDirective = [function() {
return {
scope: true,
controller: '@'
controller: '@',
priority: 500
};
}];
+15 -10
View File
@@ -188,18 +188,23 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
if (thisChangeId !== changeCounter) return;
var newScope = scope.$new();
$transclude(newScope, function(clone) {
cleanupLastIncludeContent();
// Note: This will also link all children of ng-include that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
// However, using ng-include on an element with additional content does not make sense...
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, noop);
cleanupLastIncludeContent();
currentScope = newScope;
currentElement = clone;
currentScope = newScope;
currentElement = clone;
currentElement.html(response);
$animate.enter(currentElement, null, $element, afterAnimation);
$compile(currentElement.contents())(currentScope);
currentScope.$emit('$includeContentLoaded');
scope.$eval(onloadExp);
});
currentElement.html(response);
$animate.enter(currentElement, null, $element, afterAnimation);
$compile(currentElement.contents())(currentScope);
currentScope.$emit('$includeContentLoaded');
scope.$eval(onloadExp);
}).error(function() {
if (thisChangeId === changeCounter) cleanupLastIncludeContent();
});
+1 -1
View File
@@ -50,7 +50,7 @@
* other numbers, for example 12, so that instead of showing "12 people are viewing", you can
* show "a dozen people are viewing".
*
* You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
* You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted
* into pluralized strings. In the previous example, Angular will replace `{}` with
* <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
* for <span ng-non-bindable>{{numberExpression}}</span>.
+1 -1
View File
@@ -99,7 +99,7 @@
* For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
* `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
* with the corresponding item in the array by identity. Moving the same object in array would move the DOM
* element in the same way ian the DOM.
* element in the same way in the DOM.
*
* For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
* case the object identity does not matter. Two objects are considered equivalent as long as their `id`
+23 -11
View File
@@ -28,12 +28,13 @@ var XHR = window.XMLHttpRequest || function() {
*/
function $HttpBackendProvider() {
this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks,
$document[0], $window.location.protocol.replace(':', ''));
return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, $document[0]);
}];
}
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument) {
var ABORTED = -1;
// TODO(vojta): fix the signature
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
var status;
@@ -69,13 +70,19 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
// always async
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var responseHeaders = xhr.getAllResponseHeaders();
var responseHeaders = null,
response = null;
if(status !== ABORTED) {
responseHeaders = xhr.getAllResponseHeaders();
response = xhr.responseType ? xhr.response : xhr.responseText;
}
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
completeRequest(callback,
status || xhr.status,
(xhr.responseType ? xhr.response : xhr.responseText),
response,
responseHeaders);
}
};
@@ -99,20 +106,20 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
function timeoutRequest() {
status = -1;
status = ABORTED;
jsonpDone && jsonpDone();
xhr && xhr.abort();
}
function completeRequest(callback, status, response, headersString) {
var protocol = locationProtocol || urlResolve(url).protocol;
var protocol = urlResolve(url).protocol;
// cancel timeout and subsequent timeout promise resolution
timeoutId && $browserDefer.cancel(timeoutId);
jsonpDone = xhr = null;
// fix status code for file protocol (it's always 0)
status = (protocol == 'file') ? (response ? 200 : 404) : status;
status = (protocol == 'file' && status === 0) ? (response ? 200 : 404) : status;
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
status = status == 1223 ? 204 : status;
@@ -128,6 +135,7 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
// - adds and immediately removes script elements from the document
var script = rawDocument.createElement('script'),
doneWrapper = function() {
script.onreadystatechange = script.onload = script.onerror = null;
rawDocument.body.removeChild(script);
if (done) done();
};
@@ -135,12 +143,16 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
script.type = 'text/javascript';
script.src = url;
if (msie) {
if (msie && msie <= 8) {
script.onreadystatechange = function() {
if (/loaded|complete/.test(script.readyState)) doneWrapper();
if (/loaded|complete/.test(script.readyState)) {
doneWrapper();
}
};
} else {
script.onload = script.onerror = doneWrapper;
script.onload = script.onerror = function() {
doneWrapper();
};
}
rawDocument.body.appendChild(script);
+40
View File
@@ -179,7 +179,47 @@ function LocationHashbangUrl(appBase, hashPrefix) {
hashPrefix);
}
parseAppUrl(withoutHashUrl, this, appBase);
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
this.$$compose();
/*
* In Windows, on an anchor node on documents loaded from
* the filesystem, the browser will return a pathname
* prefixed with the drive name ('/C:/path') when a
* pathname without a drive is set:
* * a.setAttribute('href', '/foo')
* * a.pathname === '/C:/foo' //true
*
* Inside of Angular, we're always using pathnames that
* do not include drive names for routing.
*/
function removeWindowsDriveName (path, url, base) {
/*
Matches paths for file protocol on windows,
such as /C:/foo/bar, and captures only /foo/bar.
*/
var windowsFilePathExp = /^\/?.*?:(\/.*)/;
var firstPathSegmentMatch;
//Get the relative path from the input URL.
if (url.indexOf(base) === 0) {
url = url.replace(base, '');
}
/*
* The input URL intentionally contains a
* first path segment that ends with a colon.
*/
if (windowsFilePathExp.exec(url)) {
return path;
}
firstPathSegmentMatch = windowsFilePathExp.exec(path);
return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
}
};
/**
+1 -1
View File
@@ -11,7 +11,7 @@
*
* The main purpose of this service is to simplify debugging and troubleshooting.
*
* The default is not to log `debug` messages. You can use
* The default is to log `debug` messages. You can use
* {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
*
* @example
+1 -1
View File
@@ -1017,7 +1017,7 @@ function getterFn(path, options, fullExp) {
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
(options.unwrapPromises
? 'if (s && s.then) {\n' +
' pw("' + fullExp.replace(/\"/g, '\\"') + '");\n' +
' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' +
' if (!("$$v" in s)) {\n' +
' p=s;\n' +
' p.$$v = undefined;\n' +
+74
View File
@@ -0,0 +1,74 @@
'use strict';
/**
* @description
* Private service to sanitize uris for links and images. Used by $compile and $sanitize.
*/
function $$SanitizeUriProvider() {
var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
/**
* @description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during a[href] sanitization.
*
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
*
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
*
* @param {RegExp=} regexp New regexp to whitelist urls with.
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
* chaining otherwise.
*/
this.aHrefSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
aHrefSanitizationWhitelist = regexp;
return this;
}
return aHrefSanitizationWhitelist;
};
/**
* @description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during img[src] sanitization.
*
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
*
* Any url about to be assigned to img[src] via data-binding is first normalized and turned into
* an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
*
* @param {RegExp=} regexp New regexp to whitelist urls with.
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
* chaining otherwise.
*/
this.imgSrcSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
imgSrcSanitizationWhitelist = regexp;
return this;
}
return imgSrcSanitizationWhitelist;
};
this.$get = function() {
return function sanitizeUri(uri, isImage) {
var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
var normalizedVal;
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
if (!msie || msie >= 8 ) {
normalizedVal = urlResolve(uri).href;
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
return 'unsafe:'+normalizedVal;
}
}
return uri;
};
};
}
+8 -12
View File
@@ -199,8 +199,7 @@ function $SceDelegateProvider() {
return resourceUrlBlacklist;
};
this.$get = ['$log', '$document', '$injector', function(
$log, $document, $injector) {
this.$get = ['$injector', function($injector) {
var htmlSanitizer = function htmlSanitizer(html) {
throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
@@ -731,18 +730,15 @@ function $SceProvider() {
* sce.js and sceSpecs.js would need to be aware of this detail.
*/
this.$get = ['$parse', '$document', '$sceDelegate', function(
$parse, $document, $sceDelegate) {
this.$get = ['$parse', '$sniffer', '$sceDelegate', function(
$parse, $sniffer, $sceDelegate) {
// Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows
// the "expression(javascript expression)" syntax which is insecure.
if (enabled && msie) {
var documentMode = $document[0].documentMode;
if (documentMode !== undefined && documentMode < 8) {
throw $sceMinErr('iequirks',
'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
}
if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) {
throw $sceMinErr('iequirks',
'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' +
'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
}
var sce = copy(SCE_CONTEXTS);
+4 -2
View File
@@ -22,6 +22,7 @@ function $SnifferProvider() {
int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
document = $document[0] || {},
documentMode = document.documentMode,
vendorPrefix,
vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
bodyStyle = document.body && document.body.style,
@@ -66,7 +67,7 @@ function $SnifferProvider() {
// jshint +W018
hashchange: 'onhashchange' in $window &&
// IE8 compatible mode lies
(!document.documentMode || document.documentMode > 7),
(!documentMode || documentMode > 7),
hasEvent: function(event) {
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
// it. In particular the event is not fired when backspace or delete key are pressed or
@@ -84,7 +85,8 @@ function $SnifferProvider() {
vendorPrefix: vendorPrefix,
transitions : transitions,
animations : animations,
msie : msie
msie : msie,
msieDocumentMode: documentMode
};
}];
}
+4 -44
View File
@@ -7,11 +7,6 @@
// exactly the behavior needed here. There is little value is mocking these out for this
// service.
var urlParsingNode = document.createElement("a");
/*
Matches paths for file protocol on windows,
such as /C:/foo/bar, and captures only /foo/bar.
*/
var windowsFilePathExp = /^\/?.*?:(\/.*)/;
var originUrl = urlResolve(window.location.href, true);
@@ -68,8 +63,7 @@ var originUrl = urlResolve(window.location.href, true);
*
*/
function urlResolve(url, base) {
var href = url,
pathname;
var href = url;
if (msie) {
// Normalize before parse. Refer Implementation Notes on why this is
@@ -80,21 +74,6 @@ function urlResolve(url, base) {
urlParsingNode.setAttribute('href', href);
/*
* In Windows, on an anchor node on documents loaded from
* the filesystem, the browser will return a pathname
* prefixed with the drive name ('/C:/path') when a
* pathname without a drive is set:
* * a.setAttribute('href', '/foo')
* * a.pathname === '/C:/foo' //true
*
* Inside of Angular, we're always using pathnames that
* do not include drive names for routing.
*/
pathname = removeWindowsDriveName(urlParsingNode.pathname, url, base);
pathname = (pathname.charAt(0) === '/') ? pathname : '/' + pathname;
// urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
return {
href: urlParsingNode.href,
@@ -104,11 +83,12 @@ function urlResolve(url, base) {
hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
hostname: urlParsingNode.hostname,
port: urlParsingNode.port,
pathname: pathname
pathname: (urlParsingNode.pathname.charAt(0) === '/')
? urlParsingNode.pathname
: '/' + urlParsingNode.pathname
};
}
/**
* Parse a request URL and determine whether this is a same-origin request as the application document.
*
@@ -121,23 +101,3 @@ function urlIsSameOrigin(requestUrl) {
return (parsed.protocol === originUrl.protocol &&
parsed.host === originUrl.host);
}
function removeWindowsDriveName (path, url, base) {
var firstPathSegmentMatch;
//Get the relative path from the input URL.
if (url.indexOf(base) === 0) {
url = url.replace(base, '');
}
/*
* The input URL intentionally contains a
* first path segment that ends with a colon.
*/
if (windowsFilePathExp.exec(url)) {
return path;
}
firstPathSegmentMatch = windowsFilePathExp.exec(path);
return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
}
+103 -35
View File
@@ -159,7 +159,7 @@
* }
* </pre>
*
* Staggering animations work by default in ngRepeat (so long as the CSS class is defiend). Outside of ngRepeat, to use staggering animations
* Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
* on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
* are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
* will also be reset if more than 10ms has passed after the last animation has been fired.
@@ -222,7 +222,7 @@
* JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
* a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
* the element's CSS class attribute value and then run the matching animation event function (if found).
* In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function
* In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will
* be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
*
* Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
@@ -263,9 +263,16 @@ angular.module('ngAnimate', ['ng'])
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
// disable animations during bootstrap, but once we bootstrapped, enable animations
// disable animations during bootstrap, but once we bootstrapped, wait again
// for another digest until enabling animations. The reason why we digest twice
// is because all structural animations (enter, leave and move) all perform a
// post digest operation before animating. If we only wait for a single digest
// to pass then the structural animation would render its animation on page load.
// (which is what we're trying to avoid when the application first boots up.)
$rootScope.$$postDigest(function() {
rootAnimateState.running = false;
$rootScope.$$postDigest(function() {
rootAnimateState.running = false;
});
});
function lookup(name) {
@@ -510,6 +517,7 @@ angular.module('ngAnimate', ['ng'])
* @function
*
* @param {boolean=} value If provided then set the animation on or off.
* @param {jQuery/jqLite element=} element If provided then the element will be used to represent the enable/disable operation
* @return {boolean} Current animation state.
*
* @description
@@ -548,7 +556,8 @@ angular.module('ngAnimate', ['ng'])
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
var classes = (element.attr('class') || '') + ' ' + className;
var currentClassName = element.attr('class') || '';
var classes = currentClassName + ' ' + className;
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) {
parentElement = afterElement ? afterElement.parent() : element.parent();
@@ -563,7 +572,7 @@ angular.module('ngAnimate', ['ng'])
//the animation if any matching animations are not found at all.
//NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
if (animationsDisabled(element, parentElement) || matches.length === 0) {
domOperation();
fireDOMOperation();
closeAnimation();
return;
}
@@ -596,27 +605,48 @@ angular.module('ngAnimate', ['ng'])
//this would mean that an animation was not allowed so let the existing
//animation do it's thing and close this one early
if(animations.length === 0) {
domOperation();
fireDOMOperation();
fireDoneCallbackAsync();
return;
}
//this value will be searched for class-based CSS className lookup. Therefore,
//we prefix and suffix the current className value with spaces to avoid substring
//lookups of className tokens
var futureClassName = ' ' + currentClassName + ' ';
if(ngAnimateState.running) {
//if an animation is currently running on the element then lets take the steps
//to cancel that animation and fire any required callbacks
$timeout.cancel(ngAnimateState.closeAnimationTimeout);
cleanup(element);
cancelAnimations(ngAnimateState.animations);
(ngAnimateState.done || noop)(true);
//if the class is removed during the reflow then it will revert the styles temporarily
//back to the base class CSS styling causing a jump-like effect to occur. This check
//here ensures that the domOperation is only performed after the reflow has commenced
if(ngAnimateState.beforeComplete) {
(ngAnimateState.done || noop)(true);
} else if(isClassBased && !ngAnimateState.structural) {
//class-based animations will compare element className values after cancelling the
//previous animation to see if the element properties already contain the final CSS
//class and if so then the animation will be skipped. Since the domOperation will
//be performed only after the reflow is complete then our element's className value
//will be invalid. Therefore the same string manipulation that would occur within the
//DOM operation will be performed below so that the class comparison is valid...
futureClassName = ngAnimateState.event == 'removeClass' ?
futureClassName.replace(ngAnimateState.className, '') :
futureClassName + ngAnimateState.className + ' ';
}
}
//There is no point in perform a class-based animation if the element already contains
//(on addClass) or doesn't contain (on removeClass) the className being animated.
//The reason why this is being called after the previous animations are cancelled
//is so that the CSS classes present on the element can be properly examined.
if((animationEvent == 'addClass' && element.hasClass(className)) ||
(animationEvent == 'removeClass' && !element.hasClass(className))) {
domOperation();
var classNameToken = ' ' + className + ' ';
if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
(animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
fireDOMOperation();
fireDoneCallbackAsync();
return;
}
@@ -627,6 +657,8 @@ angular.module('ngAnimate', ['ng'])
element.data(NG_ANIMATE_STATE, {
running:true,
event:animationEvent,
className:className,
structural:!isClassBased,
animations:animations,
done:onBeforeAnimationsComplete
@@ -637,7 +669,7 @@ angular.module('ngAnimate', ['ng'])
invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
function onBeforeAnimationsComplete(cancelled) {
domOperation();
fireDOMOperation();
if(cancelled === true) {
closeAnimation();
return;
@@ -695,6 +727,15 @@ angular.module('ngAnimate', ['ng'])
doneCallback && $timeout(doneCallback, 0, false);
}
//it is less complicated to use a flag than managing and cancelling
//timeouts containing multiple callbacks.
function fireDOMOperation() {
if(!fireDOMOperation.hasBeenRun) {
fireDOMOperation.hasBeenRun = true;
domOperation();
}
}
function closeAnimation() {
if(!closeAnimation.hasBeenRun) {
closeAnimation.hasBeenRun = true;
@@ -737,10 +778,10 @@ angular.module('ngAnimate', ['ng'])
function cancelAnimations(animations) {
var isCancelledFlag = true;
forEach(animations, function(animation) {
if(!animations['beforeComplete']) {
if(!animations.beforeComplete) {
(animation.beforeEnd || noop)(isCancelledFlag);
}
if(!animations['afterComplete']) {
if(!animations.afterComplete) {
(animation.afterEnd || noop)(isCancelledFlag);
}
});
@@ -842,13 +883,6 @@ angular.module('ngAnimate', ['ng'])
}, 10, false);
}
function applyStyle(node, style) {
var oldStyle = node.getAttribute('style') || '';
var newStyle = (oldStyle.length > 0 ? '; ' : '') + style;
node.setAttribute('style', newStyle);
return oldStyle;
}
function getElementAnimationDetails(element, cacheKey) {
var data = cacheKey ? lookupCache[cacheKey] : null;
if(!data) {
@@ -967,7 +1001,9 @@ angular.module('ngAnimate', ['ng'])
if(timings.transitionDuration > 0) {
element.addClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
activeClassName += NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME + ' ';
node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
blockTransitions(element);
} else {
blockKeyframeAnimations(element);
}
forEach(className.split(' '), function(klass, i) {
@@ -987,6 +1023,28 @@ angular.module('ngAnimate', ['ng'])
return true;
}
function blockTransitions(element) {
element[0].style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
}
function blockKeyframeAnimations(element) {
element[0].style[ANIMATION_PROP] = 'none 0s';
}
function unblockTransitions(element) {
var node = element[0], prop = TRANSITION_PROP + PROPERTY_KEY;
if(node.style[prop] && node.style[prop].length > 0) {
node.style[prop] = '';
}
}
function unblockKeyframeAnimations(element) {
var node = element[0], prop = ANIMATION_PROP;
if(node.style[prop] && node.style[prop].length > 0) {
element[0].style[prop] = '';
}
}
function animateRun(element, className, activeAnimationComplete) {
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(!element.hasClass(className) || !data) {
@@ -1002,19 +1060,18 @@ angular.module('ngAnimate', ['ng'])
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000;
var startTime = Date.now();
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
var formerStyle;
var ii = data.ii;
var applyFallbackStyle, style = '';
var applyFallbackStyle, style = '', appliedStyles = [];
if(timings.transitionDuration > 0) {
node.style[TRANSITION_PROP + PROPERTY_KEY] = '';
var propertyStyle = timings.transitionPropertyStyle;
if(propertyStyle.indexOf('all') == -1) {
applyFallbackStyle = true;
var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'clip';
var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'border-spacing';
style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; ';
style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; ';
appliedStyles.push(CSS_PREFIX + 'transition-property');
appliedStyles.push(CSS_PREFIX + 'transition-duration');
}
}
@@ -1027,16 +1084,19 @@ angular.module('ngAnimate', ['ng'])
style += CSS_PREFIX + 'transition-delay: ' +
prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
appliedStyles.push(CSS_PREFIX + 'transition-delay');
}
if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
style += CSS_PREFIX + 'animation-delay: ' +
prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
appliedStyles.push(CSS_PREFIX + 'animation-delay');
}
}
if(style.length > 0) {
formerStyle = applyStyle(node, style);
if(appliedStyles.length > 0) {
var oldStyle = node.getAttribute('style') || '';
node.setAttribute('style', oldStyle + ' ' + style);
}
element.on(css3AnimationEvents, onAnimationProgress);
@@ -1049,10 +1109,8 @@ angular.module('ngAnimate', ['ng'])
element.off(css3AnimationEvents, onAnimationProgress);
element.removeClass(activeClassName);
animateClose(element, className);
if(formerStyle != null) {
formerStyle.length > 0 ?
node.setAttribute('style', formerStyle) :
node.removeAttribute('style');
for (var i in appliedStyles) {
node.style.removeProperty(appliedStyles[i]);
}
};
@@ -1116,6 +1174,8 @@ angular.module('ngAnimate', ['ng'])
//happen in the first place
var cancel = preReflowCancellation;
afterReflow(function() {
unblockTransitions(element);
unblockKeyframeAnimations(element);
//once the reflow is complete then we point cancel to
//the new cancellation function which will remove all of the
//animation properties from the active animation
@@ -1179,7 +1239,11 @@ angular.module('ngAnimate', ['ng'])
beforeAddClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
if(cancellationMethod) {
afterReflow(animationCompleted);
afterReflow(function() {
unblockTransitions(element);
unblockKeyframeAnimations(element);
animationCompleted();
});
return cancellationMethod;
}
animationCompleted();
@@ -1192,7 +1256,11 @@ angular.module('ngAnimate', ['ng'])
beforeRemoveClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
if(cancellationMethod) {
afterReflow(animationCompleted);
afterReflow(function() {
unblockTransitions(element);
unblockKeyframeAnimations(element);
animationCompleted();
});
return cancellationMethod;
}
animationCompleted();
+196 -206
View File
@@ -1,13 +1,5 @@
'use strict';
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*
* TODO(vojta): wrap whole file into closure during build
*/
/**
* @ngdoc overview
* @name angular.mock
@@ -560,210 +552,208 @@ angular.mock.$IntervalProvider = function() {
* This directive should go inside the anonymous function but a bug in JSHint means that it would
* not be enacted early enough to prevent the warning.
*/
(function() {
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
function jsonStringToDate(string) {
var match;
if (match = string.match(R_ISO8061_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
}
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour,
int(match[5]||0) - tzMin,
int(match[6]||0),
int(match[7]||0));
return date;
function jsonStringToDate(string) {
var match;
if (match = string.match(R_ISO8061_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
}
return string;
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour,
int(match[5]||0) - tzMin,
int(match[6]||0),
int(match[7]||0));
return date;
}
return string;
}
function int(str) {
return parseInt(str, 10);
}
function padNumber(num, digits, trim) {
var neg = '';
if (num < 0) {
neg = '-';
num = -num;
}
num = '' + num;
while(num.length < digits) num = '0' + num;
if (trim)
num = num.substr(num.length - digits);
return neg + num;
function int(str) {
return parseInt(str, 10);
}
function padNumber(num, digits, trim) {
var neg = '';
if (num < 0) {
neg = '-';
num = -num;
}
num = '' + num;
while(num.length < digits) num = '0' + num;
if (trim)
num = num.substr(num.length - digits);
return neg + num;
}
/**
* @ngdoc object
* @name angular.mock.TzDate
* @description
*
* *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
*
* Mock of the Date type which has its timezone specified via constructor arg.
*
* The main purpose is to create Date-like instances with timezone fixed to the specified timezone
* offset, so that we can test code that depends on local timezone settings without dependency on
* the time zone settings of the machine where the code is running.
*
* @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
*
* @example
* !!!! WARNING !!!!!
* This is not a complete Date object so only methods that were implemented can be called safely.
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
*
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
* incomplete we might be missing some non-standard methods. This can result in errors like:
* "Date.prototype.foo called on incompatible Object".
*
* <pre>
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
* newYearInBratislava.getTimezoneOffset() => -60;
* newYearInBratislava.getFullYear() => 2010;
* newYearInBratislava.getMonth() => 0;
* newYearInBratislava.getDate() => 1;
* newYearInBratislava.getHours() => 0;
* newYearInBratislava.getMinutes() => 0;
* newYearInBratislava.getSeconds() => 0;
* </pre>
*
*/
angular.mock.TzDate = function (offset, timestamp) {
var self = new Date(0);
if (angular.isString(timestamp)) {
var tsStr = timestamp;
/**
* @ngdoc object
* @name angular.mock.TzDate
* @description
*
* *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
*
* Mock of the Date type which has its timezone specified via constructor arg.
*
* The main purpose is to create Date-like instances with timezone fixed to the specified timezone
* offset, so that we can test code that depends on local timezone settings without dependency on
* the time zone settings of the machine where the code is running.
*
* @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
*
* @example
* !!!! WARNING !!!!!
* This is not a complete Date object so only methods that were implemented can be called safely.
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
*
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
* incomplete we might be missing some non-standard methods. This can result in errors like:
* "Date.prototype.foo called on incompatible Object".
*
* <pre>
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
* newYearInBratislava.getTimezoneOffset() => -60;
* newYearInBratislava.getFullYear() => 2010;
* newYearInBratislava.getMonth() => 0;
* newYearInBratislava.getDate() => 1;
* newYearInBratislava.getHours() => 0;
* newYearInBratislava.getMinutes() => 0;
* newYearInBratislava.getSeconds() => 0;
* </pre>
*
*/
angular.mock.TzDate = function (offset, timestamp) {
var self = new Date(0);
if (angular.isString(timestamp)) {
var tsStr = timestamp;
self.origDate = jsonStringToDate(timestamp);
self.origDate = jsonStringToDate(timestamp);
timestamp = self.origDate.getTime();
if (isNaN(timestamp))
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
} else {
self.origDate = new Date(timestamp);
}
var localOffset = new Date(timestamp).getTimezoneOffset();
self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
self.date = new Date(timestamp + self.offsetDiff);
self.getTime = function() {
return self.date.getTime() - self.offsetDiff;
};
self.toLocaleDateString = function() {
return self.date.toLocaleDateString();
};
self.getFullYear = function() {
return self.date.getFullYear();
};
self.getMonth = function() {
return self.date.getMonth();
};
self.getDate = function() {
return self.date.getDate();
};
self.getHours = function() {
return self.date.getHours();
};
self.getMinutes = function() {
return self.date.getMinutes();
};
self.getSeconds = function() {
return self.date.getSeconds();
};
self.getMilliseconds = function() {
return self.date.getMilliseconds();
};
self.getTimezoneOffset = function() {
return offset * 60;
};
self.getUTCFullYear = function() {
return self.origDate.getUTCFullYear();
};
self.getUTCMonth = function() {
return self.origDate.getUTCMonth();
};
self.getUTCDate = function() {
return self.origDate.getUTCDate();
};
self.getUTCHours = function() {
return self.origDate.getUTCHours();
};
self.getUTCMinutes = function() {
return self.origDate.getUTCMinutes();
};
self.getUTCSeconds = function() {
return self.origDate.getUTCSeconds();
};
self.getUTCMilliseconds = function() {
return self.origDate.getUTCMilliseconds();
};
self.getDay = function() {
return self.date.getDay();
};
// provide this method only on browsers that already have it
if (self.toISOString) {
self.toISOString = function() {
return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
padNumber(self.origDate.getUTCDate(), 2) + 'T' +
padNumber(self.origDate.getUTCHours(), 2) + ':' +
padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z';
timestamp = self.origDate.getTime();
if (isNaN(timestamp))
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
}
} else {
self.origDate = new Date(timestamp);
}
//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getUTCDay',
'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
var localOffset = new Date(timestamp).getTimezoneOffset();
self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
self.date = new Date(timestamp + self.offsetDiff);
angular.forEach(unimplementedMethods, function(methodName) {
self[methodName] = function() {
throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
};
});
return self;
self.getTime = function() {
return self.date.getTime() - self.offsetDiff;
};
//make "tzDateInstance instanceof Date" return true
angular.mock.TzDate.prototype = Date.prototype;
})();
self.toLocaleDateString = function() {
return self.date.toLocaleDateString();
};
self.getFullYear = function() {
return self.date.getFullYear();
};
self.getMonth = function() {
return self.date.getMonth();
};
self.getDate = function() {
return self.date.getDate();
};
self.getHours = function() {
return self.date.getHours();
};
self.getMinutes = function() {
return self.date.getMinutes();
};
self.getSeconds = function() {
return self.date.getSeconds();
};
self.getMilliseconds = function() {
return self.date.getMilliseconds();
};
self.getTimezoneOffset = function() {
return offset * 60;
};
self.getUTCFullYear = function() {
return self.origDate.getUTCFullYear();
};
self.getUTCMonth = function() {
return self.origDate.getUTCMonth();
};
self.getUTCDate = function() {
return self.origDate.getUTCDate();
};
self.getUTCHours = function() {
return self.origDate.getUTCHours();
};
self.getUTCMinutes = function() {
return self.origDate.getUTCMinutes();
};
self.getUTCSeconds = function() {
return self.origDate.getUTCSeconds();
};
self.getUTCMilliseconds = function() {
return self.origDate.getUTCMilliseconds();
};
self.getDay = function() {
return self.date.getDay();
};
// provide this method only on browsers that already have it
if (self.toISOString) {
self.toISOString = function() {
return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
padNumber(self.origDate.getUTCDate(), 2) + 'T' +
padNumber(self.origDate.getUTCHours(), 2) + ':' +
padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z';
};
}
//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getUTCDay',
'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
angular.forEach(unimplementedMethods, function(methodName) {
self[methodName] = function() {
throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
};
});
return self;
};
//make "tzDateInstance instanceof Date" return true
angular.mock.TzDate.prototype = Date.prototype;
/* jshint +W101 */
angular.mock.animate = angular.module('mock.animate', ['ng'])
@@ -1572,7 +1562,7 @@ function MockHttpExpectation(method, url, data, headers) {
if (angular.isUndefined(data)) return true;
if (data && angular.isFunction(data.test)) return data.test(d);
if (data && angular.isFunction(data)) return data(d);
if (data && !angular.isString(data)) return angular.toJson(data) == d;
if (data && !angular.isString(data)) return angular.equals(data, angular.fromJson(d));
return data == d;
};
@@ -1919,9 +1909,13 @@ angular.mock.clearDataCache = function() {
(window.jasmine || window.mocha) && (function(window) {
if(window.jasmine || window.mocha) {
var currentSpec = null,
isSpecRunning = function() {
return currentSpec && (window.mocha || currentSpec.queue.running);
};
var currentSpec = null;
beforeEach(function() {
currentSpec = this;
@@ -1954,10 +1948,6 @@ angular.mock.clearDataCache = function() {
angular.callbacks.counter = 0;
});
function isSpecRunning() {
return currentSpec && (window.mocha || currentSpec.queue.running);
}
/**
* @ngdoc function
* @name angular.mock.module
@@ -2112,4 +2102,4 @@ angular.mock.clearDataCache = function() {
}
}
};
})(window);
}
+2 -2
View File
@@ -439,7 +439,7 @@ angular.module('ngResource', ['ng']).
}
/* jshint +W086 */ /* (purposefully fall through case statements) */
var isInstanceCall = data instanceof Resource;
var isInstanceCall = this instanceof Resource;
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
var httpConfig = {};
var responseInterceptor = action.interceptor && action.interceptor.response ||
@@ -522,7 +522,7 @@ angular.module('ngResource', ['ng']).
if (isFunction(params)) {
error = success; success = params; params = {};
}
var result = Resource[name](params, this, success, error);
var result = Resource[name].call(this, params, this, success, error);
return result.$promise || result;
};
});
+34 -28
View File
@@ -199,37 +199,43 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
if (template) {
var newScope = scope.$new();
$transclude(newScope, function(clone) {
clone.html(template);
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
});
cleanupLastView();
var link = $compile(clone.contents()),
current = $route.current;
currentScope = current.scope = newScope;
currentElement = clone;
if (current.controller) {
locals.$scope = currentScope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
currentScope[current.controllerAs] = controller;
}
clone.data('$ngControllerController', controller);
clone.children().data('$ngControllerController', controller);
// Note: This will also link all children of ng-view that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
// However, using ng-view on an element with additional content does not make sense...
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, angular.noop);
clone.html(template);
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
link(currentScope);
currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp);
});
cleanupLastView();
var link = $compile(clone.contents()),
current = $route.current;
currentScope = current.scope = newScope;
currentElement = clone;
if (current.controller) {
locals.$scope = currentScope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
currentScope[current.controllerAs] = controller;
}
clone.data('$ngControllerController', controller);
clone.children().data('$ngControllerController', controller);
}
link(currentScope);
currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp);
} else {
cleanupLastView();
}
+8 -1
View File
@@ -9,6 +9,9 @@
*
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* {@installModule route}
*
* <div doc-module-components="ngRoute"></div>
@@ -24,8 +27,12 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
*
* @description
*
* Used for configuring routes. See {@link ngRoute.$route $route} for an example.
* Used for configuring routes.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* ## Dependencies
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*/
function $RouteProvider(){
+28 -16
View File
@@ -1,6 +1,6 @@
'use strict';
/* global htmlSanitizeWriter: false */
/* global sanitizeText: false */
/**
* @ngdoc filter
@@ -100,7 +100,7 @@
</doc:scenario>
</doc:example>
*/
angular.module('ngSanitize').filter('linky', function() {
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
MAILTO_REGEXP = /^mailto:/;
@@ -110,28 +110,40 @@ angular.module('ngSanitize').filter('linky', function() {
var match;
var raw = text;
var html = [];
// TODO(vojta): use $sanitize instead
var writer = htmlSanitizeWriter(html);
var url;
var i;
var properties = {};
if (angular.isDefined(target)) {
properties.target = target;
}
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
writer.chars(raw.substr(0, i));
properties.href = url;
writer.start('a', properties);
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
writer.end('a');
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
writer.chars(raw);
return html.join('');
addText(raw);
return $sanitize(html.join(''));
function addText(text) {
if (!text) {
return;
}
html.push(sanitizeText(text));
}
function addLink(url, text) {
html.push('<a ');
if (angular.isDefined(target)) {
html.push('target="');
html.push(target);
html.push('" ');
}
html.push('href="');
html.push(url);
html.push('">');
addText(text);
html.push('</a>');
}
};
});
}]);
+36 -10
View File
@@ -46,6 +46,8 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
@@ -128,11 +130,24 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
</doc:scenario>
</doc:example>
*/
var $sanitize = function(html) {
function $SanitizeProvider() {
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
return function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
}));
return buf.join('');
};
}];
}
function sanitizeText(chars) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf));
return buf.join('');
};
var writer = htmlSanitizeWriter(buf, angular.noop);
writer.chars(chars);
return buf.join('');
}
// Regular Expressions for parsing tags and attributes
@@ -145,7 +160,6 @@ var START_TAG_REGEXP =
COMMENT_REGEXP = /<!--(.*?)-->/g,
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/i,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
@@ -353,8 +367,18 @@ function htmlParser( html, handler ) {
*/
var hiddenPre=document.createElement("pre");
function decodeEntities(value) {
hiddenPre.innerHTML=value.replace(/</g,"&lt;");
return hiddenPre.innerText || hiddenPre.textContent || '';
if (!value) {
return '';
}
// Note: IE8 does not preserve spaces at the start/end of innerHTML
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
var parts = spaceRe.exec(value);
parts[0] = '';
if (parts[2]) {
hiddenPre.innerHTML=parts[2].replace(/</g,"&lt;");
parts[2] = hiddenPre.innerText || hiddenPre.textContent;
}
return parts.join('');
}
/**
@@ -384,7 +408,7 @@ function encodeEntities(value) {
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf){
function htmlSanitizeWriter(buf, uriValidator){
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
@@ -398,7 +422,9 @@ function htmlSanitizeWriter(buf){
out(tag);
angular.forEach(attrs, function(value, key){
var lkey=angular.lowercase(key);
if (validAttrs[lkey]===true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
@@ -430,4 +456,4 @@ function htmlSanitizeWriter(buf){
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).value('$sanitize', $sanitize);
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document){
+4
View File
@@ -78,4 +78,8 @@ describe('module loader', function() {
window.angular.module('hasOwnProperty', []);
}).toThrowMinErr('ng','badname', "hasOwnProperty is not a valid module name");
});
it('should expose `$$minErr` on the `angular` object', function() {
expect(window.angular.$$minErr).toEqual(jasmine.any(Function));
})
});
+125 -264
View File
@@ -596,8 +596,8 @@ describe('$compile', function() {
expect(element).toHaveClass('class_2');
}));
if (!msie || msie > 10) {
// style interpolation not working on IE<11.
if (!msie || msie > 11) {
// style interpolation not working on IE (including IE11).
it('should handle interpolated css style from replacing directive', inject(
function($compile, $rootScope) {
element = $compile('<div replace-with-interpolated-style></div>')($rootScope);
@@ -3834,6 +3834,7 @@ describe('$compile', function() {
describe('img[src] sanitization', function() {
it('should NOT require trusted values for img src', inject(function($rootScope, $compile, $sce) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = 'http://example.com/image.png';
@@ -3845,127 +3846,6 @@ describe('$compile', function() {
expect(element.attr('src')).toEqual('http://example.com/image2.png');
}));
it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element.attr('src')).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should sanitize non-image data: urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');";
$rootScope.$apply();
expect(element.attr('src')).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');");
$rootScope.testUrl = "data:,foo";
$rootScope.$apply();
expect(element.attr('src')).toBe("unsafe:data:,foo");
}));
it('should not sanitize data: URIs for images', inject(function($compile, $rootScope) {
element = $compile('<img src="{{dataUri}}"></img>')($rootScope);
// image data uri
// ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
$rootScope.dataUri = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
$rootScope.$apply();
expect(element.attr('src')).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
}));
// Fails on IE <= 10 with "TypeError: Access is denied" when trying to set img[src]
if (!msie || msie > 10) {
it('should sanitize mailto: urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "mailto:foo@bar.com";
$rootScope.$apply();
expect(element.attr('src')).toBe('unsafe:mailto:foo@bar.com');
}));
}
it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
// case-sensitive
$rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
// tab in protocol
$rootScope.testUrl = "java\u0009script:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
// space before
$rootScope.testUrl = " javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
// ws chars before
$rootScope.testUrl = " \u000e javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
// post-fixed with proper url
$rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
$rootScope.$apply();
expect(element[0].src).toBeOneOf(
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
);
}));
it('should sanitize ng-src bindings as well', inject(function($compile, $rootScope) {
element = $compile('<img ng-src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should not sanitize valid urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "foo/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('foo/bar');
$rootScope.testUrl = "/foo/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('/foo/bar');
$rootScope.testUrl = "../foo/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('../foo/bar');
$rootScope.testUrl = "#foo";
$rootScope.$apply();
expect(element.attr('src')).toBe('#foo');
$rootScope.testUrl = "http://foo.com/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('http://foo.com/bar');
$rootScope.testUrl = " http://foo.com/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe(' http://foo.com/bar');
$rootScope.testUrl = "https://foo.com/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('https://foo.com/bar');
$rootScope.testUrl = "ftp://foo.com/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('ftp://foo.com/bar');
$rootScope.testUrl = "file:///foo/bar.html";
$rootScope.$apply();
expect(element.attr('src')).toBe('file:///foo/bar.html');
}));
it('should not sanitize attributes other than src', inject(function($compile, $rootScope) {
element = $compile('<img title="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -3974,141 +3854,42 @@ describe('$compile', function() {
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
}));
it('should use $$sanitizeUriProvider for reconfiguration of the src whitelist', function() {
module(function($compileProvider, $$sanitizeUriProvider) {
var newRe = /javascript:/,
returnVal;
expect($compileProvider.imgSrcSanitizationWhitelist()).toBe($$sanitizeUriProvider.imgSrcSanitizationWhitelist());
it('should allow reconfiguration of the src whitelist', function() {
module(function($compileProvider) {
expect($compileProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true);
var returnVal = $compileProvider.imgSrcSanitizationWhitelist(/javascript:/);
returnVal = $compileProvider.imgSrcSanitizationWhitelist(newRe);
expect(returnVal).toBe($compileProvider);
expect($$sanitizeUriProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
expect($compileProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
});
inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
// Fails on IE <= 11 with "TypeError: Object doesn't support this property or method" when
// trying to set img[src]
if (!msie || msie > 11) {
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element.attr('src')).toBe('javascript:doEvilStuff()');
}
$rootScope.testUrl = "http://recon/figured";
$rootScope.$apply();
expect(element.attr('src')).toBe('unsafe:http://recon/figured');
inject(function() {
// needed to the module definition above is run...
});
});
it('should use $$sanitizeUri', function() {
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
module(function($provide) {
$provide.value('$$sanitizeUri', $$sanitizeUri);
});
inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "someUrl";
$$sanitizeUri.andReturn('someSanitizedUrl');
$rootScope.$apply();
expect(element.attr('src')).toBe('someSanitizedUrl');
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true);
});
});
});
describe('a[href] sanitization', function() {
it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element.attr('href')).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should sanitize data: urls', inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "data:evilPayload";
$rootScope.$apply();
expect(element.attr('href')).toBe('unsafe:data:evilPayload');
}));
it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
// case-sensitive
$rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
// tab in protocol
$rootScope.testUrl = "java\u0009script:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
// space before
$rootScope.testUrl = " javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
// ws chars before
$rootScope.testUrl = " \u000e javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
// post-fixed with proper url
$rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
$rootScope.$apply();
expect(element[0].href).toBeOneOf(
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
);
}));
it('should sanitize ngHref bindings as well', inject(function($compile, $rootScope) {
element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should not sanitize valid urls', inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('foo/bar');
$rootScope.testUrl = "/foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('/foo/bar');
$rootScope.testUrl = "../foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('../foo/bar');
$rootScope.testUrl = "#foo";
$rootScope.$apply();
expect(element.attr('href')).toBe('#foo');
$rootScope.testUrl = "http://foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('http://foo/bar');
$rootScope.testUrl = " http://foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe(' http://foo/bar');
$rootScope.testUrl = "https://foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('https://foo/bar');
$rootScope.testUrl = "ftp://foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('ftp://foo/bar');
$rootScope.testUrl = "mailto:foo@bar.com";
$rootScope.$apply();
expect(element.attr('href')).toBe('mailto:foo@bar.com');
$rootScope.testUrl = "file:///foo/bar.html";
$rootScope.$apply();
expect(element.attr('href')).toBe('file:///foo/bar.html');
}));
it('should not sanitize href on elements other than anchor', inject(function($compile, $rootScope) {
element = $compile('<div href="{{testUrl}}"></div>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -4117,7 +3898,6 @@ describe('$compile', function() {
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
}));
it('should not sanitize attributes other than href', inject(function($compile, $rootScope) {
element = $compile('<a title="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -4126,26 +3906,38 @@ describe('$compile', function() {
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
}));
it('should use $$sanitizeUriProvider for reconfiguration of the href whitelist', function() {
module(function($compileProvider, $$sanitizeUriProvider) {
var newRe = /javascript:/,
returnVal;
expect($compileProvider.aHrefSanitizationWhitelist()).toBe($$sanitizeUriProvider.aHrefSanitizationWhitelist());
it('should allow reconfiguration of the href whitelist', function() {
module(function($compileProvider) {
expect($compileProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true);
var returnVal = $compileProvider.aHrefSanitizationWhitelist(/javascript:/);
returnVal = $compileProvider.aHrefSanitizationWhitelist(newRe);
expect(returnVal).toBe($compileProvider);
expect($$sanitizeUriProvider.aHrefSanitizationWhitelist()).toBe(newRe);
expect($compileProvider.aHrefSanitizationWhitelist()).toBe(newRe);
});
inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
$rootScope.testUrl = "http://recon/figured";
$rootScope.$apply();
expect(element.attr('href')).toBe('unsafe:http://recon/figured');
inject(function() {
// needed to the module definition above is run...
});
});
it('should use $$sanitizeUri', function() {
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
module(function($provide) {
$provide.value('$$sanitizeUri', $$sanitizeUri);
});
inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "someUrl";
$$sanitizeUri.andReturn('someSanitizedUrl');
$rootScope.$apply();
expect(element.attr('href')).toBe('someSanitizedUrl');
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
});
});
});
describe('interpolation on HTML DOM event handler attributes onclick, onXYZ, formaction', function() {
@@ -4232,6 +4024,76 @@ describe('$compile', function() {
}));
});
describe('form[action]', function() {
it('should pass through action attribute for the same domain', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = "different_page";
$rootScope.$apply();
expect(element.attr('action')).toEqual('different_page');
}));
it('should clear out action attribute for a different domain', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = "http://a.different.domain.example.com";
expect(function() { $rootScope.$apply() }).toThrowMinErr(
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
"loading resource from url not allowed by $sceDelegate policy. URL: " +
"http://a.different.domain.example.com");
}));
it('should clear out JS action attribute', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = "javascript:alert(1);";
expect(function() { $rootScope.$apply() }).toThrowMinErr(
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
"loading resource from url not allowed by $sceDelegate policy. URL: " +
"javascript:alert(1);");
}));
it('should clear out non-resource_url action attribute', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()");
expect($rootScope.$apply).toThrowMinErr(
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
"loading resource from url not allowed by $sceDelegate policy. URL: javascript:doTrustedStuff()");
}));
it('should pass through $sce.trustAs() values in action attribute', inject(function($compile, $rootScope, $sce) {
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
$rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()");
$rootScope.$apply();
expect(element.attr('action')).toEqual('javascript:doTrustedStuff()');
}));
});
if (!msie || msie >= 11) {
describe('iframe[srcdoc]', function() {
it('should NOT set iframe contents for untrusted values', inject(function($compile, $rootScope, $sce) {
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
$rootScope.html = '<div onclick="">hello</div>';
expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp(
/Can't interpolate: {{html}}\n/.source +
/[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source));
}));
it('should NOT set html for wrongly typed values', inject(function($rootScope, $compile, $sce) {
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
$rootScope.html = $sce.trustAsCss('<div onclick="">hello</div>');
expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp(
/Can't interpolate: {{html}}\n/.source +
/[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source));
}));
it('should set html for trusted values', inject(function($rootScope, $compile, $sce) {
element = $compile('<iframe srcdoc="{{html}}"></iframe>')($rootScope);
$rootScope.html = $sce.trustAsHtml('<div onclick="">hello</div>');
$rootScope.$digest();
expect(angular.lowercase(element.attr('srcdoc'))).toEqual('<div onclick="">hello</div>');
}));
});
}
describe('ngAttr* attribute binding', function() {
it('should bind after digest but not before', inject(function($compile, $rootScope) {
@@ -4479,7 +4341,6 @@ describe('$compile', function() {
$compile(element)($rootScope);
$rootScope.$digest();
data = $animate.flushNext('removeClass');
expect(element.hasClass('fire')).toBe(true);
+40
View File
@@ -454,6 +454,46 @@ describe('input', function() {
expect(scope.name).toEqual('adam');
});
it('should not update the model between "compositionstart" and "compositionend"', function() {
compileInput('<input type="text" ng-model="name" name="alias"" />');
changeInputValueTo('a');
expect(scope.name).toEqual('a');
if (!(msie < 9)) {
browserTrigger(inputElm, 'compositionstart');
changeInputValueTo('adam');
expect(scope.name).toEqual('a');
browserTrigger(inputElm, 'compositionend');
}
changeInputValueTo('adam');
expect(scope.name).toEqual('adam');
});
describe('"change" event', function() {
function assertBrowserSupportsChangeEvent(inputEventSupported) {
// Force browser to report a lack of an 'input' event
$sniffer.hasEvent = function(eventName) {
if (eventName === 'input' && !inputEventSupported) {
return false;
}
return true;
};
compileInput('<input type="text" ng-model="name" name="alias" />');
inputElm.val('mark');
browserTrigger(inputElm, 'change');
expect(scope.name).toEqual('mark');
}
it('should update the model event if the browser does not support the "input" event',function() {
assertBrowserSupportsChangeEvent(false);
});
it('should update the model event if the browser supports the "input" ' +
'event so that form auto complete works',function() {
assertBrowserSupportsChangeEvent(true);
});
});
describe('"paste" and "cut" events', function() {
beforeEach(function() {
// Force browser to report a lack of an 'input' event
+46 -1
View File
@@ -321,7 +321,6 @@ describe('ngClass animations', function() {
$rootScope.val = 'one';
$rootScope.$digest();
$animate.flushNext('addClass');
$animate.flushNext('addClass');
expect($animate.queue.length).toBe(0);
$rootScope.val = '';
@@ -374,6 +373,10 @@ describe('ngClass animations', function() {
// Enable animations by triggering the first item in the postDigest queue
digestQueue.shift()();
// wait for the 2nd animation bootstrap digest to pass
$rootScope.$digest();
digestQueue.shift()();
$rootScope.val = 'crazy';
var element = angular.element('<div ng-class="val"></div>');
jqLite($document[0].body).append($rootElement);
@@ -411,4 +414,46 @@ describe('ngClass animations', function() {
expect(enterComplete).toBe(true);
});
});
it("should not remove classes if they're going to be added back right after", function() {
module('mock.animate');
inject(function($rootScope, $compile, $animate) {
var className;
$rootScope.one = true;
$rootScope.two = true;
$rootScope.three = true;
var element = angular.element('<div ng-class="{one:one, two:two, three:three}"></div>');
$compile(element)($rootScope);
$rootScope.$digest();
//this fires twice due to the class observer firing
className = $animate.flushNext('addClass').params[1];
expect(className).toBe('one two three');
expect($animate.queue.length).toBe(0);
$rootScope.three = false;
$rootScope.$digest();
className = $animate.flushNext('removeClass').params[1];
expect(className).toBe('three');
expect($animate.queue.length).toBe(0);
$rootScope.two = false;
$rootScope.three = true;
$rootScope.$digest();
className = $animate.flushNext('removeClass').params[1];
expect(className).toBe('two');
className = $animate.flushNext('addClass').params[1];
expect(className).toBe('three');
expect($animate.queue.length).toBe(0);
});
});
});
+55
View File
@@ -85,4 +85,59 @@ describe('ngController', function() {
$rootScope.$digest();
expect(element.text()).toBe('Vojta');
}));
it('should work with ngInclude on the same element', inject(function($compile, $rootScope, $httpBackend) {
$rootScope.GreeterController = function($scope) {
$scope.name = 'Vojta';
};
element = $compile('<div><div ng-controller="GreeterController" ng-include="\'url\'"></div></div>')($rootScope);
$httpBackend.expect('GET', 'url').respond('{{name}}');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('Vojta');
}));
it('should only instantiate the controller once with ngInclude on the same element',
inject(function($compile, $rootScope, $httpBackend) {
var count = 0;
$rootScope.CountController = function($scope) {
count += 1;
};
element = $compile('<div><div ng-controller="CountController" ng-include="url"></div></div>')($rootScope);
$httpBackend.expect('GET', 'first').respond('first');
$rootScope.url = 'first';
$rootScope.$digest();
$httpBackend.flush();
$httpBackend.expect('GET', 'second').respond('second');
$rootScope.url = 'second';
$rootScope.$digest();
$httpBackend.flush();
expect(count).toBe(1);
}));
it('when ngInclude is on the same element, the content included content should get a child scope of the controller',
inject(function($compile, $rootScope, $httpBackend) {
var controllerScope;
$rootScope.ExposeScopeController = function($scope) {
controllerScope = $scope;
};
element = $compile('<div><div ng-controller="ExposeScopeController" ng-include="\'url\'"></div></div>')($rootScope);
$httpBackend.expect('GET', 'url').respond('<div ng-init="name=\'Vojta\'"></div>');
$rootScope.$digest();
$httpBackend.flush();
expect(controllerScope.name).toBeUndefined();
}));
});
+61 -4
View File
@@ -312,6 +312,31 @@ describe('ngInclude', function() {
}));
it('should exec scripts when jQuery is included', inject(function($compile, $rootScope, $httpBackend) {
if (!jQuery) {
return;
}
element = $compile('<div><span ng-include="includeUrl"></span></div>')($rootScope);
// the element needs to be appended for the script to run
element.appendTo(document.body);
window._ngIncludeCausesScriptToRun = false;
$httpBackend.expect('GET', 'url1').respond('<script>window._ngIncludeCausesScriptToRun = true;</script>');
$rootScope.includeUrl = 'url1';
$rootScope.$digest();
$httpBackend.flush();
expect(window._ngIncludeCausesScriptToRun).toBe(true);
// IE8 doesn't like deleting properties of window
window._ngIncludeCausesScriptToRun = undefined;
try {
delete window._ngIncludeCausesScriptToRun;
} catch (e) {}
}));
describe('autoscroll', function() {
var autoScrollSpy;
@@ -440,10 +465,22 @@ describe('ngInclude', function() {
});
describe('ngInclude and transcludes', function() {
var element, directive;
beforeEach(module(function($compileProvider) {
element = null;
directive = $compileProvider.directive;
}));
afterEach(function() {
if (element) {
dealoc(element);
}
});
it('should allow access to directive controller from children when used in a replace template', function() {
var controller;
module(function($compileProvider) {
var directive = $compileProvider.directive;
module(function() {
directive('template', valueFn({
template: '<div ng-include="\'include.html\'"></div>',
replace: true,
@@ -460,13 +497,33 @@ describe('ngInclude and transcludes', function() {
});
inject(function($compile, $rootScope, $httpBackend) {
$httpBackend.expectGET('include.html').respond('<div><div test></div></div>');
var element = $compile('<div><div template></div></div>')($rootScope);
element = $compile('<div><div template></div></div>')($rootScope);
$rootScope.$apply();
$httpBackend.flush();
expect(controller.flag).toBe(true);
dealoc(element);
});
});
it("should compile it's content correctly (although we remove it later)", function() {
var testElement;
module(function() {
directive('test', function() {
return {
link: function(scope, element) {
testElement = element;
}
};
});
});
inject(function($compile, $rootScope, $httpBackend) {
$httpBackend.expectGET('include.html').respond(' ');
element = $compile('<div><div ng-include="\'include.html\'"><div test></div></div></div>')($rootScope);
$rootScope.$apply();
$httpBackend.flush();
expect(testElement[0].nodeName).toBe('DIV');
});
});
});
describe('ngInclude animations', function() {
+106 -7
View File
@@ -118,6 +118,25 @@ describe('$httpBackend', function() {
});
});
it('should not try to read response data when request is aborted', function() {
callback.andCallFake(function(status, response, headers) {
expect(status).toBe(-1);
expect(response).toBe(null);
expect(headers).toBe(null);
});
$backend('GET', '/url', null, callback, {}, 2000);
xhr = MockXhr.$$lastInstance;
spyOn(xhr, 'abort');
fakeTimeout.flush();
expect(xhr.abort).toHaveBeenCalledOnce();
xhr.status = 0;
xhr.readyState = 4;
xhr.onreadystatechange();
expect(callback).toHaveBeenCalledOnce();
});
it('should abort request on timeout', function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(-1);
@@ -273,7 +292,7 @@ describe('$httpBackend', function() {
script.readyState = 'complete';
script.onreadystatechange();
} else {
script.onload()
script.onload();
}
expect(callback).toHaveBeenCalledOnce();
@@ -294,7 +313,7 @@ describe('$httpBackend', function() {
script.readyState = 'complete';
script.onreadystatechange();
} else {
script.onload()
script.onload();
}
expect(callbacks[callbackId]).toBeUndefined();
@@ -302,6 +321,38 @@ describe('$httpBackend', function() {
});
if(msie<=8) {
it('should attach onreadystatechange handler to the script object', function() {
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, noop);
expect(fakeDocument.$$scripts[0].onreadystatechange).toEqual(jasmine.any(Function));
var script = fakeDocument.$$scripts[0];
script.readyState = 'complete';
script.onreadystatechange();
expect(script.onreadystatechange).toBe(null);
});
} else {
it('should attach onload and onerror handlers to the script object', function() {
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, noop);
expect(fakeDocument.$$scripts[0].onload).toEqual(jasmine.any(Function));
expect(fakeDocument.$$scripts[0].onerror).toEqual(jasmine.any(Function));
var script = fakeDocument.$$scripts[0];
script.onload();
expect(script.onload).toBe(null);
expect(script.onerror).toBe(null);
});
}
it('should call callback with status -2 when script fails to load', function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(-2);
@@ -316,7 +367,7 @@ describe('$httpBackend', function() {
script.readyState = 'complete';
script.onreadystatechange();
} else {
script.onload()
script.onload();
}
expect(callback).toHaveBeenCalledOnce();
});
@@ -385,13 +436,61 @@ describe('$httpBackend', function() {
it('should convert 0 to 404 if no content - relative url', function() {
$backend = createHttpBackend($browser, MockXhr, null, null, null, 'file');
var originalUrlParsingNode = urlParsingNode;
$backend('GET', '/whatever/index.html', null, callback);
respond(0, '');
//temporarily overriding the DOM element to pretend that the test runs origin with file:// protocol
urlParsingNode = {
hash : "#/C:/",
host : "",
hostname : "",
href : "file:///C:/base#!/C:/foo",
pathname : "/C:/foo",
port : "",
protocol : "file:",
search : "",
setAttribute: angular.noop
};
try {
$backend = createHttpBackend($browser, MockXhr);
$backend('GET', '/whatever/index.html', null, callback);
respond(0, '');
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(404);
} finally {
urlParsingNode = originalUrlParsingNode;
}
});
it('should return original backend status code if different from 0', function () {
$backend = createHttpBackend($browser, MockXhr);
// request to http://
$backend('POST', 'http://rest_api/create_whatever', null, callback);
respond(201, '');
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(404);
expect(callback.mostRecentCall.args[0]).toBe(201);
// request to file://
$backend('POST', 'file://rest_api/create_whatever', null, callback);
respond(201, '');
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(201);
// request to file:// with HTTP status >= 300
$backend('POST', 'file://rest_api/create_whatever', null, callback);
respond(503, '');
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(503);
});
});
});
+1 -2
View File
@@ -38,7 +38,6 @@ describe('$location', function() {
if ($sniffer.msie) return;
//reset urlParsingNode
urlParsingNode = urlParsingNodePlaceholder;
expect(urlParsingNode.pathname).not.toBe('/C:/foo');
}));
@@ -324,7 +323,7 @@ describe('$location', function() {
});
it('should parse hashband url into path and search', function() {
it('should parse hashbang url into path and search', function() {
expect(url.protocol()).toBe('http');
expect(url.host()).toBe('www.server.org');
expect(url.port()).toBe(1234);
+736 -725
View File
File diff suppressed because it is too large Load Diff
+230
View File
@@ -0,0 +1,230 @@
'use strict';
describe('sanitizeUri', function() {
var sanitizeHref, sanitizeImg, sanitizeUriProvider, testUrl;
beforeEach(function() {
module(function(_$$sanitizeUriProvider_) {
sanitizeUriProvider = _$$sanitizeUriProvider_;
});
inject(function($$sanitizeUri) {
sanitizeHref = function(uri) {
return $$sanitizeUri(uri, false);
};
sanitizeImg = function(uri) {
return $$sanitizeUri(uri, true);
}
});
});
function isEvilInCurrentBrowser(uri) {
var a = document.createElement('a');
a.setAttribute('href', uri);
return a.href.substring(0, 4) !== 'http';
}
describe('img[src] sanitization', function() {
it('should sanitize javascript: urls', function() {
testUrl = "javascript:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
});
it('should sanitize non-image data: urls', function() {
testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');";
expect(sanitizeImg(testUrl)).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');");
testUrl = "data:,foo";
expect(sanitizeImg(testUrl)).toBe("unsafe:data:,foo");
});
it('should not sanitize data: URIs for images', function() {
// image data uri
// ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
testUrl = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
expect(sanitizeImg(testUrl)).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
});
it('should sanitize mailto: urls', function() {
testUrl = "mailto:foo@bar.com";
expect(sanitizeImg(testUrl)).toBe('unsafe:mailto:foo@bar.com');
});
it('should sanitize obfuscated javascript: urls', function() {
// case-sensitive
testUrl = "JaVaScRiPt:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
// tab in protocol
testUrl = "java\u0009script:doEvilStuff()";
if (isEvilInCurrentBrowser(testUrl)) {
expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
}
// space before
testUrl = " javascript:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
// ws chars before
testUrl = " \u000e javascript:doEvilStuff()";
if (isEvilInCurrentBrowser(testUrl)) {
expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
}
// post-fixed with proper url
testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
expect(sanitizeImg(testUrl)).toBeOneOf(
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
);
});
it('should sanitize ng-src bindings as well', function() {
testUrl = "javascript:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
});
it('should not sanitize valid urls', function() {
testUrl = "foo/bar";
expect(sanitizeImg(testUrl)).toBe('foo/bar');
testUrl = "/foo/bar";
expect(sanitizeImg(testUrl)).toBe('/foo/bar');
testUrl = "../foo/bar";
expect(sanitizeImg(testUrl)).toBe('../foo/bar');
testUrl = "#foo";
expect(sanitizeImg(testUrl)).toBe('#foo');
testUrl = "http://foo.com/bar";
expect(sanitizeImg(testUrl)).toBe('http://foo.com/bar');
testUrl = " http://foo.com/bar";
expect(sanitizeImg(testUrl)).toBe(' http://foo.com/bar');
testUrl = "https://foo.com/bar";
expect(sanitizeImg(testUrl)).toBe('https://foo.com/bar');
testUrl = "ftp://foo.com/bar";
expect(sanitizeImg(testUrl)).toBe('ftp://foo.com/bar');
testUrl = "file:///foo/bar.html";
expect(sanitizeImg(testUrl)).toBe('file:///foo/bar.html');
});
it('should allow reconfiguration of the src whitelist', function() {
var returnVal;
expect(sanitizeUriProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true);
returnVal = sanitizeUriProvider.imgSrcSanitizationWhitelist(/javascript:/);
expect(returnVal).toBe(sanitizeUriProvider);
testUrl = "javascript:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('javascript:doEvilStuff()');
testUrl = "http://recon/figured";
expect(sanitizeImg(testUrl)).toBe('unsafe:http://recon/figured');
});
});
describe('a[href] sanitization', function() {
it('should sanitize javascript: urls', inject(function() {
testUrl = "javascript:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should sanitize data: urls', inject(function() {
testUrl = "data:evilPayload";
expect(sanitizeHref(testUrl)).toBe('unsafe:data:evilPayload');
}));
it('should sanitize obfuscated javascript: urls', inject(function() {
// case-sensitive
testUrl = "JaVaScRiPt:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
// tab in protocol
testUrl = "java\u0009script:doEvilStuff()";
if (isEvilInCurrentBrowser(testUrl)) {
expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
}
// space before
testUrl = " javascript:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
// ws chars before
testUrl = " \u000e javascript:doEvilStuff()";
if (isEvilInCurrentBrowser(testUrl)) {
expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
}
// post-fixed with proper url
testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
expect(sanitizeHref(testUrl)).toBeOneOf(
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
);
}));
it('should sanitize ngHref bindings as well', inject(function() {
testUrl = "javascript:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should not sanitize valid urls', inject(function() {
testUrl = "foo/bar";
expect(sanitizeHref(testUrl)).toBe('foo/bar');
testUrl = "/foo/bar";
expect(sanitizeHref(testUrl)).toBe('/foo/bar');
testUrl = "../foo/bar";
expect(sanitizeHref(testUrl)).toBe('../foo/bar');
testUrl = "#foo";
expect(sanitizeHref(testUrl)).toBe('#foo');
testUrl = "http://foo/bar";
expect(sanitizeHref(testUrl)).toBe('http://foo/bar');
testUrl = " http://foo/bar";
expect(sanitizeHref(testUrl)).toBe(' http://foo/bar');
testUrl = "https://foo/bar";
expect(sanitizeHref(testUrl)).toBe('https://foo/bar');
testUrl = "ftp://foo/bar";
expect(sanitizeHref(testUrl)).toBe('ftp://foo/bar');
testUrl = "mailto:foo@bar.com";
expect(sanitizeHref(testUrl)).toBe('mailto:foo@bar.com');
testUrl = "file:///foo/bar.html";
expect(sanitizeHref(testUrl)).toBe('file:///foo/bar.html');
}));
it('should allow reconfiguration of the href whitelist', function() {
var returnVal;
expect(sanitizeUriProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true);
returnVal = sanitizeUriProvider.aHrefSanitizationWhitelist(/javascript:/);
expect(returnVal).toBe(sanitizeUriProvider);
testUrl = "javascript:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('javascript:doEvilStuff()');
testUrl = "http://recon/figured";
expect(sanitizeHref(testUrl)).toBe('unsafe:http://recon/figured');
});
});
});
+13 -20
View File
@@ -29,10 +29,10 @@ describe('SCE', function() {
describe('IE8 quirks mode', function() {
function runTest(enabled, documentMode, expectException) {
module(function($provide) {
$provide.value('$document', [{
documentMode: documentMode,
createElement: function() {}
}]);
$provide.value('$sniffer', {
msie: documentMode,
msieDocumentMode: documentMode
});
$provide.value('$sceDelegate', {trustAs: null, valueOf: null, getTrusted: null});
});
@@ -43,22 +43,15 @@ describe('SCE', function() {
return $injector.invoke(sceProvider.$get, sceProvider);
}
var origMsie = $window.msie;
try {
$window.msie = true;
if (expectException) {
expect(constructSce).toThrowMinErr(
'$sce', 'iequirks', 'Strict Contextual Escaping does not support Internet Explorer ' +
'version < 9 in quirks mode. You can fix this by adding the text <!doctype html> to ' +
'the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more ' +
'information.');
} else {
// no exception.
constructSce();
}
}
finally {
$window.msie = origMsie;
if (expectException) {
expect(constructSce).toThrowMinErr(
'$sce', 'iequirks', 'Strict Contextual Escaping does not support Internet Explorer ' +
'version < 9 in quirks mode. You can fix this by adding the text <!doctype html> to ' +
'the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more ' +
'information.');
} else {
// no exception.
constructSce();
}
});
}
+10 -2
View File
@@ -334,7 +334,15 @@ describe('$sniffer', function() {
});
});
it('should return true for msie when internet explorer is being used', inject(function($sniffer) {
expect($sniffer.msie > 0).toBe(window.navigator.appName == 'Microsoft Internet Explorer');
it('should return the internal msie flag', inject(function($sniffer) {
expect(isNaN($sniffer.msie)).toBe(isNaN(msie));
if (msie) {
expect($sniffer.msie).toBe(msie);
}
}));
it('should return document.documentMode as msieDocumentMode', function() {
var someDocumentMode = 123;
expect(sniffer({}, {documentMode: someDocumentMode}).msieDocumentMode).toBe(someDocumentMode);
});
});
+8 -1
View File
@@ -1,7 +1,7 @@
'use strict';
describe('urlUtils', function() {
describe('parse', function() {
describe('urlResolve', function() {
it('should normalize a relative url', function () {
expect(urlResolve("foo").href).toMatch(/^https?:\/\/[^/]+\/foo$/);
});
@@ -14,6 +14,13 @@ describe('urlUtils', function() {
expect(parsed.hostname).not.toBe("");
expect(parsed.pathname).not.toBe("");
});
it('should return pathname as / if empty path provided', function () {
//IE counts / as empty, necessary to use / so that pathname is not context.html
var parsed = urlResolve('/');
expect(parsed.pathname).toBe('/');
})
});
describe('isSameOrigin', function() {
+2582 -2328
View File
File diff suppressed because it is too large Load Diff
+51 -5
View File
@@ -1,11 +1,8 @@
'use strict';
var msie = +((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
describe('ngMock', function() {
var noop = angular.noop;
describe('TzDate', function() {
function minutes(min) {
@@ -686,10 +683,10 @@ describe('ngMock', function() {
expect(d($rootScope)).toMatch(/{"abc":"123"}/);
}));
it('should serialize scope that has overridden "hasOwnProperty"', inject(function($rootScope){
it('should serialize scope that has overridden "hasOwnProperty"', inject(function($rootScope, $sniffer){
// MS IE8 just doesn't work for this kind of thing, since "for ... in" doesn't return
// things like hasOwnProperty even if it is explicitly defined on the actual object!
if (msie<=8) return;
if ($sniffer.msie<=8) return;
$rootScope.hasOwnProperty = 'X';
expect(d($rootScope)).toMatch(/Scope\(.*\): \{/);
expect(d($rootScope)).toMatch(/hasOwnProperty: "X"/);
@@ -944,6 +941,29 @@ describe('ngMock', function() {
});
it('should match data object if specified', function() {
hb.when('GET', '/a/b', {a: 1, b: 2}).respond(201, 'content1');
hb.when('GET', '/a/b').respond(202, 'content2');
hb('GET', '/a/b', '{"a":1,"b":2}', function(status, response) {
expect(status).toBe(201);
expect(response).toBe('content1');
});
hb('GET', '/a/b', '{"b":2,"a":1}', function(status, response) {
expect(status).toBe(201);
expect(response).toBe('content1');
});
hb('GET', '/a/b', null, function(status, response) {
expect(status).toBe(202);
expect(response).toBe('content2');
});
hb.flush();
});
it('should match only method', function() {
hb.when('GET').respond(202, 'c');
callback.andCallFake(function(status, response) {
@@ -1075,6 +1095,32 @@ describe('ngMock', function() {
});
it ('should not throw an exception when parsed body is equal to expected body object', function() {
hb.when('GET').respond(200, '', {});
hb.expect('GET', '/match', {a: 1, b: 2});
expect(function() {
hb('GET', '/match', '{"a":1,"b":2}', noop, {});
}).not.toThrow();
hb.expect('GET', '/match', {a: 1, b: 2});
expect(function() {
hb('GET', '/match', '{"b":2,"a":1}', noop, {});
}).not.toThrow();
});
it ('should throw exception when only parsed body differs from expected body object', function() {
hb.when('GET').respond(200, '', {});
hb.expect('GET', '/match', {a: 1, b: 2});
expect(function() {
hb('GET', '/match', '{"a":1,"b":3}', noop, {});
}).toThrow('Expected GET /match with different data\n' +
'EXPECTED: {"a":1,"b":2}\nGOT: {"a":1,"b":3}');
});
it("should use when's respond() when no expect() respond is defined", function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(201);
+12
View File
@@ -533,6 +533,18 @@ describe("resource", function() {
expect(person.name).toEqual('misko');
});
it('should return a resource instance when calling a class method with a resource instance', function() {
$httpBackend.expect('GET', '/Person/123').respond('{"name":"misko"}');
var Person = $resource('/Person/:id');
var person = Person.get({id:123});
$httpBackend.flush();
$httpBackend.expect('POST', '/Person').respond('{"name":"misko2"}');
var person2 = Person.save(person);
$httpBackend.flush();
expect(person2).toEqual(jasmine.any(Person));
});
describe('promise api', function() {
+37 -7
View File
@@ -515,12 +515,23 @@ describe('ngView', function() {
});
describe('ngView and transcludes', function() {
var element, directive;
beforeEach(module('ngRoute', function($compileProvider) {
element = null;
directive = $compileProvider.directive;
}));
afterEach(function() {
if (element) {
dealoc(element);
}
});
it('should allow access to directive controller from children when used in a replace template', function() {
var controller;
module('ngRoute');
module(function($compileProvider, $routeProvider) {
module(function($routeProvider) {
$routeProvider.when('/view', {templateUrl: 'view.html'});
var directive = $compileProvider.directive;
directive('template', function() {
return {
template: '<div ng-view></div>',
@@ -542,14 +553,35 @@ describe('ngView and transcludes', function() {
});
inject(function($compile, $rootScope, $httpBackend, $location) {
$httpBackend.expectGET('view.html').respond('<div><div test></div></div>');
var element = $compile('<div><div template></div></div>')($rootScope);
element = $compile('<div><div template></div></div>')($rootScope);
$location.url('/view');
$rootScope.$apply();
$httpBackend.flush();
expect(controller.flag).toBe(true);
dealoc(element);
});
});
it("should compile it's content correctly (although we remove it later)", function() {
var testElement;
module(function($compileProvider, $routeProvider) {
$routeProvider.when('/view', {template: ' '});
var directive = $compileProvider.directive;
directive('test', function() {
return {
link: function(scope, element) {
testElement = element;
}
};
});
});
inject(function($compile, $rootScope, $location) {
element = $compile('<div><div ng-view><div test someAttr></div></div></div>')($rootScope);
$location.url('/view');
$rootScope.$apply();
expect(testElement[0].nodeName).toBe('DIV');
});
});
});
describe('ngView animations', function() {
@@ -656,7 +688,6 @@ describe('ngView animations', function() {
item = $animate.flushNext('enter').element;
$animate.flushNext('addClass').element;
$animate.flushNext('addClass').element;
expect(item.hasClass('classy')).toBe(true);
@@ -676,7 +707,6 @@ describe('ngView animations', function() {
$animate.flushNext('enter').element;
item = $animate.flushNext('leave').element;
$animate.flushNext('addClass').element;
$animate.flushNext('addClass').element;
expect(item.hasClass('boring')).toBe(true);
+122 -41
View File
@@ -5,12 +5,15 @@ describe('HTML', function() {
var expectHTML;
beforeEach(module('ngSanitize'));
beforeEach(inject(function($sanitize) {
beforeEach(function() {
expectHTML = function(html){
return expect($sanitize(html));
var sanitize;
inject(function($sanitize) {
sanitize = $sanitize;
});
return expect(sanitize(html));
};
}));
});
describe('htmlParser', function() {
if (angular.isUndefined(window.htmlParser)) return;
@@ -183,13 +186,22 @@ describe('HTML', function() {
toEqual('');
});
it('should keep spaces as prefix/postfix', function() {
expectHTML(' a ').toEqual(' a ');
});
it('should allow multiline strings', function() {
expectHTML('\na\n').toEqual('&#10;a\&#10;');
});
describe('htmlSanitizerWriter', function() {
if (angular.isUndefined(window.htmlSanitizeWriter)) return;
var writer, html;
var writer, html, uriValidator;
beforeEach(function() {
html = '';
writer = htmlSanitizeWriter({push:function(text){html+=text;}});
uriValidator = jasmine.createSpy('uriValidator');
writer = htmlSanitizeWriter({push:function(text){html+=text;}}, uriValidator);
});
it('should write basic HTML', function() {
@@ -258,41 +270,106 @@ describe('HTML', function() {
});
});
describe('isUri', function() {
function isUri(value) {
return value.match(URI_REGEXP);
}
it('should be URI', function() {
expect(isUri('http://abc')).toBeTruthy();
expect(isUri('HTTP://abc')).toBeTruthy();
expect(isUri('https://abc')).toBeTruthy();
expect(isUri('HTTPS://abc')).toBeTruthy();
expect(isUri('ftp://abc')).toBeTruthy();
expect(isUri('FTP://abc')).toBeTruthy();
expect(isUri('mailto:me@example.com')).toBeTruthy();
expect(isUri('MAILTO:me@example.com')).toBeTruthy();
expect(isUri('tel:123-123-1234')).toBeTruthy();
expect(isUri('TEL:123-123-1234')).toBeTruthy();
expect(isUri('#anchor')).toBeTruthy();
describe('uri validation', function() {
it('should call the uri validator', function() {
writer.start('a', {href:'someUrl'}, false);
expect(uriValidator).toHaveBeenCalledWith('someUrl', false);
uriValidator.reset();
writer.start('img', {src:'someImgUrl'}, false);
expect(uriValidator).toHaveBeenCalledWith('someImgUrl', true);
uriValidator.reset();
writer.start('someTag', {src:'someNonUrl'}, false);
expect(uriValidator).not.toHaveBeenCalled();
});
it('should not be URI', function() {
expect(isUri('')).toBeFalsy();
expect(isUri('javascript:alert')).toBeFalsy();
it('should drop non valid uri attributes', function() {
uriValidator.andReturn(false);
writer.start('a', {href:'someUrl'}, false);
expect(html).toEqual('<a>');
html = '';
uriValidator.andReturn(true);
writer.start('a', {href:'someUrl'}, false);
expect(html).toEqual('<a href="someUrl">');
});
});
});
describe('uri checking', function() {
beforeEach(function() {
this.addMatchers({
toBeValidUrl: function() {
var sanitize;
inject(function($sanitize) {
sanitize = $sanitize;
});
var input = '<a href="'+this.actual+'"></a>';
return sanitize(input) === input;
},
toBeValidImageSrc: function() {
var sanitize;
inject(function($sanitize) {
sanitize = $sanitize;
});
var input = '<img src="'+this.actual+'"/>';
return sanitize(input) === input;
}
});
});
describe('javascript URL attribute', function() {
beforeEach(function() {
this.addMatchers({
toBeValidUrl: function() {
return URI_REGEXP.exec(this.actual);
}
});
it('should use $$sanitizeUri for links', function() {
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
module(function($provide) {
$provide.value('$$sanitizeUri', $$sanitizeUri);
});
inject(function() {
$$sanitizeUri.andReturn('someUri');
expectHTML('<a href="someUri"></a>').toEqual('<a href="someUri"></a>');
expect($$sanitizeUri).toHaveBeenCalledWith('someUri', false);
$$sanitizeUri.andReturn('unsafe:someUri');
expectHTML('<a href="someUri"></a>').toEqual('<a></a>');
});
});
it('should use $$sanitizeUri for links', function() {
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
module(function($provide) {
$provide.value('$$sanitizeUri', $$sanitizeUri);
});
inject(function() {
$$sanitizeUri.andReturn('someUri');
expectHTML('<img src="someUri"/>').toEqual('<img src="someUri"/>');
expect($$sanitizeUri).toHaveBeenCalledWith('someUri', true);
$$sanitizeUri.andReturn('unsafe:someUri');
expectHTML('<img src="someUri"/>').toEqual('<img/>');
});
});
it('should be URI', function() {
expect('').toBeValidUrl();
expect('http://abc').toBeValidUrl();
expect('HTTP://abc').toBeValidUrl();
expect('https://abc').toBeValidUrl();
expect('HTTPS://abc').toBeValidUrl();
expect('ftp://abc').toBeValidUrl();
expect('FTP://abc').toBeValidUrl();
expect('mailto:me@example.com').toBeValidUrl();
expect('MAILTO:me@example.com').toBeValidUrl();
expect('tel:123-123-1234').toBeValidUrl();
expect('TEL:123-123-1234').toBeValidUrl();
expect('#anchor').toBeValidUrl();
expect('/page1.md').toBeValidUrl();
});
it('should not be URI', function() {
expect('javascript:alert').not.toBeValidUrl();
});
describe('javascript URLs', function() {
it('should ignore javascript:', function() {
expect('JavaScript:abc').not.toBeValidUrl();
expect(' \n Java\n Script:abc').not.toBeValidUrl();
@@ -318,15 +395,19 @@ describe('HTML', function() {
});
it('should ignore hex encoded whitespace javascript:', function() {
expect('jav&#x09;ascript:alert("A");').not.toBeValidUrl();
expect('jav&#x0A;ascript:alert("B");').not.toBeValidUrl();
expect('jav&#x0A ascript:alert("C");').not.toBeValidUrl();
expect('jav\u0000ascript:alert("D");').not.toBeValidUrl();
expect('java\u0000\u0000script:alert("D");').not.toBeValidUrl();
expect(' &#14; java\u0000\u0000script:alert("D");').not.toBeValidUrl();
expect('jav&#x09;ascript:alert();').not.toBeValidUrl();
expect('jav&#x0A;ascript:alert();').not.toBeValidUrl();
expect('jav&#x0A ascript:alert();').not.toBeValidUrl();
expect('jav\u0000ascript:alert();').not.toBeValidUrl();
expect('java\u0000\u0000script:alert();').not.toBeValidUrl();
expect(' &#14; java\u0000\u0000script:alert();').not.toBeValidUrl();
});
});
});
describe('sanitizeText', function() {
it('should escape text', function() {
expect(sanitizeText('a<div>&</div>c')).toEqual('a&lt;div&gt;&amp;&lt;/div&gt;c');
});
});
});
+2 -2
View File
@@ -5,5 +5,5 @@ set -e
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
grunt parallel:travis --reporters dots \
--browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10 \
--e2e-browsers SL_Chrome
--browsers BS_Chrome,BS_Safari,BS_Firefox,BS_IE_8,BS_IE_9,BS_IE_10,BS_IE_11 \
--e2e-browsers BS_Chrome
View File