Compare commits

..

368 Commits

Author SHA1 Message Date
Igor Minar de6d4ca1da chore(release): cut 1.1.3 radioactive-gargle release 2013-02-20 12:54:44 -08:00
Igor Minar 776dfc678b docs(changelog): add release notes for 1.0.5 and 1.1.3 2013-02-20 11:16:19 -08:00
Igor Minar 9532234bf1 fix($compile): sanitize values bound to a[href] 2013-02-20 00:06:26 -08:00
Per Rovegård 5f5d4feadb fix(ngClass): keep track of old ngClass value manually
ngClassWatchAction, when called as a $watch function, gets the wrong old
value after it has been invoked previously due to observation of the
interpolated class attribute. As a result it doesn't remove classes
properly. Keeping track of the old value manually seems to fix this.

Closes #1637
2013-02-18 20:25:43 -08:00
Pete Bacon Darwin 791804bdbf fix(compile): should not leak memory when there are top level empty text nodes
The change to prevent <span> elements being wrapped around empty text nodes caused these empty text nodes to have scopes and controllers attached, through jqLite.data() calls, which led to memory leaks and errors in IE8.
Now we exclude all but document nodes and elements from having jqLite.data() set both in the compiler and in ng-view.

Fixes: #1968 and #1876
2013-02-18 12:05:16 +00:00
Luis Ramón López 7eafbb98c6 feat(routeProvider): Add support to catch-all parameters in routes
This allows routeProvider to accept parameters that matches
substrings even when they contain slashes if they are prefixed
with an asterisk instead of a colon.
For example, routes like edit/color/:color/largecode/*largecode
will match with something like this
http://appdomain.com/edit/color/brown/largecode/code/with/slashs.
2013-02-14 21:36:59 -08:00
Pete Bacon Darwin bb8448c011 fix(compile): Initialize interpolated attributes before directive linking 2013-02-14 21:36:59 -08:00
Pete Bacon Darwin 2ed53087d7 fix(compile): Interpolate @ locals before the link function runs
Do a one-off interpolation of @ locals to ensure that the link fn receives attributes that are already interpolated.
2013-02-14 21:36:59 -08:00
Lucas Galfasó 0af172040e feat(ngSwitch): support multiple matches on ngSwitchWhen and ngSwitchDefault
Closes #1074
2013-02-14 19:55:05 -08:00
Will Moore e19b04c9ec fix($httpBackend): patch for Firefox bug w/ CORS and response headers
A workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=608735
In FF getAllResponseHeaders() returns null if the request is the result of CORS.

Tried to format the code so that when a FF patch is released and gains enough
traction it can easily be selected and deleted. Heavily inspired by jQuery's
patch for the same bug. This patch falls short of passing through custom headers
but covers all of the "simple response headers" in the spec at
http://www.w3.org/TR/cors/

This commit should get reverted once Firefox 21 gets out.

Closes #1468
2013-02-14 16:45:30 -08:00
Igor Minar 37e8b12265 fix(a): workaround IE bug affecting mailto urls
Apparently there is a really weird bug in IE6-8 that causes anchor textContent
to be reset with href content when both contain @ symbol.

Inserting a bogus comment node into all anchor elements in IE works around this
browser bug.

I'm fixing the issue via directive because that way we'll fix it for jQuery as
well.

I fixed an e2e test too because it was incorrect.

Closes #1949
2013-02-14 16:42:58 -08:00
Igor Minar 1ace5eb396 style(filter): remove ws 2013-02-14 16:38:43 -08:00
Igor Minar 3c2aee01b0 fix(*): don't use instanceof to detect arrays
this breaks when multiple javascript contexts are involved - like in node-webkit

see original PR: #1966

Closes #1966
2013-02-14 16:37:00 -08:00
Cedric Soulas 37bdcc984a docs($resource): fix bad indentation producing a code block 2013-02-14 15:47:16 -08:00
Ewen Cumming 027f20be1f docs($rootScope): rearrange event listener docs 2013-02-14 15:47:16 -08:00
deboer 9b7c1d0f7c fix(ngSwitch): make ngSwitch compatible with controller BC module
add a $scope to the ngSwitch's controller to fool the controller
BC (backwards compatibility) module used by DFA.
2013-02-14 15:36:03 -08:00
Vineet Kumar 5548328b67 docs($q): fix a few typos 2013-02-14 15:18:58 -08:00
Dylan Pyle 7c6b1e06e8 docs(guide): fix some invalid javascript in directive documentation
Use double quotes to maintain consistency with other HTML
2013-02-14 15:11:04 -08:00
Vojta Jina 288b69a314 fix($http): do not encode special characters @$:, in params
encodeURIComponent is too aggressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
with regards to the character set (pchar) allowed in path segments so we need
this test to make sure that we don't over-encode the params and break stuff
like buzz api which uses @self.

This is has already been fixed in `$resource`. This commit fixes it in a same way
for `$http` as well.

BREAKING CHANGE: $http does follow RFC3986 and does not encode special characters
like `$@,:` in params. If your application needs to encode these characters, encode
them manually, before sending the request.
2013-02-14 14:52:46 -08:00
Mark Nadig 2a2123441c fix($resource): params should expand array values properly
Today, calling e.g. var R = $resource('/Path/:a'); R.get({a: 'foo', bar: ['baz1', 'baz2']}); results in a query
string like "/Path/doh?bar=baz1,baz2" which is undesirable. This commit enhances resource to use
$http to encode any non-url parameters resulting in a query string like "/Path/doh?bar=baz1&bar=baz2".

BREAKING CHANGE: if the server relied on the buggy behavior then either the
backend should be fixed or a simple serialization of the array should be done
on the client before calling the resource service.
2013-02-14 14:52:46 -08:00
Daniel Luz 1d7a95df56 feat(scope): only evaluate constant $watch expressions once 2013-02-14 14:43:56 -08:00
Daniel Luz 1ed638582d feat($parse): added constant and literal properties
* `literal` is set to true if the expression's top-level is a JavaScript
  literal (number, string, boolean, null/undefined, array, object), even
  if it contains non-literals inside.

* `constant` is set to true if the expression is known to be made
  entirely of constant values, i.e., evaluating it will always yield the
  same result.

A consequence is that a JSON expression is guaranteed to be both literal
and constant.
2013-02-14 14:43:56 -08:00
Daniel Luz 3b14092135 docs($parse): document function argument types, fix minor typo 2013-02-14 14:43:56 -08:00
Daniel Luz ef268196b9 fix($rootScope): minor typo fixes 2013-02-14 14:43:56 -08:00
James Morrin 12ba6cec4f feat(noConflict): restore previous angular namespace reference 2013-02-14 14:43:55 -08:00
Fredrik Bonander b7e1fb0515 fix(resource): Update RegExp to allow urlParams with out leading slash
Will allow reoucese to be loaded from a relative path
Example:
var R = $resource(':path');
R.get({ path : 'data.json' });

Example usage:
Load resources in applications not using webserver, ie local webapp in 
on a tablet.
2013-02-14 14:43:55 -08:00
Shyam Seshadri 755beb2b66 fix(compiler): Allow startingTag method to handle text / comment nodes 2013-02-14 14:43:55 -08:00
Trotter Cashion 6d70ff5c8d chore(reakefile): auto install npm packages 2013-02-14 14:43:55 -08:00
Rosina Bignall ace54ff08c feat(filter): Add comparison function to filter
Add optional comparator function argument to $filter('filter')(array,
expression, comparator) such that the comparator function is used to
compare the values and predicates.  When true, defaults to equality.
When missing defaults to substring matching.
2013-02-14 14:43:55 -08:00
Kury Kruitbosch f5835963d5 fix(numberFilter): fix formatting when "0" passed as fractionSize
When checking to add decimal and trialing 0s number filter used to check
trueness of fractionSize. "0" evaluating to true causes "123" to return "123."
2013-02-14 13:15:50 -08:00
Jesse Cooke f3231b9447 docs(guide): Fix typos in concepts/model,view. 2013-02-14 13:05:25 -08:00
Igor Minar a8a3efb5f0 chore(Rakefile): parallelize the build on Travis
now that the forking issue is solved we can run regular build there

https://github.com/travis-ci/travis-ci/issues/845
2013-02-14 12:46:19 -08:00
Cedric Soulas e47f8d2b96 docs($resource): fix missing punctuation 2013-02-14 11:57:33 -08:00
Igor Minar dba6bc73e8 feat($resource): expose promise based api via $then and $resolved
Expose $then and $resolved properties on resource action return values which
allow checking if a promise has been resolved already as well as registering
listeners at any time of the resource object life-cycle.

This commit replaces unreleased commit f3bff27460
which exposed unintuitive $q api instead and didn't expose important stuff
like http headers.
2013-02-11 22:24:21 -08:00
Igor Minar c0a0781425 chore(matchers): fix hasBeenCalledOnceWith matcher
the error message was wrong and misleading
2013-02-11 22:13:15 -08:00
Igor Minar 035e0130f3 test(angular.copy): add tests for scenarios when target is missing 2013-02-11 22:11:07 -08:00
Igor Minar 9e57ce0c7a revert: refactor(angular.copy): use slice(0) to clone arrays
This reverts commit 28273b7f1ef52e46d5bc23c41bc7a1492cf23014o.

slice(0) doesn't perform deep copy of the array so its unsuitable
for our purposes.
2013-02-11 21:58:00 -08:00
Igor Minar 42a5033c56 chore(docs): improve docs parser type
previously we barfed on function type definition with optional arguments
like {function(number=)}

this fixes it

I also added a bunch of code that helps to debug incorrectly parsed docs.
2013-02-11 14:08:16 -08:00
Igor Minar 6b19e7d527 refactor(angular.copy): use array.length=0 to empty arrays 2013-02-11 14:07:49 -08:00
Igor Minar 28273b7f1e refactor(angular.copy): use slice(0) to clone arrays
slice(0) is way faster on most browsers
2013-02-11 14:07:03 -08:00
Igor Minar ec54712ff3 fix(angular.forEach): correctly iterate over objects with length prop
Should handle JQLite, jQuery, NodeList and other objects like arrays
but not other generic objects or instances of user defined types
with length property.

Closes #1840
2013-02-11 12:29:55 -08:00
radu 23dd78f8a4 docs($q): fix typos 2013-02-07 04:16:16 -08:00
Julie d46fe3c23f fix(scenario): include error messages in XML output
Fix the XML output of scenario tests so that it properly includes error
messages from failing specs.
2013-02-07 04:09:52 -08:00
Enrique Paredes 92ca7efaa4 fix($compile): rename $compileNote to compileNode
Directives was observing different instances of Attributes than the one
that interpolation was registered with because we failed to realize
that the compile node and link node were the same (one of them
was a wrapper rather than raw node)

Closes #1941
2013-02-07 02:49:12 -08:00
Fredrik Bonander 7090924515 fix($cookies): set cookies on Safari&IE when base[href] is undefined
Safari and IE don't like being told to store cookies with path set to
undefined. This change ensures that if base[href] (from which cookie path
is derived) is undefined then the cookie path defaults to ''.

The test verifies that the cookie is set instead of checking that cookie has correct path,
this is due to that cookie meta information is not avabile once the cookie is set.

Closes #1190, #1191
2013-02-07 02:36:25 -08:00
Maxim Grach df744f3af4 feat(dateFilter): add [.,]sss formatter for milliseconds
Also Implement getMilliseconds() method of TzDate and
add test for this in ngMock.
2013-02-07 02:28:33 -08:00
Sam McCall 8155c3a29e feat($http): allow overriding the XSRF header and cookie name
Add 'xsrfCookieName' and 'xsrfHeaderName' property to $httpProvider.defaults and
http config object, which give the name of the cookie the XSRF token is found
in, and the name of the header it is sent in, respectively.
This allows interop with servers with built-in XSRF support that use different
names.
The defaults match the current hard-coded values of 'XSRF-TOKEN' and
'X-XSRF-TOKEN'.
2013-02-07 01:48:01 -08:00
Philip Roberts b001c8ece5 fix(date): invert timezone sign and always display sign
This commit fixes #1261 and #1532. This covers
two separate issues:

- Positive timezones were being formatted without
a leading `+` resulting in a formatting string
like: "HH:MM:ssZ" giving "12:13:141000" instead
of "12:13:14+1000". Fixed by checking if timezone
is > 0 and adding a leading "+".

- Timezone output signs were inverted.
mock.TzDate expects the timezone _offset_ as it's
first argument, _not_ the timezone. This means
that a mock.TzDate with a positive offset should
result in a date string with a negative timezone,
and vice-versa.

Closes #1261, #1532
2013-02-07 01:32:04 -08:00
Igor Minar bec4435945 docs(guide): remove stale info about filters changing DOM
as of v0.10.6 this is not the case any more
2013-02-06 14:14:49 -08:00
Thomas Schultz 6fb1054ce6 docs(tutorial): remove extra back-tick character 2013-02-06 21:51:42 +01:00
theotheo a83eced974 docs(module): fix code example 2013-02-06 20:40:06 +01:00
radu 7cc4063303 docs(ngApp): fix typo 2013-02-05 21:29:11 +01:00
Igor Minar d8e242418d docs(contributing): add CLA anchor for deeplinking 2013-02-04 09:38:01 -08:00
PowerKiKi 6518369f25 docs(ngClass): fix typo in description 2013-02-04 10:36:29 +00:00
Brian Ford 649b892205 feat(Scope): expose transcluded and isolate scope info for batarang
test($compile): add test for exposing transclude and isolate scope info to batarang
2013-01-30 10:42:56 -05:00
Dean Sofer e0295cfec4 docs($cookies): added example to $cookies api docs
Better than nothing.
2013-01-29 16:16:17 -08:00
radu 17fc6a70fe docs(nextUid): fix typo
Update src/Angular.js

removed redundant 'the' from nextUid()'s ngdoc
2013-01-29 15:49:10 -08:00
radu 5d0f9ce4c7 docs(tutorial): fix typo
Update docs/content/tutorial/step_00.ngdoc

removed redundant verb
2013-01-29 15:46:32 -08:00
Fred Sauer 250aec71f3 docs(Scope): fix argument docs for $on 2013-01-29 15:38:19 -08:00
metaweta 3b317c5dcb test(ngBindHtml): prevent variable name leak
Add "var" so element is local instead of global

Strict mode doesn't allow undeclared global vars, and these really should be local anyway.
2013-01-29 13:26:06 -08:00
metaweta e4cfb9d938 refactor(Angular.js): prevent Error variable name leak in tests
Remove var Error = window.Error

window.Error is a read-only property in Apps Script.

Igor says, "we should just delete that line instead. I think it was
misko's attempt to get better closure minification, but it turns out
that it's actually hurting us after gzip (I verified it)."
2013-01-29 13:24:33 -08:00
Igor Minar 87ba8221ec chore(release): start 1.1.3 radioactive-gargle iteration 2013-01-29 13:20:43 -08:00
Fred Sauer e34519e93b docs(a): escape sample code in ng a directive 2013-01-26 22:52:16 +01:00
Igor Minar 69be39fccf fix(docs): properly generate angular.js urls in doc examples 2013-01-24 11:36:03 -08:00
Igor Minar deac80a6e8 docs(date): add missing doc about TZ behavior 2013-01-24 10:51:03 -08:00
Igor Minar 0539611bac docs(changelog): correct 1.0.4 release notes 2013-01-24 09:50:27 -08:00
Vineet Kumar d2177ae312 docs($injector): clarify $inject property description
Section heading about `$inject` property refers to it as `$injector` property.
2013-01-24 00:15:32 -05:00
Partap Davis f3bff27460 feat(resource): add $q/$resorved property to Resource 2013-01-23 20:57:26 -08:00
Igor Minar 4df45b20d4 chore(release): update the CDN version 2013-01-23 12:06:40 -08:00
Igor Minar 5745734991 chore(release): cut 1.1.2 tofu-animation release 2013-01-23 10:54:35 -08:00
Igor Minar 0551aa95f0 docs(changelog): release notes for 1.1.2 and 1.0.4 2013-01-22 22:58:55 -08:00
Igor Minar b62327ec2d docs(ngOpen): fix typo in api docs 2013-01-22 22:58:55 -08:00
Igor Minar 07a58dd766 chore(changelog.js): improve the changelog script 2013-01-22 22:49:00 -08:00
Igor Minar ffe5e01584 revert: fix($resource): Route constructor, updated RegExp
This reverts commit 06ed8ef012.

The reverted commit causes regressions. See comments in the PR:
https://github.com/angular/angular.js/pull/1402#issuecomment-12575399
2013-01-22 16:32:02 -08:00
Igor Minar 3c2e1c5e4d fix(angular.equals): relax the comparison for undefined properties
in 5ae63fd3 the comparison was made consistent but strict, so that

angular.equals({}, {foo: undefined}) // always returns false

this turns out to cause issues for data that is being roundtripped via network
and serialized via JSON because JSON.stringify serializes {foo: undefined} as {}.

Since angular.equals() behaved like this before the 5ae63fd3 in 50% of the cases,
changing the behavior in this way should not introduce any significant issues.

Closes #1648
2013-01-22 07:35:21 -08:00
Igor Minar cdf7781878 chore(Rakefile): skip build parallelization on Travis
Due to a infrastructure change on Travis starting JVMs in forked
processes doesn't currently work. Since we don't really care that
much about the build speed on Travis, I'm going to disable it there.

Related issue: https://github.com/travis-ci/travis-ci/issues/845
2013-01-21 07:50:16 -08:00
sergiopantoja 7e8d3c1736 docs(jqLite): fix typo 2013-01-20 18:39:21 +01:00
Will Moore d30845a757 docs(contribute): adding npm install to step-by-step
npm install is listed in the dependencies section of the contribute guide but
is missing from the step-by-step. This adds it as step 4.
2013-01-18 21:33:10 -08:00
Fredrik Bonander 06ed8ef012 fix($resource): Route constructor, updated RegExp
Update RegExp to allow urlParams with out leading slash (/).
- Will allow reoucese to be loaded from a relative path

Example:
var R = $resource(':path');
R.get({ path : 'data.json' });

Example usage:
Load resources in applications not using webserver, ie local webapp in on a tablet.
2013-01-18 21:28:15 -08:00
Pedro Del Gallego 2f437e8978 feat(scenario): add mouseover method to the ngScenario dsl 2013-01-18 21:24:57 -08:00
Luis Ramón López faf02f0c4d feat(route): Allow using functions as template params in 'when' 2013-01-18 21:20:49 -08:00
David Chang b8bd4d5460 feat(directive): added ng-open boolean directive
Closes# 1797 add ng-open attribute
2013-01-18 21:16:16 -08:00
pavelgj b2f46251ac fix(ngResource): correct leading slash removal.
Fixed an issues with ngResource param substitution where it was incorrectly removing leading slash when param was followed by a non-slash character.
Ex:
'/:foo/:bar.baz/:aux'

params = {
  foo: 'aaa',
  bar: undefined,
  aux: undefined
}

The above params were incorrectly producing '/aaa.baz' but now it results in '/aaa/.baz'.
2013-01-18 20:52:57 -08:00
danilsomsikov a26234f718 fix(ngSwitch): don't leak when destroyed while not attached
The leak can occur when ngSwich is used inside ngRepeat or any other
directive which is destroyed while its transcluded content (which
includes ngSwitch) is not attached to the DOM.

Refactor ngSwitch to use controller instead of storing data on compile
node. This means that we don't need to clean up the jq data cache.
Controller reference is released when the linking fn is released.

Closes #1621
2013-01-18 00:03:28 -08:00
Misko Hevery 61315211da fix(git-validator): support fixup and better errors 2013-01-17 23:52:46 -08:00
Misko Hevery af89daf464 feat(ngResource): support all $http.config actions
This allows the transformation of the $http request in both directions,
headers, caching, and timeout.
2013-01-17 23:08:39 -08:00
Fred Sauer 224d7d6e90 docs(exceptionHandler): document testing
Update src/ng/exceptionHandler.js

Here's an iniitla attempt at documenting how one might write a
test using $exceptionHandlerProvider. The key take-away is the use
of this pattern:

it(...

 module(...
   $exceptionHandlerProvider.mode('log');
 });

 inject(...
 );

});
2013-01-17 23:08:39 -08:00
Jeremy Tymes a179a9a96e feat($parse): allow strict equality in angular expressions
Allows the parser to parse strict equality and inequality
in angular expressions.

Closes #908
2013-01-17 23:08:38 -08:00
Matthew Browne 610a5a0c14 docs(rootScope): correct code examples 2013-01-17 23:08:38 -08:00
Amir H. Hajizamani 4f5583465a docs(cookbook): change prototype methods to scope methods in Buzz
As explained in 'Understanding the Controller Component', Controllers
written for new (post 1.0 RC) versions of Angular need to add methods to
the scope directly, not the function's prototype. Correcting this
example should remove any ambiguity, especially for beginners.
2013-01-18 00:49:41 -05:00
Amir H. Hajizamani 75487ec933 docs(guide): change prototype methods to scope methods in DI examples
As explained in 'Understanding the Controller Component', Controllers
written for new (post 1.0 RC) versions of Angular need to add methods to
the scope directly, not the function's prototype. Correcting this
example should remove any ambiguity, especially for beginners.
2013-01-18 00:49:41 -05:00
Fred Sauer dddb1221fa docs(ngMock.$httpBackend): fix variable declaration 2013-01-18 00:13:39 -05:00
Shai Reznik 69e4d40a76 doc(guide): Fixed typos at the unit tests guide 2013-01-17 23:43:13 -05:00
Shai Reznik d521619c58 doc(guide): Fix examples of $location.html5mode 2013-01-17 23:43:13 -05:00
Igor Minar 5ae63fd385 fix(angular.equals): consistently compare undefined object props
previously:

a = {};
b = {x:undefined};
angular.equals(a, b) == angular.equals(b, a) // returns false.

both should return false because these objects are not equal.

unlike in implemented in #1695 the comparison should be stricter
and return false not true. if we need to relax this in the future
we can always do so.

Closes #1648
2013-01-17 17:48:51 -08:00
Daniel Demmel 8b44324814 docs: recommend using Google CDN 2013-01-17 16:52:05 -08:00
nlaplante 9e991ddb1d feat($log): add $log.debug()
New debug() method with suppressable output via
$logProvider.debugEnabled()

Closes #1592
2013-01-17 16:47:39 -08:00
Matt Rohrer 93070f1488 docs(guide): minor grammar fixes 2013-01-17 19:10:46 -05:00
Gergely Imreh 3c8583e5dd chore(Rakefile): force 32bit JVM mode only when java supports it
Some Java installs don't have '-d32' flag (e.g. OpenJDK which is standard
for some Linux systems), and the closure_compile fails because of forcing
that flag. Test, and only run in faster 32bit mode if supported, or
else just run with no flag (default mode).
2013-01-17 01:34:01 -08:00
Igor Minar 78699c62ea chore(docs): use done() instead of end() in gen-docs.js 2013-01-17 00:51:56 -08:00
Pete Bacon Darwin 49f9e4cef1 fix($compile): do not wrap empty root text nodes in spans
Closes #1059
2013-01-17 00:28:44 -08:00
Pete Bacon Darwin 7e746015ea fix(ngRepeat): correctly apply $last if repeating over object
If the $last property is calculated from the original collectionLength
on an object and properties starting with $ were filtered out, then $last
is never applied and $middle is applied erroniously.

Closes #1789
2013-01-17 00:25:30 -08:00
James deBoer 8c269883fd chore(Rakefile): remove a duplicate file in angularFiles.js 2013-01-16 23:45:09 -08:00
James deBoer a69674b36d chore(Rakefile): generate version.json
Changes 'rake version' to output a version.json file which
contains the structured version info which can be used in other tools.
2013-01-16 23:41:45 -08:00
Igor Minar 8b9e6c3501 fix(scenario): don't trigger input events on IE9
input.enter() should trigger 'change' rather than 'input' event on IE9 because
input events on IE9 are broken and angular doesn't rely on them
2013-01-16 23:26:36 -08:00
Kanwei Li c97c53dbd4 docs(CHANGELOG): fix typo 2013-01-16 21:55:06 +01:00
Martin Probst c6392616ea fix($route): support route params not separated with slashes.
Commit 773ac4a broke support for route parameters that are not seperated
from other route parts by slashes, which this change fixes. It also adds
some documentation about path parameters to the when() method and
escapes all regular expression special characters in the URL, not just
some.
2013-01-16 09:41:02 -08:00
Igor Minar 74dd2f7980 fix($compile): safely create transclude comment nodes
Closes #1740
2013-01-14 21:57:23 -08:00
Lucas Galfasó c0cb9f8c14 doc(directive): Fix typos in dialog widget
Fixes #1799
2013-01-13 10:08:19 +00:00
Igor Minar 7dff7bb696 chore(*): remove obsolete files 2013-01-09 21:28:53 -08:00
Martin Probst cdf6fb19c8 feat($compile): support modifying the DOM structure in postlink fn
Support modifying the DOM structure in the post link function of a directive
by creating a defensive copy of the node list, as opposed to a live DOM list.
This is useful for directives to actually replace their entire DOM fragment,
e.g. with the HTML fragment generated by a 3rd party component (Closure, Bootstrap ...).
Fix the indentation of the compileNodes function (was one too little).
2013-01-09 20:06:22 -08:00
Igor Minar c909f49112 style($compile): fix indentation 2013-01-09 16:54:29 -08:00
Igor Minar cc821502bc fix(date): parse string input as local time unless TZ is specified
previously we were always parsing the string input as UTC which cased issues like:

{{ '2012-04-01' | date:'d MMM yyyy' }} renders as 31 Mar 2012

BREAKING CHANGE: string input without timezone info is now parsed as local time/date

Closes #847
2013-01-09 09:50:43 -08:00
naomiblack 037aefae47 Update docs/content/misc/faq.ngdoc
Updated the canonical video to a recent one. Fixed a typo.
2013-01-09 09:49:44 +00:00
Pete Bacon Darwin febb4c1c35 fix(jqLite): children() should only return elements
The jQuery implementation of children only returns child nodes of the given element that are elements themselves. The previous jqLite implementation was returning all nodes except those that are text nodes. Use jQLite.contents() to get all the child nodes.

The jQuery implementation of contents returns [] if the object has no child nodes.  The previous jqLite implementation was returning undefined, causing a stack overflow in test/testabilityPatch.js when it tried to `cleanup()` a window object.

The testabilityPatch was incorrectly using children() rather than contents() inside cleanup() to iterate down through all the child nodes of the element to clean up.
2013-01-09 09:22:35 +00:00
Keyamoon 76a6047af6 fix(jqLite): make next() ignore non-element nodes
next() is supposed to return the next sibling *element* so it
should ignore text nodes. To achieve this, nextElementSibling()
should be used instead of nextSibling().
2013-01-08 14:54:15 -08:00
Igor Minar b6b7c5a1d6 fix($injector): remove bogus fn arg
getService fn takes only one argument, removing the second one.

Closes #1711
2013-01-08 14:36:03 -08:00
Igor Minar 5b5f35d5e4 refactor($browser): remove faulty 20+ cookies warning
the warning is defunct (and the test is incorrect) so obviously nobody is using
it and it just takes up space.

also the browser behavior varies (ff and chrome allow up to 150 cookies, safari
even more), so it's not very useful.

Closes #1712
2013-01-08 14:23:50 -08:00
Igor Minar 14948cf5d9 revert: fix(a): prevent Opera from incorrectly navigating on link click
This reverts commit c81d8176cc.

This commit causes several issues (#1651, #1674, #1662) and doesn't even
contain a test that proves that anything on Opera got actually fixed.

If the original Opera resurfaces, we'll fix it properly.
2013-01-08 11:47:22 -08:00
kim lokoy 2b0978b07c docs(guide): fix typos in unit test guide 2013-01-07 21:01:01 +01:00
Pawel Kozlowski 1122dc7a5b docs(forms): fix code example for a custom form control
Closes #1021
2013-01-05 22:47:36 +01:00
naomiblack a3a9d4af05 docs(faq): add info on logo reuse and how to get t-shirts and stickers 2013-01-04 18:59:11 +01:00
Jonathan Card 36089931a5 docs(form): minor form doc and example fixes
Form documentation fixes:
- Fix broken form example in docs
- A few small other corrections in form docs.
2013-01-04 09:28:35 +01:00
Per Rovegård 74db36ee94 docs($http): clarify documentation on error status codes
Modify the documentation for $http to correspond to what Angular
considers a success status code.

Closes #1693
2013-01-03 20:49:43 +01:00
Matt Hardy 8d42ce8563 docs(guide): change example controller to properly call greet method on greeter 2012-12-31 12:49:51 +01:00
petrovalex f0c6ebc076 feat($timeout-mock): add verifyNoPendingTasks method
added verifyNoPendingTasks method, which throws error if not all
deferred tasks have been flushed

Closes #1245
2012-12-20 20:39:40 +01:00
Murilo da Silva 59d9b89852 docs(anchorScroll): correct word "location" 2012-12-19 21:01:41 +01:00
John Fletcher 6aac69039e docs(guide): minor English corrections to the Directive guide 2012-12-19 20:35:31 +01:00
Jeremy Tymes 9e96d98345 feat(limitTo): limitTo filter accepts strings
This allows strings to be filtered by limitTo, using the same methods

Closes #653
2012-12-19 20:13:36 +01:00
Miško Hevery 1e13544da8 docs(directive): old syntax 2012-12-18 20:38:43 -08:00
Mark Nadig e03182f018 feat(directive): ng:keydown, ng:keyup
New directives for binding to keydown and keyup events.

Closes #1035
2012-12-18 22:57:58 +01:00
Pawel Kozlowski f2d526190a docs(ngView): fix code example (change template to templateUrl)
Closes #1715
2012-12-17 23:18:12 +01:00
Miško Hevery 039b138042 docs(q): added testing information 2012-12-14 05:49:22 -08:00
Gonzalo Ruiz de Villa 30a9da5dc1 fix($route): correctly extract $routeParams from urls
Routes like '/bar/foovalue/barvalue' matching '/bar/:foo/:bar'
now are well mapped in $routeParams to:
{bar:'barvalue', foo:'foovalue'}

Closes: #1501
Signed-off-by: Gonzalo Ruiz de Villa <gonzaloruizdevilla@gmail.com>
2012-12-14 01:15:15 +01:00
ggoodman 25e1ad9a94 feat(docs): Add angularjs tag to plunks and make private
This is a minor edit to allow Plunks created by way of the docs.angularjs.org site to be appropriately tagged as `angularjs`.
Also, make these generated Plunks private by default.
2012-12-11 19:04:46 +00:00
Peter Evjan 37cced6296 docs(README.md): add missing 'you' and a comma 2012-12-11 19:20:30 +01:00
Romain Neutron a66c968df2 docs(guide): fix injector service code example
Fix syntax and update code to the latest API
2012-12-10 23:19:20 +01:00
Juha Syrjälä c398d7d370 docs($resource): document port number escaping and fix typo
Closes #1243
2012-12-09 17:13:29 +01:00
Eric Case d93533812b docs(tutorial): typo fix commandx -> command 2012-12-08 11:33:07 +01:00
Eric Case ff11061a8f docs($q): typo fix - programing -> programming 2012-12-07 20:45:33 +01:00
János Rusiczki c52bfd37ee doc(concepts): Fix typo in $render() function 2012-12-07 10:03:58 +00:00
Jeremy Tymes f8b755982a docs($http): fix link typo in $http doc
Should be $httpBackend instead of $httpBacked

Closes #1516
2012-12-06 21:25:47 +01:00
Fred Sauer bcc7089b3c docs(mocks): update src/ngMock/angular-mocks.js documentation
Clarify how to use `$exceptionHandlerProvider.mode('log')` in tests
2012-12-06 21:18:49 +01:00
Fred Sauer d43cc3f893 docs(mocks): fix documentation bug: angular.mock.debug 2012-12-06 21:13:51 +01:00
Igor Minar c9d937082c chore(bootstrap-prettify): update urls to code.angularjs.org
Closes #1599
2012-12-05 02:55:43 +01:00
_pants 26adeb119b fix(select): support optgroup + select[multiple] combo
Closes #1553
2012-12-05 02:20:11 +01:00
Stephane Bisson 15183f3e1f feat(e2eRunner): fail when an option to select does not exist 2012-12-01 20:05:42 +01:00
Sudhir Jonathan 2c405f4171 fix($injector): provider can now be defined in the array format
`injector.instantiate` is now called for arrays too, instead of only for functions.

Closes #1452
2012-12-01 18:41:59 +01:00
Sudhir Jonathan 8991680d8a fix($resource): HTTP method should be case-insensitive
Perform call `angular.uppercase` on all given action methods.

Closes #1403
2012-11-30 22:58:11 +01:00
Cezar Berea ec801ac137 refactor($resource): fix indentation and move a method definition
Moved Resource.bind out of the actions forEach
2012-11-30 22:35:57 +01:00
Igor Minar d6da505f4e fix(Scope): ensure that a scope is destroyed only once
Due to bd524fc4 calling $destroy() on a scope mupltiple times cases NPE.

Closes #1627
2012-11-30 13:10:00 +01:00
Daniel Luz 5f7054bf5d docs(directive): correct expression, fix typo and re-wrap lines 2012-11-29 19:40:33 +01:00
Johannes Hansen cf4ed8a145 fix(docs): add missing </div> tag to sourceEdit directive template 2012-11-29 19:40:30 +01:00
Igor Minar d1e48fcbf3 docs(menu): fix the navbar drop down links 2012-11-28 23:56:09 +01:00
Pascal Corpet cc42c99bec feat($resource): allow dynamic default parameters
Default resource params can now be calculated at runtime if defined
via a function.
2012-11-28 16:01:57 +01:00
Igor Minar 3c7bfa77aa chore(release): start 1.1.2 tofu-animation iteration 2012-11-28 14:21:33 +01:00
Igor Minar 2ee0f56c54 chore(release): cutting the 1.1.1 pathological-kerning release 2012-11-27 01:45:35 +01:00
Igor Minar 9b18644f30 docs(CHANGELOG): release notes for 1.0.3 and 1.1.1 releases 2012-11-27 01:43:05 +01:00
Rado Kirov fce100a46c fix($http): only set X-XSFR-TOKEN header for same-domain request
This is needed to prevent CORS preflight checks. The XSFR token
is quite useless for CORS requests anyway.

BREAKING CHANGE: X-XSFR-TOKEN is no longer send for cross domain
requests. This shouldn't affect any known production service.

Closes #1096
2012-11-26 23:58:59 +01:00
Rado Kirov 3a75b1124d fix($http): remove 'X-Requested-With' from header defaults
X-Requested-With header is rarely used in practice and by using
it all the time we are triggering preflight checks for crossdomain
requests.

We could try detecting if we are doing CORS requests or not, but
it doesn't look like it's worth the trouble.

BREAKING CHANGE: X-Requested-With header is not set by $http service
any more. If anyone actually uses this header it's quite easy to add
it back via:

```
myAppModule.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
}]);
```

Closes #1004
2012-11-26 23:36:40 +01:00
Rado Kirov a32bc40fd7 fix($location): reset $location.$$replace with every watch call
Closes #1111
2012-11-26 23:21:02 +01:00
Igor Minar cfe13b5dac chore(validate-commit-msg): recognize 'revert' as valid commit type 2012-11-26 21:05:38 +01:00
Igor Minar d859dcecea fix(ngClassOdd/ngClassEven): support shrinking/reordering in repeaters
We need to watch $index in addition to cssClasses because only then
we can correctly respond to shrinking or reordered repeaters.

Closes #1076
2012-11-26 20:36:53 +01:00
Igor Minar d3b32a7c94 style(jqLite): better variable names
selector => cssClasses
2012-11-26 20:36:53 +01:00
Igor Minar 1b17dfa693 fix(ngRepeat): support mostly-stable repeating for primitives
I'm reverting changes that were originally done to ngRepeat to fix #933,
because these are now not necessary after the previous changes to keep
ngModel always synced with the DOM.
2012-11-26 20:36:53 +01:00
Igor Minar e6d9bea4f3 fix(ngModel): sync ngModel state with scope state
In cases when we reuse elements in a repeater but associate
them with a new scope (see #933 - repeating over array of
primitives) it's possible for the internal ngModel state and
the scope state to get out of sync. This change ensure that
the two are always sync-ed up even in cases where we
reassociate an element with a different (but similar) scope.

In the case of repeating over array of primitives it's still
possible to run into issue if we iterate over primitives and
use form controls or similar widgets without ngModel - oh well,
we'd likely need a special repeater for primitives to deal
with this properly, even then there might be cornercases.

Closes #933
2012-11-26 20:36:53 +01:00
Igor Minar c8e9105fe6 test(ngRepeat): clean up and improve tests 2012-11-26 20:36:52 +01:00
Igor Minar d644dcfa52 fix(ngRepeat): attempt to simplify and improve existing fix for #933
I'm keeping this in for future reference. The issue with this solution
is that if we shift() the first item in the array, the whole repeater
DOM will be rebuilt from scratch, we need to do better than that.
2012-11-26 20:31:54 +01:00
Igor Minar e7d37ee45a test(ngRepeat): add test for issue #1076 2012-11-26 20:17:11 +01:00
Pawel Kozlowski 733a97adf8 feat(form): add ability to reset a form to pristine state
Retting a form to pristine state will cause all of the nested
form and form controls to be recursively reset as well.

Closes #856
2012-11-26 16:44:34 +01:00
Igor Minar 96ed9ff59a fix(jqLite): support append on document fragment
previously jquery didn't support append on this node type, now it does
(since 1.8.x) so I'm adding this to jqlite as well.
2012-11-26 15:45:04 +01:00
Igor Minar b9a9f91fbf fix(jqLite): fire $destroy event via triggerHandler
in jQuery 1.8.x the data() data structure is changed and events are
not accessible via data().events. Since all we need is to trigger
all event handlers, we can do so via triggerHandler() api instead of
mocking with the internal jQuery data structures.

This fix was originally proposed by PeteAppleton via PR #1512.

Closes #1512
2012-11-26 15:45:04 +01:00
Igor Minar 650fd933df feat(jqLite): add triggerHandler()
we need triggerHandler() to become jQuery 1.8.x compatible.

this is not fully featured triggerHandler() implementation - it doesn't
bother creating new DOM events and passing them into the event handlers.

this is intentional, we don't need access to the native DOM event for our
own purposes and creating these event objects is really tricky.
2012-11-26 15:45:04 +01:00
Igor Minar e249502880 chore(jquery): upgrade to jquery 1.8.2 2012-11-26 15:45:04 +01:00
Iristyle ca3e0e7374 docs(CONTRIBUTING.md): add contrib info file for GitHub 2012-11-25 21:00:14 +01:00
Vojta Jina e6966e05f5 fix(Scope): allow removing a listener during event 2012-11-25 11:39:54 +01:00
Kevin Western 682418f029 docs(README.md): fix "API Docs" link
use direct link to api docs
2012-11-25 01:21:52 +01:00
Dean Sofer c8fd7fd0e2 docs(api): add ngRequired to input/select/textarea directives
Closes #1202
2012-11-25 01:18:10 +01:00
JP Sugarbroad 168db33985 feat($cacheFactory): cache.put now returns the added value
This allows common programming patterns to be expressed with more
concise code.

See #1583 for code examples.
2012-11-25 00:01:34 +01:00
Tom Davis 79af2badcb fix($http): config.param should expand array values properly
Today, calling e.g. $http(url, { params: { a: [1,2,3] } }) results in a query
string like "?a=%5B1%2C2%2C3%5D" which is undesirable. This commit enhances
buildURL to createa query string like "?a=1&a=2&a=3".

BREAKING CHANGE: if the server relied on the buggy behavior then either the
backend should be fixed or a simple serialization of the array should be done
on the client before calling the $http service.

Closes #1363
2012-11-24 22:26:23 +01:00
Zach Dexter 610927d77b feat(linky): allow optional 'target' argument
Closes #1443
2012-11-24 22:21:50 +01:00
Jeremy Tymes 55d15806fb fix($cacheFactory): return undefined when removing non-existent entry
Instead of throwning an exception, remove should return undefined when
cache entry to be removed doesn't exist.

Closes #1497
2012-11-24 21:56:28 +01:00
Adrian Gheorghe 94e1c0391c fix($resource): prevent default params to be shared between actions
Having a $resource defined as:

var R = $resource('/Path', {}, {
  get: {method: 'GET', params: {objId: '1'}},
  perform: {method: 'GET'}
});

was causing both actions to call the same URI (if called in this order):

R.get({}); // => /Path?objId=1
R.perform({}); // => /Path?objId=1
2012-11-24 21:27:24 +01:00
Kris Jenkins b21f4a376d docs(): Fix a couple of typos in the documentation 2012-11-21 23:06:59 +01:00
Dave Clayton f28f283fcf docs(guide/concepts): some typo/grammar fixes 2012-11-17 23:55:32 +01:00
John Hume e362a510e3 docs(guide/directive): fix typo 2012-11-17 23:49:19 +01:00
Uri Goldshtein 8a7f752a80 docs($q): fix missing bracket in the example 2012-11-17 23:45:08 +01:00
Igor Minar af7e0bd0a7 fix(CSP): update to the latest CSP api
window.SecurityPolicy.isActive() is now window.securityPolicy.isActive

since this is available only in Chrome Canary which has already been
updated, we can safely make this change without worrying about
backwards compatilibty.

Closes #1577
2012-11-15 01:46:58 +01:00
Igor Minar bd524fc4e5 fix($rootScope): workaround for Chrome's memleak
Under certain circumstances chrome fails to GC scopes
because of buggy optimizations and caching. Nulling out
references to (not from!) other scopes helps Chrome to
realize that this object should be GC-ed.

This is really just a workaround as the real problem needs
to be fixed in Chrome.

See discusstion at:
https://github.com/angular/angular.js/issues/1313#issuecomment-10378451

And chrome bug at:
https://code.google.com/p/v8/issues/detail?id=2073

Closes #1313
2012-11-14 19:56:28 +01:00
Haralan Dobrev 19a324ce11 docs(angular.module): improve angular.Module#run docs 2012-11-11 11:40:18 +01:00
Jamison Dance cd8b78ebfd docs(guide): fix run-on sentence in modules guide 2012-11-11 11:34:06 +01:00
Jamison Dance 8891757891 docs(tutorial): change module name in step-7 2012-11-11 11:34:06 +01:00
Wes Alvaro 5c5193946d docs($timeout): set return type to Promise instead of *.
The cancel function accepts a Promise, but the timeout function
fails to specify returning a Promise.
2012-11-11 11:31:51 +01:00
Josh Adams ffa6c5195f docs(ngList): fix typo 2012-11-11 11:24:59 +01:00
Josh Adams a758799c7f docs(encodeUriSegment): fix typo 2012-11-11 11:22:43 +01:00
Tim Macfarlane e9253a88b9 docs(guide/directive): fix names in scope '='; easier to grok 2012-11-11 11:20:24 +01:00
Christian Vuerings f3e053cb6f docs(ngHide): Fix typo and make it more in line with ngShow 2012-11-11 10:36:28 +01:00
Anna Vester 04450c48df feat($sanitize): support telephone links
Per http://www.ietf.org/rfc/rfc3966.txt support tel: links
2012-11-11 10:31:27 +01:00
Igor Minar 8650843603 chore(docs): fix docs-scenario.html 2012-11-11 10:31:27 +01:00
Igor Minar e034fa08a8 chore(docs): remove obsolete gae files 2012-11-11 10:31:27 +01:00
Miško Hevery c6b4ab3548 Update docs/content/guide/directive.ngdoc
docs(directive): fix typo
2012-11-05 19:34:20 -08:00
Sudhir Jonathan b429f538a3 chore(testacular): use local testacular version
Making testacular a dependency to avoid having to install it globally.
(Causes npm issues on some machines)
2012-10-31 16:47:28 -07:00
Sudhir Jonathan b3cae4f457 fix(select): select option with a label of 0 is not shown
Bug caused by the use of the `||` operator to replace all non-truthy
values with an empty string. Changed to replace only `undefined` values.

Closes #1401
2012-10-31 15:03:13 -07:00
Igor Minar 7b52a976e1 chore(validate-commit-msg): allow '/' in scope 2012-10-31 14:47:59 -07:00
Fred Sauer 3a624a7ff5 docs(guide/location): fix table formatting
Fix table formatting so headings are bold, rows are separated by lines, and rows have :hover style
2012-10-31 14:47:53 -07:00
Tim Macfarlane b32adb7dea docs(module): fix typo in example
fixed example app, `simpleAppModule` should have been `myAppModule`.
2012-10-31 14:21:28 -07:00
sqwishy trick 271d2bed3a chore(injector): fix typo in injector documentation 2012-10-31 14:19:05 -07:00
Adam Macejak 249a1d84e7 fix(scenario-runner): support data-ng and x-ng based attributes
Prefixed attributes like data-ng-model and x-ng-model were not being
found by the Selector. It was only looking at ng: and ng- prefixed
attributes.
Added a few tests as well to ensure the aforementioned prefixed
attributes are being matched properly.

Closes #1020
2012-10-31 13:58:13 -07:00
Daniel Luz fdf85bfd86 docs(contribute): fix task name for continuous testing 2012-10-31 13:12:49 -07:00
Igor Minar 090e5426ac fix(docs): correctly generate filenames for plunkr/fiddle
previously examples like $http where broken because we would strip part of the
filename (http-hello.html -> http)

we really want to strip only the id suffix that we append to disambiguate
common filenames (like index.html) which appear in many examples.
2012-10-31 13:03:50 -07:00
Shyam Seshadri 7c67b2fb6a feat(docs): add plunkr support
Add option to edit source in Angular Docs in Plunkr in addition to JsFiddle
2012-10-31 12:54:52 -07:00
Daniel Luz 3c9b39ff52 fix(doc): typo on FAQ
Closes #1493
2012-10-31 10:24:12 -07:00
Igor Minar 54b3875ba5 fix($compile): don't look for class directives in empty string
if className is undefined or empty string, don't bother looking for directives in there
2012-10-29 17:49:56 -07:00
Igor Minar 008a782bc8 fix($compile): compilation should not recurse into empty nodes
if a node doesn't have children then don't try to compile these non-existent children
2012-10-29 17:49:36 -07:00
Igor Minar 524c5c8b5d style($compile): better fn names for debugging 2012-10-29 17:46:44 -07:00
Igor Minar b936236fbc refactor(): simplify nodeLinkFn 2012-10-29 17:46:44 -07:00
Igor Minar fc115bfd0d fix($compile): prevent double attr interpolation w/ templateUrl
This fixes the issue that caused two attr interpolation observers
to be registered for the same attribute as a result of isolate
scope definition with attr (@) property for this attribute.

Duplicate observers would then fight with each other updating the
model.

The issue occured only when this directive was used in a repeater
because that's when we clone the template node which caused the
two observers to point to two different sets of $attr instances.

Closes #1166, #836
2012-10-29 17:46:44 -07:00
Braden Shepherdson bca1604c12 fix(currency): Handle not-quite-zero values
IEEE 754 floating point sometimes results in values that are very small,
rather than zero. One example is 1.0 + 1.07 - 2.07, which returns
4.440892098500626e-16 instead of 0.

This change tweaks the number formatting logic so that an exponential
value with a negative exponent that is larger than the precision+1
returns 0 instead. For example: with precision 2, anything with an
exponent of -4, -5 or more would become 0. 9e-3 = 0.009 = 0.01, but 9e-4
= 0.0009 = 0.001 = 0.00. This detail is unlikely to matter since this
quirk is usually only triggered with values very close to zero.

Closes #1469
2012-10-26 08:51:28 -07:00
Braden Shepherdson f4517b500c doc(faq): Add Common Pitfalls section
Describes several common pitfalls new users of Angular fall into that
I've observed in #angularjs.
2012-10-26 08:49:44 -07:00
Braden Shepherdson f54edbbdd4 doc(faq): Fix minor spelling and wording errors 2012-10-26 08:49:44 -07:00
Igor Minar be50e0769a chore(check-size.sh): fix rake target 2012-10-22 12:39:37 -07:00
Igor Minar cf78fb5661 docs(contribute): add CLA note to code submission section 2012-10-19 09:14:05 -07:00
Igor Minar 5c9eb75867 docs(contribute): add visible link to github project 2012-10-19 09:02:06 -07:00
Igor Minar f43cf3b816 chore(jstd-scenario-adapter): remove from our repo
since we don't need the adapter for JsTD (testacular contains its own),
I'm removing this dead code.
2012-10-18 03:26:51 -07:00
Igor Minar 175e727f05 chore(validate-commit-msg): allow * and - in scope string 2012-10-18 03:26:08 -07:00
Igor Minar d938983c06 chore(jasmine): remove Jasmine from our repo
it's bundled with Testacular, so we don't need it here
2012-10-18 03:14:27 -07:00
Igor Minar 8d69f4b93a chore(jstd): remove JsTestDriver from our repo
Testacular FTW!
2012-10-18 03:13:04 -07:00
Igor Minar ca96ec32f9 docs(tutorial): replace JsTD with Testacular + drop snapshots
JsTD references have been replaced with Testacular stuff.

snapshots are PITA to maintain so I'm dropping them, everyone loves the Git
version anyway.
2012-10-18 02:33:45 -07:00
Igor Minar 4f59022582 chore(Rakefile): remove test_out dir when cleaning 2012-10-17 15:44:18 -07:00
Igor Minar 3bd95dbb1a chore(Rakefile): tune JVM for closure compiler
Using the client VM and forcing 32bit mode gives us huge perf boost.

before:

reali   0m8.173s
user    0m39.984s
sys     0m1.408s

after:

real    0m3.000s
user    0m12.687s
sys     0m0.852s
2012-10-17 15:36:51 -07:00
Igor Minar c959fa4fe8 chore(Rakefile): paralelize closure compilation
this speeds up the build by paralelizing closure compilation (the slowest
piece of the build process)

before:

real  0m14.372s
user  0m31.649s
sys   0m1.006s

after:

real  0m8.191s
user  0m40.473s
sys   0m1.378s
2012-10-17 14:38:15 -07:00
Vojta Jina a5d434d857 chore(test): add junit config for testacular 2012-10-17 13:17:16 -07:00
Igor Minar 0ae0591f42 chore(Rakefile): misc_options should support + -> , conversion 2012-10-17 12:48:08 -07:00
Igor Minar 43ac783d35 chore(Rakefile): use exec for webserver
exec unlike system replaces the current process. this way when we kill
the webserver process we don't get scary looking 'rake aborted' error
2012-10-17 12:45:10 -07:00
Misko Hevery c96dc60594 fix(doc): disable directory listing in docs.angularjs.org 2012-10-05 16:43:01 -07:00
Igor Minar b440ad36f3 docs(downloading): update the downloading docs 2012-10-05 03:13:51 -07:00
Vojta Jina 8b2532cec7 chore: add travis config 2012-09-24 23:39:33 -07:00
Vojta Jina 8db47ca7d4 fix($compile): reference local in isolate scope
This was really corner case:
Watcher needs to return changed value, to notify that model might have changed and one more $digest cycle needs to be performed.

The watcher, that takes care of reference binding into an isolate scope ("="), did not return changed value, if the change was from the isolate scope to the parent.

If any other watcher returned change, it worked fine, as this change caused re-digest.

Closes #1272
2012-09-20 18:32:01 -07:00
Vojta Jina bcaa3bb373 docs: load angular from CDN only on production
So that when running the docs locally, eg. during e2e testing, we use the latest build version of angular, rather than the stable one from CDN.

This fixes e2e tests running with Testacular.
2012-09-17 18:08:16 -07:00
Igor Minar 6fc4fdb438 docs(README): update README.md with new rake tasks 2012-09-17 14:51:21 -07:00
Igor Minar 6a5f8c0483 chore(Rakefile): fix test:jquery task 2012-09-17 14:50:51 -07:00
Igor Minar 20c116d9d5 docs(contribute): update contribute docs 2012-09-16 10:40:44 -07:00
Igor Minar 4a04c2ec0c chore(): remove unused files 2012-09-16 10:39:43 -07:00
Igor Minar 89dd566277 docs(contribute): update misc/contribute docs with Testacular info 2012-09-15 08:11:53 -07:00
Igor Minar 9d168f058f chore(testing): Testacular config files + rake tasks
- adds testacular config files for jqlite, jquery, modules and e2e tests
- replaces obsolete JsTD Rake tasks with Testacular onces
- rake tasks are parameterazied so that they can be used locally as well as on CI server

usage:

rake test  # run all tests on Chrome
rake test[Safari+Chrome+Opera]  # run all tests on Safari, Chrome and Opera
rake test[Safari]  # run all tests on Safari
rake test:jqlite # run unit tests using jqlite on Chrome
rake test:jqlite[Safari,"--reporter=dots"]  # run jqlite-based unit tests on Safari with dots reporter
rake autotest:jquery  # start testacular with jquery-based config and watch fs for changes
rake test:e2e # run end to end tests
2012-09-13 16:23:18 -07:00
Miško Hevery 5418564f04 docs(directive): remove reference to old isolation syntax 2012-09-13 11:31:06 -07:00
Misko Hevery b0a05a7531 fix($route): support inline annotation on .resolve 2012-09-11 22:10:26 -07:00
Tom Hughes 209b67df6a feat($http): Allow setting withCredentials on defaults
Closes #1095.
2012-09-11 21:59:31 -07:00
Vojta Jina 2e1539356a chore(scripts): add init-repo script 2012-09-11 17:27:57 -07:00
Misko Hevery 331cd5a8cb fix($evalAsync): have only one global async queue
Having one async queue per scope complicates the matters when users wish to do
partial scope updates, since many services put events on the rootScope. By
having single queue the programing model is simplified.
2012-09-11 16:12:41 -07:00
Brian Ford f2ebfa16b0 docs(guide): fix directive interpolation example code
Closes #1339
2012-09-11 16:12:41 -07:00
Shyam Seshadri 95276a7e10 fix(scenario): emit RunnerBegin event 2012-09-11 16:12:41 -07:00
Vojta Jina 5dbd942bac chore(scripts): add commit-msg hook (validation) 2012-09-11 16:12:40 -07:00
Jimmy Zhuo 84c13d96ff fix(scenario): NPE when no angular loaded in test page 2012-09-11 16:12:40 -07:00
Daniel Luz 79941d2527 docs($rootScope): fix iteration limit described by $watch, it's actually 10 as of now 2012-09-11 15:11:02 -07:00
Daniel Luz 03ebecd5eb docs($rootScope): fix typos and minor wording tweaks on $watch 2012-09-11 15:11:02 -07:00
Daniel Luz 62bb728d07 docs($rootScope): fix quoting on expression 2012-09-11 15:11:02 -07:00
Daniel Luz b8eb843b25 docs($rootScope): standardize on present, third-person actions for descriptions 2012-09-11 15:11:02 -07:00
Daniel Luz 053247e412 docs($rootScope): backquote attribute types too on $on 2012-09-11 15:11:02 -07:00
Daniel Luz 7fa391c979 docs($cacheFactory): fix backquotes on method descriptions 2012-09-11 15:11:01 -07:00
Daniel Luz b01c28c900 docs($rootScope): fix typos on $new 2012-09-11 15:11:01 -07:00
Daniel Luz c0b9e94dec docs($rootScope): fix typo on $eval 2012-09-11 15:11:01 -07:00
Daniel Luz 83fbdd1097 docs($rootScope): fix typos on $watch 2012-09-11 15:11:01 -07:00
Jay Zeng 03042c52b9 docs(ngResource): Spelling typo (agressive => aggressive) 2012-09-11 15:08:55 -07:00
Igor Minar 2a4a8226d1 fix($resource): fix isDefined -> angular.isDefined 2012-09-10 14:49:22 -07:00
sgtpep c81d8176cc fix(a): prevent Opera from incorrectly navigating on link click
we handle the navigation by ourselves, so we need to prevent the default action.

Opera ignores event.preventDefault() call so we must return false.
2012-09-06 16:06:27 -07:00
Kai Groner 04329151d2 fix(FormController): propagate dirty state to parent forms 2012-09-06 16:06:26 -07:00
Jonathan Zacsh a9be003fce chore(docs): get correct location for jasmine-node 2012-09-06 16:06:25 -07:00
Shyam Seshadri ca30fce28c fix(*): name all anonymous watch functions in Angular
This will allow us to see function names in Batarang and debugger.

Closes #1119
2012-09-06 16:06:25 -07:00
Xiangru Chen b6e4a71166 fix(ngSrc): don't set src if value is empty string
Current implementation of ngSrc may lead to empty src attribute when page is loading.

For example:

<img ng-src="{{image.url}}">
can be temporarily rendered as

<img src="">
before the image resource is loaded.

Some browser emits a request to the current page when seeing <img src=""> (Firefox13 and IE8 will, Chromium20 won't), which leads to performance problems.
2012-09-06 16:06:24 -07:00
Misko Hevery d9eff86ef7 fix($injector): more conservative annotation parsing 2012-09-06 16:06:24 -07:00
Pedro Del Gallego 8cb9c99ec0 feat(scenario): add dblclick method to the ngScenario dsl 2012-09-06 16:06:24 -07:00
Iwein Fuld 9473780e77 fix(dateFilter): make timezone optional
Makes the time zone optional in the date filter

Problem with the current R_ISO8601_STR regex was that the time was optional, but the zone was not.
This results in the filter not formatting local date times, which it could easily do.

For example:
2012-08-30 -> formatted
2012-08-30T06:06:06.123Z -> formatted
2012-08-30T06:06:06.123 -> NOT formatted

A simple change in the regex fixes this. Arguably this is closer to the ISO8601 spec which specifies
local dates being in the "current time zone" and not requiring a Z. In any case it behaves more like
a user would expect.
2012-09-06 16:06:23 -07:00
Misko Hevery eb5fd400d3 docs(concept): correct example for creating injector 2012-09-06 16:06:23 -07:00
Godmar Back 0472c5f07e docs(module): fixed module example and corrected typos 2012-09-06 16:06:23 -07:00
Cameron Westland 92558fe411 feat(mocha): support mocha in angular mocks 2012-09-06 16:06:23 -07:00
Gregory Pike d519953a4b feat(ngModel): support ngTrim attribute on input 2012-09-06 16:06:23 -07:00
Benjamín Eidelman 4909d1d39d fix($resource): allow falsy values in URL parameters
Close #1212

when a param value was 0 (or false) it was ignored and removed from url.
after this fix that only happens if the value is undefined or null.
2012-09-06 16:06:22 -07:00
Jay Zeng 7079ff5eb6 docs(module): myAppModule -> simpleAppModule 2012-09-06 16:06:22 -07:00
petrovalex 10e1c759f4 fix($resource): ignore undefined parameters
- $resource should handle multiple params with same name
- ignore slashes of undefined parameters
- fix default parameters issue, mentioned in #875

Closes #875
Closes #782
2012-09-06 16:06:22 -07:00
petrovalex 6c67719dfa fix(ngClassEven/Odd): filtering/ordering and repeater
Closes #1076
2012-09-06 16:06:22 -07:00
Max Martinsson cebd015f78 fix(ngClass): works with class interpolation
Closes #1016
2012-09-06 16:06:21 -07:00
Max Martinsson fbdab513dd feat($resource): support custom headers per action
Closes #736
2012-09-06 16:06:21 -07:00
Zhenbo Zhang f2b7fffdc0 fix(ngRepeat): now works with primitive types
closes #933
2012-09-06 16:06:21 -07:00
petrovalex 42c38b29f7 fix($parser): string concatination with undefined model
Closes #988
2012-09-06 16:06:21 -07:00
Stein Jakob Nordbø f299fd5122 fix(dateFilter): support sub-second precision on dateFilter 2012-09-06 16:06:19 -07:00
Igor Minar 05c88b866b docs($route): rename leftover $afterRouteChange to $routeChangeSuccess 2012-09-06 15:03:35 -07:00
Igor Minar 9b08bfa251 chore(release): prepare 1.1.1 pathological-kerning iteration 2012-09-06 10:41:23 -07:00
Misko Hevery 99a000bac2 fix(docs): broken url to angular-bootstrap 2012-09-04 18:15:00 -07:00
Igor Minar f353fea042 chore(Rakefile): add 'version' rake task to generate version.txt 2012-09-04 16:38:01 -07:00
Igor Minar b1f50307b3 chore(docs): bump up the stable version 2012-09-04 16:31:57 -07:00
Igor Minar d0c0eadedd chore(release): cut the 1.1.0 increase-gravatar release 2012-09-04 11:11:09 -07:00
Igor Minar b8fac353f0 chore(docs): don't rewrite colons in doc filenames 2012-09-04 11:11:09 -07:00
Igor Minar b22308152f chore(Rakefile): zip only the build dir 2012-08-31 13:59:03 -07:00
Igor Minar 5e9041818b revert: fix(ng-repeat) to work with primitive types
this was accidentaly merged in. the commit is not ready yet
and we don't have CLA signature.

This reverts commit 98d489712e.
2012-08-31 13:59:03 -07:00
Igor Minar db861db1f2 docs(changelog): release notes for 1.0.2 and 1.1.0 releases 2012-08-31 13:59:03 -07:00
Jonathan Zacsh b12d1b6813 fix(docs): Making sure gen_docs.sh looks for a globally installed copy of jasmine-node as well as local. 2012-08-30 22:33:30 -07:00
Fernando Correia acb499f820 docs(tutorial): correct typos and clarify a few sections 2012-08-30 22:19:34 -07:00
Brice Burgess 9a710c788d fix(docs): indicate support for passing a string as the controller property on $routeProvider's route object 2012-08-30 22:13:20 -07:00
brettcannon 1b34c6d558 doc(misc) Mention how attribute names map to directive names. 2012-08-30 22:09:16 -07:00
Igor Minar a62c7b8b4e test(locationSpec): fix broken tests after vojta's commit 2012-08-30 16:25:23 -07:00
Sahat Yalkabov 62cfedbe0c doc(module) changed simpleApp to myApp in the Module page guide for consistency 2012-08-30 16:10:39 -07:00
Steve Nicolai 5cb7297a08 doc(devguide) - Fix typos and small grammatical errors in the developer guide. 2012-08-30 16:02:24 -07:00
Igor Minar 0f05516d14 chore(docs): ask GAE to serve docs-keywords.js 2012-08-30 15:58:55 -07:00
Uri Goldshtein f5f1200f25 Loading from Google CDN
As you guys had mansion, we can and need to do it through Google CDN for better performance,
so i've updated it accordingly
2012-08-30 15:49:11 -07:00
Tyson Benson c023c850c3 docs(typos): fix typos in dev guide 2012-08-30 15:43:58 -07:00
German Galvis 5318588d6e fix(scenario): Adding meta tag to avoid cache issues 2012-08-30 15:36:42 -07:00
phil 14c8f6a7ca docs(api): fix typo on home page
Refference -> Reference
2012-08-30 15:31:29 -07:00
csugden 351deb555f Update docs/content/guide/overview.ngdoc
Corrects video information
2012-08-30 15:28:15 -07:00
Jamie Krug 847d2da0f8 fix(docs): Fix typos and improve grammar. 2012-08-30 15:25:21 -07:00
Jamie Krug dbefd671e4 fix(docs): Fix bad links. 2012-08-30 15:25:20 -07:00
Colin Frei aff68a9ddf docs(module) fix typo 2012-08-30 15:22:08 -07:00
Zhenbo Zhang 0a71753ce3 fix(ng-repeat) to work with primitive types 2012-08-30 15:20:40 -07:00
Vojta Jina 1a8642aac2 fix(mocks): free up memory after every spec 2012-08-30 15:18:09 -07:00
Igor Minar 8114d55a15 test(bootstrap): test exception siling during bootstrap
Closes #1018
2012-08-30 15:15:11 -07:00
Igor Minar 9398040a41 test(ngApp): add missing test for [ng-app] bootstrap 2012-08-30 15:15:11 -07:00
Brian Ford d804bbcd51 feat($interpolate): provide contextual error messages
if an exception occurs during interpolation of a string
(e.g. name() in "Hello, {{name()}}!" throws an exception) we now print
an error message with the expression that was being evaluated when the
exception was thrown.
2012-08-30 14:50:22 -07:00
Igor Minar d3fa7a2e9e fix(jqLite): better support for xhtml
it turns out that some stuff doesn't work in xhtml as it does in html.

for example &nbsp; can't be innerHTML-ed and auto-closing of elements
doesn't work.

the reporter of the referenced issue claimed that innerHTML vs text on
script made a difference but that doesn't appear to be true in my testing.

I'm not including test for this because testacular doesn't currently
run tests in xhtml yet.

Closes #1301
2012-08-30 10:53:23 -07:00
Igor Minar 8693eac417 chore(docs): correctly link docs images 2012-08-30 02:26:35 -07:00
Igor Minar e0184d4aef chore(Rakefile): fix the default task 2012-08-29 16:56:48 -07:00
Igor Minar 1702e49548 chore(Rakefile): remove bogus symlink from build 2012-08-29 14:58:10 -07:00
Igor Minar d6706efe7f chore(docs): use symlinks to build docs
so that we can just edit source files without rebuilding docs.

this works for all docs files, except for those that are generated
or rewritten during build.
2012-08-28 16:06:50 -07:00
Igor Minar b08d4b22d2 chore(Rakefile): various build script changes
- restructure rake tasks

  this splits up the concatination and minification into two
  tasks so that we can just build angular.js quickly without wasting
  time with minification which is often not needed when just debugging
  some issue on 3rd party site.

- use symlinks when creating final zip file

- switch from btar to zip

- get rid of version numbers from filenames

- rewrite version numbers in all index files

Closes #1226
2012-08-28 12:38:34 -07:00
Misko Hevery e8ded01cf5 doc($log): correct non-working example 2012-08-27 15:44:38 -07:00
Misko Hevery 7a5f25f667 doc(guide): add concepts 2012-08-27 15:44:38 -07:00
Misko Hevery 96697f464f fix(ngdoc): failing test 2012-08-27 15:44:38 -07:00
Colin Frei 7e18724dfa doc(directive) correct typos 2012-08-27 15:01:50 -07:00
Misko Hevery c269eb3d26 fix(docs) typo 2012-08-27 14:59:19 -07:00
Misko Hevery fa62ea810f fix(ng-list): remove data bound flicker 2012-08-27 14:59:18 -07:00
Misko Hevery bf8ed8a532 doc(misc) updated getting started to reflect the new homepage 2012-08-27 14:59:18 -07:00
Misko Hevery d05a2809a1 doc(guide) simplify the guide home page 2012-08-27 14:59:17 -07:00
Igor Minar fa6c8c3131 chore(Rakefile): rewrite version numbers in all index files 2012-08-27 12:26:04 -07:00
Igor Minar f7ac8ef97a chore(docs): support _escaped_fragment_ hack for crawler 2012-08-25 02:30:55 -07:00
Igor Minar 4a4b28dbf3 chore(docs): use GAE and Google CDN for docs
Short summary: if you use local node server everything should work as before,
if you use GAE, everything should work now as well, but we pull assets from CDN.

- GAE doesn't support ':' in filenames, so I had to replace it with '_'
  but only in the filename, all servers were reconfigured to rewrite the
  urls from : to _ when doing file lookup
- We now pull angular assets from google CDN when deployed on GAE (locally
  or in production). When running on a non GAE server we pull assets from
  ../ directory as before
- Since only certain versions of Angular are available on CDN and we want
  to be able to autodeploy docs, I had to pin down the Angular files
  to a "stable" version when running on GAE
2012-08-24 14:54:35 -07:00
Igor Minar 3e12bc481d docs(a): expose hidden docs
It seems that docs for these directive were previously hidden by accident
2012-08-24 14:54:34 -07:00
johnlindquist 32137cab82 docs(ngRoute): fix typo
aftre -> after
2012-08-23 15:11:07 -04:00
phil f7b4296c38 docs(tutorial): fix typo in step_00
Just removed an extra comma. No big deal.
2012-08-23 01:02:28 -07:00
Igor Minar cab5e1d9b3 fix(docs): update docs top menu links 2012-08-16 11:26:36 -07:00
Igor Minar dfe99836cd fix($compile): denormalize directive templates
Since developers are allowed to customize start/end interpolation
strings, but third-party directive creators don't know about these
customizations, we should standardize on {{ }} in templates of
reusable (third-party) directives. During the compilation, these
templates are then denormalized to use whatever the custom
start/end symbol is, effectively translating the template into the
syntax of the runtime environment.

This addresses an issue raised at http://goo.gl/e8VPV

Existing code should not be affected by this change since project
that do use custom interpolation markers are not expected to use
{{ }} in existing directive templates.
2012-08-13 14:33:56 -07:00
Igor Minar 0f37194fb7 refactor($compile): code cleanup 2012-08-13 09:48:23 -07:00
Brian Ford e85774f709 fix(ngPluralize): fixes ng-pluralize when using non-standard start/end symbols
Closes #1134
2012-08-13 09:48:23 -07:00
Igor Minar 44345c74de style(ngPluralizeSpec): fix indentation 2012-08-13 09:48:22 -07:00
Igor Minar 58f121a5c2 feat($interpolate): expose start/end symbols in run phase
previously the startSymbol() and endSymbol() getters were exposed only via provider
in the config phase
2012-08-13 09:48:22 -07:00
Igor Minar cf6023ef22 docs($interpolateProvider): fixing docs 2012-08-13 09:48:22 -07:00
Igor Minar 2034871764 fix($interpolate): $interpolateProvider.endSymbol() returns startSymbol
I also added missing tests.
2012-08-13 09:48:21 -07:00
Igor Minar 15d283b114 docs($interpolate): fix typo in description 2012-08-13 09:48:21 -07:00
Vojta Jina 9be169365c docs($compileProvider): remove duplicate of .directive() 2012-08-12 10:45:14 -07:00
Vojta Jina 00683a8bbb docs: fix broken links to $compileProvider.directive() 2012-08-12 10:44:29 -07:00
Brian Ford f00b6cca02 fix(docs): fixed documentation for using linky 2012-08-10 16:33:25 -07:00
Brian Ford e05a97c6f5 chore(ngDoc): add support for custom @usage metadata 2012-08-10 16:33:13 -07:00
Brian Ford 2e3651686c fix(docs): added note about using JSON3 as a polyfill for IE7 2012-08-10 16:27:44 -07:00
Brian Ford 536de14821 fix(docs): added note about needing JSON shim for IE7 and earlier 2012-08-10 16:27:44 -07:00
Vojta Jina e0a54f6b20 feat($http): support reponseType
Closes #1013
2012-08-10 16:17:59 -07:00
Igor Minar 9767f7bdd3 fix(option): support option elements in datalist
previously we expected to find option elements only within select element and if
that was not the case we throw an error. This made it impossible to include datalist
element with nested option elements in the template.

Closes #1165
2012-08-10 16:14:30 -07:00
Vojta Jina 167aa0c29c feat($sniffer): auto detect CSP mode
Chrome Canary now has CSP with apis that allow auto-detection. This change
will turn on CSP mode automatically when we detect its presence.

https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#script-interfaces--experimental
2012-08-10 14:53:53 -07:00
unirgy 4ccd9eb883 docs($rootScope): fix $on listener signature doc
Added args in $on() listener syntax declaration
2012-08-10 14:50:22 -07:00
Igor Minar c0d638a94b test(jqLite): add missing test for $destroy event 2012-08-10 13:04:39 -07:00
Igor Minar 054d40f338 fix(form): prevent page reload when form destroyed
this fix ensures that we prevent the default action on form submission
(full page reload) even in cases when the form is being destroyed as
a result of the submit event handler (e.g. when route change is
triggered).

The fix is more complicated than I'd like it to be mainly because
we need to ensure that we don't create circular references between
js closures and dom elements via DOM event handlers that would then
result in a memory leak.

Also the differences between IE8, IE9 and normal browsers make testing
this ugly.

Closes #1238
2012-08-10 13:03:55 -07:00
Igor Minar 5cec32492c test(form): fix broken preventDefault test
the original test relied on incorrect assumptions about how jasmine async
tests work (when setTimeout is triggered) and how browser reloads a page
(the sequence of events) and thus the test passes even when the default
is not prevented.

this change fixes the test by registering an extra submit event handler
that checks if the default was prevented.

if the default was not prevented, the test will fail and the page will
be reloaded causing the test runner to panic.
2012-08-07 22:07:49 -07:00
Igor Minar c25cb7d488 refactor(formSpec): group preventDefault specs into a describe 2012-08-07 17:23:16 -07:00
Igor Minar 54e4a6ffbf docs(faq): update faq docs 2012-08-07 10:15:54 -07:00
Igor Minar eee9a51fad docs(styles): fix the cog icon alignment 2012-08-06 16:54:18 -07:00
Vojta Jina 77e6d833f6 chore(nodeserver): add font mime type 2012-08-04 12:09:28 -07:00
Vojta Jina 33ad2b4126 docs(guide): hide scenario for directive example
scenario test for this example would be tricky, we need to teach
the runner how to inject mocks first.
2012-07-30 21:21:34 -07:00
Vojta Jina b84eaffd39 docs: fix icons
Copy fontawesome during build
2012-07-30 21:20:28 -07:00
brettcannon a1107e81eb fix(docs): "in depth" -> "in-depth" 2012-07-20 20:54:42 -07:00
JP Sugarbroad e3e8813e3c refactor($injector): move $injector into the providerCache
Better than special-casing '$injector' in createInjector.
2012-07-19 21:56:22 -07:00
Igor Minar 6e2d9711e8 chore(release): start 1.1.0 increase-gravatas iteration 2012-07-19 21:48:58 -07:00
91 changed files with 6600 additions and 4384 deletions
+122 -2
View File
@@ -1,3 +1,123 @@
<a name="1.1.3"></a>
# 1.1.3 radioactive-gargle (2013-02-20)
_Note: 1.1.x releases are [considered unstable](http://blog.angularjs.org/2012/07/angularjs-10-12-roadmap.html).
They pass all tests but we reserve the right to change new features/apis in between minor releases. Check them
out and please give us feedback._
_Note: This release also contains all bug fixes available in [1.0.5](#1.0.5)._
## Bug Fixes
- **$compile:**
- initialize interpolated attributes before directive linking
([bb8448c0](https://github.com/angular/angular.js/commit/bb8448c011127306df08c7479b66e5afe7a0fa94))
- interpolate @ locals before the link function runs
([2ed53087](https://github.com/angular/angular.js/commit/2ed53087d7dd06d728e333a449265f7685275548))
- **$http:**
- do not encode special characters `@$:,` in params
([288b69a3](https://github.com/angular/angular.js/commit/288b69a314e9bd14458b6647532eb62aad5c5cdf))
- **$resource:**
- params should expand array values properly
([2a212344](https://github.com/angular/angular.js/commit/2a2123441c2b749b8f316a24c3ca3f77a9132a01))
## Features
- **$http:** allow overriding the XSRF header and cookie name
([8155c3a2](https://github.com/angular/angular.js/commit/8155c3a29ea0eb14806913b8ac08ba7727e1969c))
- **$parse:** added `constant` and `literal` properties
([1ed63858](https://github.com/angular/angular.js/commit/1ed638582d2f2c7f89384d9712f4cfac52cc5b70))
- **$resource:** expose promise based api via $then and $resolved
([dba6bc73](https://github.com/angular/angular.js/commit/dba6bc73e802fdae685a9f351d3e23c7efa8568a))
- **$routeProvider:** add support to catch-all parameters in routes
([7eafbb98](https://github.com/angular/angular.js/commit/7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5))
- **Scope:**
- expose transcluded and isolate scope info for batarang
([649b8922](https://github.com/angular/angular.js/commit/649b892205615a144dafff9984c0e6ab10ed341d))
- only evaluate constant $watch expressions once
([1d7a95df](https://github.com/angular/angular.js/commit/1d7a95df565192fc02a18b0b297b39dd615eaeb5))
- **angular.noConflict:** added api to restore previous angular namespace reference
([12ba6cec](https://github.com/angular/angular.js/commit/12ba6cec4fb79521101744e02a7e09f9fbb591c4))
- **Directives:**
- **ngSwitch:** support multiple matches on ngSwitchWhen and ngSwitchDefault
([0af17204](https://github.com/angular/angular.js/commit/0af172040e03811c59d01682968241e3df226774),
[#1074](https://github.com/angular/angular.js/issues/1074))
- **Filters:**
- **date:** add `[.,]sss` formatter for milliseconds
([df744f3a](https://github.com/angular/angular.js/commit/df744f3af46fc227a934f16cb63c7a6038e7133b))
- **filter:** add comparison function to filter
([ace54ff0](https://github.com/angular/angular.js/commit/ace54ff08c4593195b49eadb04d258e6409d969e))
## Breaking Changes
- **$http:** due to [288b69a3](https://github.com/angular/angular.js/commit/288b69a314e9bd14458b6647532eb62aad5c5cdf),
$http now follows RFC3986 and does not encode special characters like `$@,:` in params.
If your application needs to encode these characters, encode them manually, before sending the request.
- **$resource:** due to [2a212344](https://github.com/angular/angular.js/commit/2a2123441c2b749b8f316a24c3ca3f77a9132a01),
if the server relied on the buggy behavior of serializing arrays as http query arguments then
either the backend should be fixed or a simple serialization of the array should be done
on the client before calling the resource service.
<a name="1.0.5"></a>
# 1.0.5 flatulent-propulsion (2013-02-20)
## Bug Fixes
- **$compile:**
- sanitize values bound to a[href]
([9532234b](https://github.com/angular/angular.js/commit/9532234bf1c408af9a6fd2c4743fdb585b920531))
- rename $compileNote to compileNode
([92ca7efa](https://github.com/angular/angular.js/commit/92ca7efaa4bc4f37da3008b234e19343a1fa4207),
[#1941](https://github.com/angular/angular.js/issues/1941))
- should not leak memory when there are top level empty text nodes
([791804bd](https://github.com/angular/angular.js/commit/791804bdbfa6da7a39283623bd05628a01cd8720))
- allow startingTag method to handle text / comment nodes
([755beb2b](https://github.com/angular/angular.js/commit/755beb2b66ce9f9f9a218f2355bbaf96d94fbc15))
- **$cookies:** set cookies on Safari&IE when base[href] is undefined
([70909245](https://github.com/angular/angular.js/commit/7090924515214752b919b0c5630b3ea5e7c77223),
[#1190](https://github.com/angular/angular.js/issues/1190))
- **$http:**
- patch for Firefox bug w/ CORS and response headers
([e19b04c9](https://github.com/angular/angular.js/commit/e19b04c9ec985821edf1269c628cfa261f81d631),
[#1468](https://github.com/angular/angular.js/issues/1468))
- **$resource:**
- update RegExp to allow urlParams with out leading slash
([b7e1fb05](https://github.com/angular/angular.js/commit/b7e1fb0515798e1b4f3f2426f6b050951bee2617))
- **Directives:**
- **a:** workaround IE bug affecting mailto urls
([37e8b122](https://github.com/angular/angular.js/commit/37e8b12265291918396bfee65d444a8f63697b73),
[#1949](https://github.com/angular/angular.js/issues/1949))
- **ngClass:** keep track of old ngClass value manually
([5f5d4fea](https://github.com/angular/angular.js/commit/5f5d4feadbfa9d8ecc8150041dfd2bca2b2e9fea),
[#1637](https://github.com/angular/angular.js/issues/1637))
- **ngSwitch:** make ngSwitch compatible with controller backwards-compatiblity module
([9b7c1d0f](https://github.com/angular/angular.js/commit/9b7c1d0f7ce442d4ad2ec587e66d2d335e64fa4e))
- **Filters:**
- **date:** invert timezone sign and always display sign
([b001c8ec](https://github.com/angular/angular.js/commit/b001c8ece5472626bf49cf82753e8ac1aafd2513),
[#1261](https://github.com/angular/angular.js/issues/1261))
- **number:** fix formatting when "0" passed as fractionSize
([f5835963](https://github.com/angular/angular.js/commit/f5835963d5982003a713dd354eefd376ed39ac02))
- **scenario runner:** include error messages in XML output
([d46fe3c2](https://github.com/angular/angular.js/commit/d46fe3c23fa269dcc10249148f2af14f3db6b066))
- **Misc:**
- don't use instanceof to detect arrays
([3c2aee01](https://github.com/angular/angular.js/commit/3c2aee01b0b299995eb92f4255159585b0f53c10),
[#1966](https://github.com/angular/angular.js/issues/1966))
- angular.forEach should correctly iterate over objects with length prop
([ec54712f](https://github.com/angular/angular.js/commit/ec54712ff3dab1ade44f94fa82d67edeffa79a1d),
[#1840](https://github.com/angular/angular.js/issues/1840))
<a name="1.1.2"></a>
# 1.1.2 tofu-animation (2013-01-22)
@@ -73,6 +193,8 @@ _Note: This release also contains all bug fixes available in [1.0.4](#1.0.4)._
- HTTP method should be case-insensitive
([8991680d](https://github.com/angular/angular.js/commit/8991680d8ab632dda60cd70c780868c803c74509),
[#1403](https://github.com/angular/angular.js/issues/1403))
- correct leading slash removal in resource URLs
([b2f46251](https://github.com/angular/angular.js/commit/b2f46251aca76c8568ee7d4bab54edbc9d7a186a))
- **$route:**
- support route params not separated with slashes.
([c6392616](https://github.com/angular/angular.js/commit/c6392616ea5245bd0d2f77dded0b948d9e2637c8))
@@ -100,8 +222,6 @@ _Note: This release also contains all bug fixes available in [1.0.4](#1.0.4)._
- **ngRepeat:** correctly apply $last if repeating over object
([7e746015](https://github.com/angular/angular.js/commit/7e746015ea7dec3e9eb81bc4678fa9b6a83bc47c),
[#1789](https://github.com/angular/angular.js/issues/1789))
- **ngResource:** correct leading slash removal.
([b2f46251](https://github.com/angular/angular.js/commit/b2f46251aca76c8568ee7d4bab54edbc9d7a186a))
- **ngSwitch:** don't leak when destroyed while not attached
([a26234f7](https://github.com/angular/angular.js/commit/a26234f7183013e2fcc9b35377e181ad96dc9917),
[#1621](https://github.com/angular/angular.js/issues/1621))
-4
View File
@@ -26,10 +26,6 @@ Building AngularJS
Running Tests
-------------
Running tests requires installation of [Testacular](http://vojtajina.github.com/testacular):
sudo npm install -g testacular
To execute all unit tests, use:
rake test:unit
+6 -25
View File
@@ -21,6 +21,8 @@ task :default => [:package]
desc 'Init the build workspace'
task :init do
%x(npm install)
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
v = YAML::load( File.open( 'version.yaml' ) )
@@ -57,25 +59,6 @@ task :concat_scenario => :init do
end
desc 'Concat JSTD Scenario Adapter'
task :concat_jstd_scenario_adapter => :init do
concat_file('jstd-scenario-adapter.js', [
'src/ngScenario/jstd-scenario-adapter/angular.prefix',
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
'src/ngScenario/jstd-scenario-adapter/angular.suffix',
])
# TODO(vojta) use jstd configuration when implemented
# (instead of including jstd-adapter-config.js)
File.open(path_to('jstd-scenario-adapter-config.js'), 'w') do |f|
f.write("/**\r\n" +
" * Configuration for jstd scenario adapter \n */\n" +
"var jstdScenarioAdapter = {\n relativeUrlPrefix: '/build/docs/'\n};\n")
end
end
desc 'Concat AngularJS files'
task :concat => :init do
@@ -115,7 +98,7 @@ end
desc 'Minify JavaScript'
task :minify => [:init, :concat, :concat_scenario, :concat_jstd_scenario_adapter] do
task :minify => [:init, :concat, :concat_scenario] do
[ 'angular.js',
'angular-cookies.js',
'angular-loader.js',
@@ -124,11 +107,7 @@ task :minify => [:init, :concat, :concat_scenario, :concat_jstd_scenario_adapter
'angular-bootstrap.js',
'angular-bootstrap-prettify.js'
].each do |file|
unless ENV['TRAVIS']
fork { closure_compile(file) }
else
closure_compile(file)
end
fork { closure_compile(file) }
end
Process.waitall
@@ -366,6 +345,8 @@ end
def start_testacular(config, singleRun, browsers, misc_options)
Rake::Task[:init].invoke
sh "./node_modules/testacular/bin/testacular start " +
"#{config} " +
"#{'--single-run=true' if singleRun} " +
+3 -3
View File
@@ -234,7 +234,7 @@ The separation of the controller and the view is important because:
The model is the data which is used merged with the template to produce the view. To be able to
render the model into the view, the model has to be able to be referenced from the scope. Unlike many
other frameworks Angular makes no restrictions or requirements an the model. There are no classes
other frameworks Angular makes no restrictions or requirements on the model. There are no classes
to inherit from or special accessor methods for accessing or changing the model. The model can be
primitive, object hash, or a full object Type. In short the model is a plain JavaScript object.
@@ -248,9 +248,9 @@ primitive, object hash, or a full object Type. In short the model is a plain Jav
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-view.png">
The view is what the users sees. The view begins its life as a template, it is merged with the
The view is what the user sees. The view begins its life as a template, is merged with the
model and finally rendered into the browser DOM. Angular takes a very different approach to
rendering the view, compared to most other templating systems.
rendering the view compared to most other templating systems.
* **Others** - Most templating systems begin as an HTML string with special templating markup.
Often the template markup breaks the HTML syntax which means that the template can not be
@@ -2,9 +2,7 @@
@name Developer Guide: Templates: Understanding Angular Filters
@description
Angular filters format data for display to the user. In addition to formatting data, filters can
also modify the DOM. This allows filters to handle tasks such as conditionally applying CSS styles
to filtered output.
Angular filters format data for display to the user.
For example, you might have a data object that needs to be formatted according to the locale before
displaying it to the user. You can pass expressions through a chain of filters like this:
+1 -1
View File
@@ -95,7 +95,7 @@ Compilation of HTML happens in three phases:
var $compile = ...; // injected into your code
var scope = ...;
var html = '<div ng-bind='exp'></div>';
var html = '<div ng-bind="exp"></div>';
// Step 1: parse HTML into DOM element
var template = angular.element(html);
+3 -3
View File
@@ -158,9 +158,9 @@ angular.module('myModule', []).
angular.module('myModule', []).
config(function($provide, $compileProvider, $filterProvider) {
$provide.value('a', 123)
$provide.factory('a', function() { return 123; })
$compileProvider.directive('directiveName', ...).
$provide.value('a', 123);
$provide.factory('a', function() { return 123; });
$compileProvider.directive('directiveName', ...);
$filterProvider.register('filterName', ...);
});
</pre>
+2 -1
View File
@@ -222,7 +222,8 @@ To run the E2E test suite:
To create and submit a change:
1. Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be
1. <a name="CLA"></a>
Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be
accepted, the CLA must be signed. It's a quick process, we promise!
For individuals we have a [simple click-through form](http://code.google.com/legal/individual-cla-v1.0.html). For
+2 -2
View File
@@ -109,7 +109,7 @@ __`app/index.html`:__
<html ng-app>
The `ng-app` attribute is represents an Angular directive (named `ngApp`; Angular uses
The `ng-app` attribute represents an Angular directive (named `ngApp`; Angular uses
`name-with-dashes` for attribute names and `camelCase` for the corresponding directive name)
used to flag an element which Angular should consider to be the root element of our application.
This gives application developers the freedom to tell Angular if the entire html page or only a
@@ -127,7 +127,7 @@ being the element on which the `ngApp` directive was defined.
* Double-curly binding with an expression:
Nothing here {{'yet' + '!'}}`
Nothing here {{'yet' + '!'}}
This line demonstrates the core feature of Angular's templating capabilities a binding, denoted
by double-curlies `{{ }}` as well as a simple expression `'yet' + '!'` used in this binding.
+23 -5
View File
@@ -55,12 +55,15 @@ describe('ngdoc', function() {
'@name a\n' +
'@param {*} a short\n' +
'@param {Type} b med\n' +
'@param {Class=} [c=2] long\nline');
'@param {Class=} [c=2] long\nline\n' +
'@param {function(number, string=)} d fn with optional arguments');
doc.parse();
expect(doc.param).toEqual([
{name:'a', description:'<p>short</p>', type:'*', optional:false, 'default':undefined},
{name:'b', description:'<p>med</p>', type:'Type', optional:false, 'default':undefined},
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'}
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'},
{name:'d', description:'<p>fn with optional arguments</p>',
type: 'function(number, string=)', optional: false, 'default':undefined}
]);
});
@@ -318,9 +321,9 @@ describe('ngdoc', function() {
});
it('should not parse @property without a type', function() {
var doc = new Doc("@property fake");
var doc = new Doc("@property fake", 'test.js', '44');
expect(function() { doc.parse(); }).
toThrow(new Error("Not a valid 'property' format: fake"));
toThrow(new Error("Not a valid 'property' format: fake (found in: test.js:44)"));
});
it('should parse @property with type', function() {
@@ -350,15 +353,30 @@ describe('ngdoc', function() {
describe('@returns', function() {
it('should not parse @returns without type', function() {
var doc = new Doc("@returns lala");
expect(doc.parse).toThrow();
expect(function() { doc.parse(); }).
toThrow();
});
it('should not parse @returns with invalid type', function() {
var doc = new Doc("@returns {xx}x} lala", 'test.js', 34);
expect(function() { doc.parse(); }).
toThrow(new Error("Not a valid 'returns' format: {xx}x} lala (found in: test.js:34)"));
});
it('should parse @returns with type and description', function() {
var doc = new Doc("@name a\n@returns {string} descrip tion");
doc.parse();
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip tion</p>'});
});
it('should parse @returns with complex type and description', function() {
var doc = new Doc("@name a\n@returns {function(string, number=)} description");
doc.parse();
expect(doc.returns).toEqual({type: 'function(string, number=)', description: '<p>description</p>'});
});
it('should transform description of @returns with markdown', function() {
var doc = new Doc("@name a\n@returns {string} descrip *tion*");
doc.parse();
+56 -41
View File
@@ -214,23 +214,25 @@ Doc.prototype = {
if (atName) {
var text = trim(atText.join('\n')), match;
if (atName == 'param') {
match = text.match(/^\{([^}=]+)(=)?\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 12 2 34 4 5 5 6 6 3 7 7
match = text.match(/^\{([^}]+)\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 1 23 3 4 4 5 5 2 6 6
if (!match) {
throw new Error("Not a valid 'param' format: " + text);
throw new Error("Not a valid 'param' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
}
var optional = (match[1].slice(-1) === '=');
var param = {
name: match[5] || match[4],
description:self.markdown(text.replace(match[0], match[7])),
type: match[1],
optional: !!match[2],
'default':match[6]
name: match[4] || match[3],
description:self.markdown(text.replace(match[0], match[6])),
type: optional ? match[1].substring(0, match[1].length-1) : match[1],
optional: optional,
'default':match[5]
};
self.param.push(param);
} else if (atName == 'returns' || atName == 'return') {
match = text.match(/^\{([^}=]+)\}\s+(.*)/);
match = text.match(/^\{([^}]+)\}\s+(.*)/);
if (!match) {
throw new Error("Not a valid 'returns' format: " + text + ' in ' + self.file + ':' + self.line);
throw new Error("Not a valid 'returns' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
}
self.returns = {
type: match[1],
@@ -245,7 +247,7 @@ Doc.prototype = {
} else if(atName == 'property') {
match = text.match(/^\{(\S+)\}\s+(\S+)(\s+(.*))?/);
if (!match) {
throw new Error("Not a valid 'property' format: " + text);
throw new Error("Not a valid 'property' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
}
var property = new Doc({
type: match[1],
@@ -383,40 +385,53 @@ Doc.prototype = {
var self = this;
dom.h('Usage', function() {
var restrict = self.restrict || 'AC';
if (restrict.match(/E/)) {
dom.text('as element (see ');
dom.text('This directive can be used as custom element, but we aware of ');
dom.tag('a', {href:'guide/ie'}, 'IE restrictions');
dom.text(')');
dom.code(function() {
dom.text('<');
dom.text(dashCase(self.shortName));
renderParams('\n ', '="', '"');
dom.text('>\n</');
dom.text(dashCase(self.shortName));
dom.text('>');
});
dom.text('.');
}
if (restrict.match(/A/)) {
var element = self.element || 'ANY';
dom.text('as attribute');
dom.code(function() {
dom.text('<' + element + ' ');
dom.text(dashCase(self.shortName));
renderParams('\n ', '="', '"', true);
dom.text('>\n ...\n');
dom.text('</' + element + '>');
});
}
if (restrict.match(/C/)) {
dom.text('as class');
var element = self.element || 'ANY';
dom.code(function() {
dom.text('<' + element + ' class="');
dom.text(dashCase(self.shortName));
renderParams(' ', ': ', ';', true);
dom.text('">\n ...\n');
dom.text('</' + element + '>');
if (self.usage) {
dom.tag('pre', function() {
dom.tag('code', function() {
dom.text(self.usage);
});
});
} else {
if (restrict.match(/E/)) {
dom.text('as element:');
dom.code(function() {
dom.text('<');
dom.text(dashCase(self.shortName));
renderParams('\n ', '="', '"');
dom.text('>\n</');
dom.text(dashCase(self.shortName));
dom.text('>');
});
}
if (restrict.match(/A/)) {
var element = self.element || 'ANY';
dom.text('as attribute');
dom.code(function() {
dom.text('<' + element + ' ');
dom.text(dashCase(self.shortName));
renderParams('\n ', '="', '"', true);
dom.text('>\n ...\n');
dom.text('</' + element + '>');
});
}
if (restrict.match(/C/)) {
dom.text('as class');
var element = self.element || 'ANY';
dom.code(function() {
dom.text('<' + element + ' class="');
dom.text(dashCase(self.shortName));
renderParams(' ', ': ', ';', true);
dom.text('">\n ...\n');
dom.text('</' + element + '>');
});
}
}
self.html_usage_directiveInfo(dom);
self.html_usage_parameters(dom);
+3704 -3668
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
1.7.2
1.8.2
+4 -4
View File
@@ -2,9 +2,9 @@
"name": "AngularJS",
"version": "0.0.0",
"dependencies" : {
"testacular" : "canary",
"jasmine-node" : "*",
"q-fs" : "*",
"qq" : "*"
"testacular" : "0.5.9",
"jasmine-node" : "1.2.3",
"q-fs" : "0.1.36",
"qq" : "0.3.5"
}
}
+61 -11
View File
@@ -49,8 +49,7 @@ if ('i' !== 'I'.toLowerCase()) {
function fromCharCode(code) {return String.fromCharCode(code);}
var Error = window.Error,
/** holds major version number for IE or NaN for real browsers */
var /** holds major version number for IE or NaN for real browsers */
msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]),
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
@@ -58,12 +57,32 @@ var Error = window.Error,
push = [].push,
toString = Object.prototype.toString,
_angular = window.angular,
/** @name angular */
angular = window.angular || (window.angular = {}),
angularModule,
nodeName_,
uid = ['0', '0', '0'];
/**
* @ngdoc function
* @name angular.noConflict
* @function
*
* @description
* Restores the previous global value of angular and returns the current instance. Other libraries may already use the
* angular namespace. Or a previous version of angular is already loaded on the page. In these cases you may want to
* restore the previous namespace and keep a reference to angular.
*
* @return {Object} The current angular namespace
*/
function noConflict() {
var a = window.angular;
window.angular = _angular;
return a;
}
/**
* @ngdoc function
* @name angular.forEach
@@ -91,6 +110,30 @@ var Error = window.Error,
* @param {Object=} context Object to become context (`this`) for the iterator function.
* @returns {Object|Array} Reference to `obj`.
*/
/**
* @private
* @param {*} obj
* @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, ...)
*/
function isArrayLike(obj) {
if (!obj || (typeof obj.length !== 'number')) return false;
// We have on object which has length property. Should we treat it as array?
if (typeof obj.hasOwnProperty != 'function' &&
typeof obj.constructor != 'function') {
// This is here for IE8: it is a bogus object treat it as array;
return true;
} else {
return obj instanceof JQLite || // JQLite
(jQuery && obj instanceof jQuery) || // jQuery
toString.call(obj) !== '[object Object]' || // some browser native object
typeof obj.callee === 'function'; // arguments (on IE8 looks like regular obj)
}
}
function forEach(obj, iterator, context) {
var key;
if (obj) {
@@ -102,7 +145,7 @@ function forEach(obj, iterator, context) {
}
} else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context);
} else if (isObject(obj) && isNumber(obj.length)) {
} else if (isArrayLike(obj)) {
for (key = 0; key < obj.length; key++)
iterator.call(context, obj[key], key);
} else {
@@ -147,7 +190,7 @@ function reverseParams(iteratorFn) {
/**
* A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
* characters such as '012ABC'. The reason why we are not using simply a number counter is that
* the number string gets longer over time, and it can also overflow, where as the the nextId
* the number string gets longer over time, and it can also overflow, where as the nextId
* will grow much slower, it is a string, and it will never overflow.
*
* @returns an unique alpha-numeric string
@@ -543,9 +586,7 @@ function copy(source, destination){
} else {
if (source === destination) throw Error("Can't copy equivalent objects or arrays");
if (isArray(source)) {
while(destination.length) {
destination.pop();
}
destination.length = 0;
for ( var i = 0; i < source.length; i++) {
destination.push(copy(source[i]));
}
@@ -756,9 +797,18 @@ function startingTag(element) {
// are not allowed to have children. So we just ignore it.
element.html('');
} catch(e) {}
return jqLite('<div>').append(element).html().
match(/^(<[^>]+>)/)[1].
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
// As Per DOM Standards
var TEXT_NODE = 3;
var elemHtml = jqLite('<div>').append(element).html();
try {
return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
elemHtml.
match(/^(<[^>]+>)/)[1].
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
} catch(e) {
return lowercase(elemHtml);
}
}
@@ -842,7 +892,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
* Use this directive to auto-bootstrap on application. Only
* one directive can be used per HTML document. The directive
* designates the root of the application and is typically placed
* ot the root of the page.
* at the root of the page.
*
* In the example below if the `ngApp` directive would not be placed
* on the `html` element then the document would not be compiled
+2 -1
View File
@@ -48,7 +48,8 @@ function publishExternalAPI(angular){
'isDate': isDate,
'lowercase': lowercase,
'uppercase': uppercase,
'callbacks': {counter: 0}
'callbacks': {counter: 0},
'noConflict': noConflict
});
angularModule = setupModuleLoader(window);
+6 -7
View File
@@ -192,7 +192,7 @@ function annotate(fn) {
* This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
* are supported.
*
* # The `$injector` property
* # The `$inject` property
*
* If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
* services to be injected into the function.
@@ -410,9 +410,10 @@ function createInjector(modulesToLoad) {
decorator: decorator
}
},
providerInjector = createInternalInjector(providerCache, function() {
throw Error("Unknown provider: " + path.join(' <- '));
}),
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function() {
throw Error("Unknown provider: " + path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) {
@@ -489,9 +490,7 @@ function createInjector(modulesToLoad) {
try {
for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
var invokeArgs = invokeQueue[i],
provider = invokeArgs[0] == '$injector'
? providerInjector
: providerInjector.get(invokeArgs[0]);
provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
+4 -4
View File
@@ -4,10 +4,10 @@ var directive = {};
var service = { value: {} };
var DEPENDENCIES = {
'angular.js': 'http://code.angularjs.org/' + angular.version.full + 'angular.min.js',
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-resource.min.js',
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-sanitize.min.js',
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + 'angular-cookies.min.js'
'angular.js': 'http://code.angularjs.org/' + angular.version.full + '/angular.min.js',
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-resource.min.js',
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-sanitize.min.js',
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-cookies.min.js'
};
+4 -3
View File
@@ -353,11 +353,11 @@ var JQLitePrototype = JQLite.prototype = {
// value on get.
//////////////////////////////////////////
var BOOLEAN_ATTR = {};
forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) {
forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
BOOLEAN_ATTR[lowercase(value)] = value;
});
var BOOLEAN_ELEMENTS = {};
forEach('input,select,option,textarea,button,form'.split(','), function(value) {
forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
BOOLEAN_ELEMENTS[uppercase(value)] = true;
});
@@ -659,8 +659,9 @@ forEach({
append: function(element, node) {
forEach(new JQLite(node), function(child){
if (element.nodeType === 1)
if (element.nodeType === 1 || element.nodeType === 11) {
element.appendChild(child);
}
});
},
+1 -1
View File
@@ -237,7 +237,7 @@ function Browser(window, document, $log, $sniffer) {
*/
self.baseHref = function() {
var href = baseElement.attr('href');
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
};
//////////////////////////////////////////////////////////////
+3 -1
View File
@@ -14,7 +14,7 @@
* @returns {object} Newly created cache object with the following set of methods:
*
* - `{object}` `info()` — Returns id, size, and options of cache.
* - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache.
* - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns it.
* - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
* - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
* - `{void}` `removeAll()` — Removes all cached values.
@@ -53,6 +53,8 @@ function $CacheFactoryProvider() {
if (size > capacity) {
this.remove(staleEnd.key);
}
return value;
},
+69 -9
View File
@@ -155,7 +155,8 @@ function $CompileProvider($provide) {
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ',
urlSanitizationWhitelist = /^\s*(https?|ftp|mailto):/;
/**
@@ -209,11 +210,41 @@ function $CompileProvider($provide) {
};
/**
* @ngdoc function
* @name ng.$compileProvider#urlSanitizationWhitelist
* @methodOf ng.$compileProvider
* @function
*
* @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 `urlSanitizationWhitelist` 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 it is 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.urlSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
urlSanitizationWhitelist = regexp;
return this;
}
return urlSanitizationWhitelist;
};
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
'$controller', '$rootScope',
'$controller', '$rootScope', '$document',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
$controller, $rootScope) {
$controller, $rootScope, $document) {
var Attributes = function(element, attr) {
this.$$element = element;
@@ -235,7 +266,8 @@ function $CompileProvider($provide) {
*/
$set: function(key, value, writeAttr, attrName) {
var booleanKey = getBooleanAttrName(this.$$element[0], key),
$$observers = this.$$observers;
$$observers = this.$$observers,
normalizedVal;
if (booleanKey) {
this.$$element.prop(key, value);
@@ -254,6 +286,19 @@ function $CompileProvider($provide) {
}
}
// sanitize a[href] values
if (nodeName_(this.$$element[0]) === 'A' && key === 'href') {
urlSanitizationNode.setAttribute('href', value);
// href property always returns normalized absolute url, so we can match against that
normalizedVal = urlSanitizationNode.href;
if (!normalizedVal.match(urlSanitizationWhitelist)) {
this[key] = value = 'unsafe:' + normalizedVal;
}
}
if (writeAttr !== false) {
if (value === null || value === undefined) {
this.$$element.removeAttr(attrName);
@@ -297,7 +342,8 @@ function $CompileProvider($provide) {
}
};
var startSymbol = $interpolate.startSymbol(),
var urlSanitizationNode = $document[0].createElement('a'),
startSymbol = $interpolate.startSymbol(),
endSymbol = $interpolate.endSymbol(),
denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
? identity
@@ -330,7 +376,14 @@ function $CompileProvider($provide) {
var $linkNode = cloneConnectFn
? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
: $compileNodes;
$linkNode.data('$scope', scope);
// Attach scope only to non-text nodes.
for(var i = 0, ii = $linkNode.length; i<ii; i++) {
var node = $linkNode[i];
if (node.nodeType == 1 /* element */ || node.nodeType == 9 /* document */) {
$linkNode.eq(i).data('$scope', scope);
}
}
safeAddClass($linkNode, 'ng-scope');
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
@@ -420,6 +473,7 @@ function $CompileProvider($provide) {
(function(transcludeFn) {
return function(cloneFn) {
var transcludeScope = scope.$new();
transcludeScope.$$transcluded = true;
return transcludeFn(transcludeScope, cloneFn).
bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
@@ -725,6 +779,8 @@ function $CompileProvider($provide) {
lastValue,
parentGet, parentSet;
scope.$$isolateBindings[scopeName] = mode + attrName;
switch (mode) {
case '@': {
@@ -732,6 +788,10 @@ function $CompileProvider($provide) {
scope[scopeName] = value;
});
attrs.$$observers[attrName].$$scope = parentScope;
if( attrs[attrName] ) {
// If the attribute has been provided then we trigger an interpolation to ensure the value is there for use in the link fn
scope[scopeName] = $interpolate(attrs[attrName])(parentScope);
}
break;
}
@@ -935,7 +995,7 @@ function $CompileProvider($provide) {
}
directives.unshift(derivedSyncDirective);
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn);
afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
@@ -1015,10 +1075,10 @@ function $CompileProvider($provide) {
function addAttrInterpolateDirective(node, directives, value, name) {
var interpolateFn = $interpolate(value, true);
// no interpolation found -> ignore
if (!interpolateFn) return;
directives.push({
priority: 100,
compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
@@ -1030,7 +1090,7 @@ function $CompileProvider($provide) {
interpolateFn = $interpolate(attr[name], true);
}
attr[name] = undefined;
attr[name] = interpolateFn(scope);
($$observers[name] || ($$observers[name] = [])).$$inter = true;
(attr.$$observers && attr.$$observers[name].$$scope || scope).
$watch(interpolateFn, function interpolateFnWatchAction(value) {
+15 -5
View File
@@ -11,15 +11,25 @@
*
* The reasoning for this change is to allow easy creation of action links with `ngClick` directive
* without changing the location or causing page reloads, e.g.:
* <a href="" ng-click="model.$save()">Save</a>
* `<a href="" ng-click="model.$save()">Save</a>`
*/
var htmlAnchorDirective = valueFn({
restrict: 'E',
compile: function(element, attr) {
// turn <a href ng-click="..">link</a> into a link in IE
// but only if it doesn't have name attribute, in which case it's an anchor
if (!attr.href) {
attr.$set('href', '');
if (msie <= 8) {
// turn <a href ng-click="..">link</a> into a stylable link in IE
// but only if it doesn't have name attribute, in which case it's an anchor
if (!attr.href && !attr.name) {
attr.$set('href', '');
}
// add a comment node to anchors to workaround IE bug that causes element content to be reset
// to new attribute content if attribute is updated with value containing @ and element also
// contains value with @
// see issue #1949
element.append(document.createComment('IE fix'));
}
return function(scope, element) {
+35 -3
View File
@@ -66,7 +66,7 @@
it('should execute ng-click but not reload when no href but name specified', function() {
element('#link-5').click();
expect(input('value').val()).toEqual('5');
expect(element('#link-5').attr('href')).toBe('');
expect(element('#link-5').attr('href')).toBe(undefined);
});
it('should only change url when only ng-href', function() {
@@ -272,6 +272,37 @@
* @param {string} expression Angular expression that will be evaluated.
*/
/**
* @ngdoc directive
* @name ng.directive:ngOpen
* @restrict A
*
* @description
* The HTML specs do not require browsers to preserve the special attributes such as open.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce the `ngOpen` directive.
*
* @example
<doc:example>
<doc:source>
Check me check multiple: <input type="checkbox" ng-model="open"><br/>
<details id="details" ng-open="open">
<summary>Show/Hide me</summary>
</details>
</doc:source>
<doc:scenario>
it('should toggle open', function() {
expect(element('#details').prop('open')).toBeFalsy();
input('open').check();
expect(element('#details').prop('open')).toBeTruthy();
});
</doc:scenario>
</doc:example>
*
* @element DETAILS
* @param {string} expression Angular expression that will be evaluated.
*/
var ngAttributeAliasDirectives = {};
@@ -309,8 +340,9 @@ forEach(['src', 'href'], function(attrName) {
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
// to set the property as well to achieve the desired effect
if (msie) element.prop(attrName, value);
// to set the property as well to achieve the desired effect.
// we use attr[attrName] value since $set can sanitize the url.
if (msie) element.prop(attrName, attr[attrName]);
});
}
};
+31 -2
View File
@@ -5,7 +5,8 @@ var nullFormCtrl = {
$addControl: noop,
$removeControl: noop,
$setValidity: noop,
$setDirty: noop
$setDirty: noop,
$setPristine: noop
};
/**
@@ -37,7 +38,8 @@ function FormController(element, attrs) {
var form = this,
parentForm = element.parent().controller('form') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
errors = form.$error = {};
errors = form.$error = {},
controls = [];
// init state
form.$name = attrs.name;
@@ -61,6 +63,8 @@ function FormController(element, attrs) {
}
form.$addControl = function(control) {
controls.push(control);
if (control.$name && !form.hasOwnProperty(control.$name)) {
form[control.$name] = control;
}
@@ -73,6 +77,8 @@ function FormController(element, attrs) {
forEach(errors, function(queue, validationToken) {
form.$setValidity(validationToken, true, control);
});
arrayRemove(controls, control);
};
form.$setValidity = function(validationToken, isValid, control) {
@@ -120,6 +126,29 @@ function FormController(element, attrs) {
parentForm.$setDirty();
};
/**
* @ngdoc function
* @name ng.directive:form.FormController#$setPristine
* @methodOf ng.directive:form.FormController
*
* @description
* Sets the form to its pristine state.
*
* This method can be called to remove the 'ng-dirty' class and set the form to its pristine
* state (ng-pristine class). This method will also propagate to all the controls contained
* in this form.
*
* Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
* saving or resetting it.
*/
form.$setPristine = function () {
element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
form.$dirty = false;
form.$pristine = true;
forEach(controls, function(control) {
control.$setPristine();
});
};
}
+34 -3
View File
@@ -28,6 +28,8 @@ var inputType = {
* patterns defined as scope expressions.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
* @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trimming the
* input.
*
* @example
<doc:example>
@@ -35,12 +37,12 @@ var inputType = {
<script>
function Ctrl($scope) {
$scope.text = 'guest';
$scope.word = /^\w*$/;
$scope.word = /^\s*\w*\s*$/;
}
</script>
<form name="myForm" ng-controller="Ctrl">
Single word: <input type="text" name="input" ng-model="text"
ng-pattern="word" required>
ng-pattern="word" required ng-trim="false">
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.pattern">
@@ -69,6 +71,12 @@ var inputType = {
input('text').enter('hello world');
expect(binding('myForm.input.$valid')).toEqual('false');
});
it('should not be trimmed', function() {
input('text').enter('untrimmed ');
expect(binding('text')).toEqual('untrimmed ');
expect(binding('myForm.input.$valid')).toEqual('true');
});
</doc:scenario>
</doc:example>
*/
@@ -382,7 +390,14 @@ function isEmpty(value) {
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
var listener = function() {
var value = trim(element.val());
var value = element.val();
// By default we will trim the value
// If the attribute ng-trim exists we will avoid trimming
// e.g. <input ng-model="foo" ng-trim="false">
if (toBoolean(attr.ngTrim || 'T')) {
value = trim(value);
}
if (ctrl.$viewValue !== value) {
scope.$apply(function() {
@@ -963,6 +978,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
parentForm.$setValidity(validationErrorKey, isValid, this);
};
/**
* @ngdoc function
* @name ng.directive:ngModel.NgModelController#$setPristine
* @methodOf ng.directive:ngModel.NgModelController
*
* @description
* Sets the control to its pristine state.
*
* This method can be called to remove the 'ng-dirty' class and set the control to its pristine
* state (ng-pristine class).
*/
this.$setPristine = function () {
this.$dirty = false;
this.$pristine = true;
$element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
};
/**
* @ngdoc function
+4 -2
View File
@@ -3,6 +3,7 @@
function classDirective(name, selector) {
name = 'ngClass' + name;
return ngDirective(function(scope, element, attr) {
var oldVal = undefined;
scope.$watch(attr[name], ngClassWatchAction, true);
@@ -26,13 +27,14 @@ function classDirective(name, selector) {
}
function ngClassWatchAction(newVal, oldVal) {
function ngClassWatchAction(newVal) {
if (selector === true || scope.$index % 2 === selector) {
if (oldVal && (newVal !== oldVal)) {
removeClass(oldVal);
}
addClass(newVal);
}
oldVal = newVal;
}
@@ -65,7 +67,7 @@ function classDirective(name, selector) {
*
* The directive won't add duplicate classes if a particular class was already set.
*
* When the expression changes, the previously added classes are removed and only then the classes
* When the expression changes, the previously added classes are removed and only then the
* new classes are added.
*
* @element ANY
+33 -1
View File
@@ -37,7 +37,7 @@
*/
var ngEventDirectives = {};
forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '),
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup'.split(' '),
function(name) {
var directiveName = directiveNormalize('ng-' + name);
ngEventDirectives[directiveName] = ['$parse', function($parse) {
@@ -164,6 +164,38 @@ forEach(
*/
/**
* @ngdoc directive
* @name ng.directive:ngKeydown
*
* @description
* Specify custom behavior on keydown event.
*
* @element ANY
* @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon
* keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
*
* @example
* See {@link ng.directive:ngClick ngClick}
*/
/**
* @ngdoc directive
* @name ng.directive:ngKeyup
*
* @description
* Specify custom behavior on keyup event.
*
* @element ANY
* @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon
* keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.)
*
* @example
* See {@link ng.directive:ngClick ngClick}
*/
/**
* @ngdoc directive
* @name ng.directive:ngSubmit
+30 -18
View File
@@ -20,8 +20,11 @@
* On child elments add:
*
* * `ngSwitchWhen`: the case statement to match against. If match then this
* case will be displayed.
* * `ngSwitchDefault`: the default case when no other casses match.
* case will be displayed. If the same match appears multiple times, all the
* elements will be displayed.
* * `ngSwitchDefault`: the default case when no other case match. If there
* are multiple default cases, all of them will be displayed when no other
* case match.
*
* @example
<doc:example>
@@ -63,27 +66,34 @@ var NG_SWITCH = 'ng-switch';
var ngSwitchDirective = valueFn({
restrict: 'EA',
require: 'ngSwitch',
controller: function ngSwitchController() {
// asks for $scope to fool the BC controller module
controller: ['$scope', function ngSwitchController() {
this.cases = {};
},
}],
link: function(scope, element, attr, ctrl) {
var watchExpr = attr.ngSwitch || attr.on,
selectedTransclude,
selectedElement,
selectedScope;
selectedTranscludes,
selectedElements,
selectedScopes = [];
scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
if (selectedElement) {
selectedScope.$destroy();
selectedElement.remove();
selectedElement = selectedScope = null;
for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
selectedScopes[i].$destroy();
selectedElements[i].remove();
}
if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) {
selectedElements = [];
selectedScopes = [];
if ((selectedTranscludes = ctrl.cases['!' + value] || ctrl.cases['?'])) {
scope.$eval(attr.change);
selectedScope = scope.$new();
selectedTransclude(selectedScope, function(caseElement) {
selectedElement = caseElement;
element.append(caseElement);
forEach(selectedTranscludes, function(selectedTransclude) {
var selectedScope = scope.$new();
selectedScopes.push(selectedScope);
selectedTransclude(selectedScope, function(caseElement) {
selectedElements.push(caseElement);
element.append(caseElement);
});
});
}
});
@@ -96,7 +106,8 @@ var ngSwitchWhenDirective = ngDirective({
require: '^ngSwitch',
compile: function(element, attrs, transclude) {
return function(scope, element, attr, ctrl) {
ctrl.cases['!' + attrs.ngSwitchWhen] = transclude;
ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
ctrl.cases['!' + attrs.ngSwitchWhen].push(transclude);
};
}
});
@@ -107,7 +118,8 @@ var ngSwitchDefaultDirective = ngDirective({
require: '^ngSwitch',
compile: function(element, attrs, transclude) {
return function(scope, element, attr, ctrl) {
ctrl.cases['?'] = transclude;
ctrl.cases['?'] = (ctrl.cases['?'] || []);
ctrl.cases['?'].push(transclude);
};
}
});
+1 -1
View File
@@ -147,7 +147,7 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
if (current.controller) {
locals.$scope = lastScope;
controller = $controller(current.controller, locals);
element.contents().data('$ngControllerController', controller);
element.children().data('$ngControllerController', controller);
}
link(lastScope);
+63 -17
View File
@@ -32,6 +32,22 @@
* called for each element of `array`. The final result is an array of those elements that
* the predicate returned true for.
*
* @param {function(expected, actual)|true|undefined} comparator Comparator which is used in
* determining if the expected value (from the filter expression) and actual value (from
* the object in the array) should be considered a match.
*
* Can be one of:
*
* - `function(expected, actual)`:
* The function will be given the object value and the predicate value to compare and
* should return true if the item should be included in filtered result.
*
* - `true`: A shorthand for `function(expected, actual) { return angular.equals(expected, actual)}`.
* this is essentially strict comparison of expected and actual.
*
* - `false|undefined`: A short hand for a function which will look for a substring match in case
* insensitive way.
*
* @example
<doc:example>
<doc:source>
@@ -39,7 +55,8 @@
{name:'Mary', phone:'800-BIG-MARY'},
{name:'Mike', phone:'555-4321'},
{name:'Adam', phone:'555-5678'},
{name:'Julie', phone:'555-8765'}]"></div>
{name:'Julie', phone:'555-8765'},
{name:'Juliette', phone:'555-5678'}]"></div>
Search: <input ng-model="searchText">
<table id="searchTextResults">
@@ -53,9 +70,10 @@
Any: <input ng-model="search.$"> <br>
Name only <input ng-model="search.name"><br>
Phone only <input ng-model="search.phone"å><br>
Equality <input type="checkbox" ng-model="strict"><br>
<table id="searchObjResults">
<tr><th>Name</th><th>Phone</th><tr>
<tr ng-repeat="friend in friends | filter:search">
<tr ng-repeat="friend in friends | filter:search:strict">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<tr>
@@ -75,14 +93,20 @@
it('should search in specific fields when filtering with a predicate object', function() {
input('search.$').enter('i');
expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
toEqual(['Mary', 'Mike', 'Julie']);
toEqual(['Mary', 'Mike', 'Julie', 'Juliette']);
});
it('should use a equal comparison when comparator is true', function() {
input('search.name').enter('Julie');
input('strict').check();
expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
toEqual(['Julie']);
});
</doc:scenario>
</doc:example>
*/
function filterFilter() {
return function(array, expression) {
if (!(array instanceof Array)) return array;
return function(array, expression, comperator) {
if (!isArray(array)) return array;
var predicates = [];
predicates.check = function(value) {
for (var j = 0; j < predicates.length; j++) {
@@ -92,20 +116,43 @@ function filterFilter() {
}
return true;
};
switch(typeof comperator) {
case "function":
break;
case "boolean":
if(comperator == true) {
comperator = function(obj, text) {
return angular.equals(obj, text);
}
break;
}
default:
comperator = function(obj, text) {
text = (''+text).toLowerCase();
return (''+obj).toLowerCase().indexOf(text) > -1
};
}
var search = function(obj, text){
if (text.charAt(0) === '!') {
if (typeof text == 'string' && text.charAt(0) === '!') {
return !search(obj, text.substr(1));
}
switch (typeof obj) {
case "boolean":
case "number":
case "string":
return ('' + obj).toLowerCase().indexOf(text) > -1;
return comperator(obj, text);
case "object":
for ( var objKey in obj) {
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
return true;
}
switch (typeof text) {
case "object":
return comperator(obj, text);
break;
default:
for ( var objKey in obj) {
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
return true;
}
}
break;
}
return false;
case "array":
@@ -128,19 +175,18 @@ function filterFilter() {
for (var key in expression) {
if (key == '$') {
(function() {
var text = (''+expression[key]).toLowerCase();
if (!text) return;
if (!expression[key]) return;
var path = key
predicates.push(function(value) {
return search(value, text);
return search(value, expression[path]);
});
})();
} else {
(function() {
if (!expression[key]) return;
var path = key;
var text = (''+expression[key]).toLowerCase();
if (!text) return;
predicates.push(function(value) {
return search(getter(value, path), text);
return search(getter(value,path), expression[path]);
});
})();
}
+22 -9
View File
@@ -168,7 +168,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
fraction += '0';
}
if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize);
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
}
parts.push(isNegative ? pattern.negPre : pattern.posPre);
@@ -211,8 +211,12 @@ function dateStrGetter(name, shortForm) {
}
function timeZoneGetter(date) {
var offset = date.getTimezoneOffset();
return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
var zone = -1 * date.getTimezoneOffset();
var paddedZone = (zone >= 0) ? "+" : "";
paddedZone += padNumber(zone / 60, 2) + padNumber(Math.abs(zone % 60), 2);
return paddedZone;
}
function ampmGetter(date, formats) {
@@ -237,6 +241,9 @@ var DATE_FORMATS = {
m: dateGetter('Minutes', 1),
ss: dateGetter('Seconds', 2),
s: dateGetter('Seconds', 1),
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
// we can be just safely rely on using `sss` since we currently don't support single or two digit fractions
sss: dateGetter('Milliseconds', 3),
EEEE: dateStrGetter('Day'),
EEE: dateStrGetter('Day', true),
a: ampmGetter,
@@ -275,6 +282,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
* * `'m'`: Minute in hour (0-59)
* * `'ss'`: Second in minute, padded (00-59)
* * `'s'`: Second in minute (0-59)
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
* * `'a'`: am/pm marker
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
*
@@ -298,7 +306,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
*
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
* number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
* specified in the string input, the time is considered to be in the local timezone.
* @param {string=} format Formatting rules (see Description). If not specified,
* `mediumDate` is used.
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
@@ -318,7 +327,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
expect(binding("1288323623006 | date:'medium'")).
toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
});
@@ -330,18 +339,22 @@ function dateFilter($locale) {
var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
function jsonStringToDate(string){
// 1 2 3 4 5 6 7 8 9 10 11
function jsonStringToDate(string) {
var match;
if (match = string.match(R_ISO8601_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
tzMin = 0,
dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
timeSetter = match[8] ? date.setUTCHours : date.setHours;
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));
dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
timeSetter.call(date, int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
return date;
}
return string;
+50 -33
View File
@@ -6,20 +6,20 @@
* @function
*
* @description
* Creates a new array containing only a specified number of elements in an array. The elements
* are taken from either the beginning or the end of the source array, as specified by the
* value and sign (positive or negative) of `limit`.
* Creates a new array or string containing only a specified number of elements. The elements
* are taken from either the beginning or the end of the source array or string, as specified by
* the value and sign (positive or negative) of `limit`.
*
* Note: This function is used to augment the `Array` type in Angular expressions. See
* {@link ng.$filter} for more information about Angular arrays.
*
* @param {Array} array Source array to be limited.
* @param {string|Number} limit The length of the returned array. If the `limit` number is
* positive, `limit` number of items from the beginning of the source array are copied.
* If the number is negative, `limit` number of items from the end of the source array are
* copied. The `limit` will be trimmed if it exceeds `array.length`
* @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
* elements.
* @param {Array|string} input Source array or string to be limited.
* @param {string|number} limit The length of the returned array or string. If the `limit` number
* is positive, `limit` number of items from the beginning of the source array/string are copied.
* If the number is negative, `limit` number of items from the end of the source array/string
* are copied. The `limit` will be trimmed if it exceeds `array.length`
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
* had less than `limit` elements.
*
* @example
<doc:example>
@@ -27,59 +27,76 @@
<script>
function Ctrl($scope) {
$scope.numbers = [1,2,3,4,5,6,7,8,9];
$scope.limit = 3;
$scope.letters = "abcdefghi";
$scope.numLimit = 3;
$scope.letterLimit = 3;
}
</script>
<div ng-controller="Ctrl">
Limit {{numbers}} to: <input type="integer" ng-model="limit">
<p>Output: {{ numbers | limitTo:limit }}</p>
Limit {{numbers}} to: <input type="integer" ng-model="numLimit">
<p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
Limit {{letters}} to: <input type="integer" ng-model="letterLimit">
<p>Output letters: {{ letters | limitTo:letterLimit }}</p>
</div>
</doc:source>
<doc:scenario>
it('should limit the numer array to first three items', function() {
expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
it('should limit the number array to first three items', function() {
expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3');
expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3');
expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
});
it('should update the output when -3 is entered', function() {
input('limit').enter(-3);
expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
input('numLimit').enter(-3);
input('letterLimit').enter(-3);
expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
});
it('should not exceed the maximum size of input array', function() {
input('limit').enter(100);
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
input('numLimit').enter(100);
input('letterLimit').enter(100);
expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
});
</doc:scenario>
</doc:example>
*/
function limitToFilter(){
return function(array, limit) {
if (!(array instanceof Array)) return array;
return function(input, limit) {
if (!isArray(input) && !isString(input)) return input;
limit = int(limit);
if (isString(input)) {
//NaN check on limit
if (limit) {
return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
} else {
return "";
}
}
var out = [],
i, n;
// check that array is iterable
if (!array || !(array instanceof Array))
return out;
// if abs(limit) exceeds maximum length, trim it
if (limit > array.length)
limit = array.length;
else if (limit < -array.length)
limit = -array.length;
if (limit > input.length)
limit = input.length;
else if (limit < -input.length)
limit = -input.length;
if (limit > 0) {
i = 0;
n = limit;
} else {
i = array.length + limit;
n = array.length;
i = input.length + limit;
n = input.length;
}
for (; i<n; i++) {
out.push(array[i]);
out.push(input[i]);
}
return out;
+1 -1
View File
@@ -89,7 +89,7 @@
orderByFilter.$inject = ['$parse'];
function orderByFilter($parse){
return function(array, sortPredicate, reverseOrder) {
if (!(array instanceof Array)) return array;
if (!isArray(array)) return array;
if (!sortPredicate) return array;
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
sortPredicate = map(sortPredicate, function(predicate){
+82 -20
View File
@@ -29,6 +29,43 @@ function parseHeaders(headers) {
}
var IS_SAME_DOMAIN_URL_MATCH = /^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/;
/**
* Parse a request and location URL and determine whether this is a same-domain request.
*
* @param {string} requestUrl The url of the request.
* @param {string} locationUrl The current browser location url.
* @returns {boolean} Whether the request is for the same domain.
*/
function isSameDomain(requestUrl, locationUrl) {
var match = IS_SAME_DOMAIN_URL_MATCH.exec(requestUrl);
// if requestUrl is relative, the regex does not match.
if (match == null) return true;
var domain1 = {
protocol: match[2],
host: match[4],
port: int(match[6]) || DEFAULT_PORTS[match[2]] || null,
// IE8 sets unmatched groups to '' instead of undefined.
relativeProtocol: match[2] === undefined || match[2] === ''
};
match = URL_MATCH.exec(locationUrl);
var domain2 = {
protocol: match[1],
host: match[3],
port: int(match[5]) || DEFAULT_PORTS[match[1]] || null
};
return (domain1.protocol == domain2.protocol || domain1.relativeProtocol) &&
domain1.host == domain2.host &&
(domain1.port == domain2.port || (domain1.relativeProtocol &&
domain2.port == DEFAULT_PORTS[domain2.protocol]));
}
/**
* Returns a function that provides access to parsed headers.
*
@@ -88,7 +125,7 @@ function $HttpProvider() {
JSON_END = /[\}\]]\s*$/,
PROTECTION_PREFIX = /^\)\]\}',?\n/;
var $config = this.defaults = {
var defaults = this.defaults = {
// transform incoming response data
transformResponse: [function(data) {
if (isString(data)) {
@@ -108,12 +145,14 @@ function $HttpProvider() {
// default headers
headers: {
common: {
'Accept': 'application/json, text/plain, */*',
'X-Requested-With': 'XMLHttpRequest'
'Accept': 'application/json, text/plain, */*'
},
post: {'Content-Type': 'application/json;charset=utf-8'},
put: {'Content-Type': 'application/json;charset=utf-8'}
}
},
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN'
};
var providerResponseInterceptors = this.responseInterceptors = [];
@@ -215,7 +254,6 @@ function $HttpProvider() {
*
* - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
* - `Accept: application/json, text/plain, * / *`
* - `X-Requested-With: XMLHttpRequest`
* - `$httpProvider.defaults.headers.post`: (header defaults for HTTP POST requests)
* - `Content-Type: application/json`
* - `$httpProvider.defaults.headers.put` (header defaults for HTTP PUT requests)
@@ -348,9 +386,10 @@ function $HttpProvider() {
* {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
* an unauthorized site can gain your user's private data. Angular provides following mechanism
* to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
* called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
* runs on your domain could read the cookie, your server can be assured that the XHR came from
* JavaScript running on your domain.
* (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only
* JavaScript that runs on your domain could read the cookie, your server can be assured that
* the XHR came from JavaScript running on your domain. The header will not be set for
* cross-domain requests.
*
* To take advantage of this, your server needs to set a token in a JavaScript readable session
* cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
@@ -360,6 +399,9 @@ function $HttpProvider() {
* up its own tokens). We recommend that the token is a digest of your site's authentication
* cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
*
* The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
* properties of either $httpProvider.defaults, or the per-request config object.
*
*
* @param {object} config Object describing the request to be made and how it should be
* processed. The object has following properties:
@@ -370,6 +412,8 @@ function $HttpProvider() {
* `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
* - **data** `{string|Object}` Data to be sent as the request message data.
* - **headers** `{Object}` Map of strings representing HTTP headers to send to the server.
* - **xsrfHeaderName** `{string}` Name of HTTP header to populate with the XSRF token.
* - **xsrfCookieName** `{string}` Name of cookie containing the XSRF token.
* - **transformRequest** `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version.
@@ -384,6 +428,8 @@ function $HttpProvider() {
* - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
* requests with credentials} for more information.
* - **responseType** - `{string}` - see {@link
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
*
* @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
* standard `then` method and two http specific methods: `success` and `error`. The `then`
@@ -476,10 +522,17 @@ function $HttpProvider() {
function $http(config) {
config.method = uppercase(config.method);
var reqTransformFn = config.transformRequest || $config.transformRequest,
respTransformFn = config.transformResponse || $config.transformResponse,
defHeaders = $config.headers,
reqHeaders = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
var xsrfHeader = {},
xsrfCookieName = config.xsrfCookieName || defaults.xsrfCookieName,
xsrfHeaderName = config.xsrfHeaderName || defaults.xsrfHeaderName,
xsrfToken = isSameDomain(config.url, $browser.url()) ?
$browser.cookies()[xsrfCookieName] : undefined;
xsrfHeader[xsrfHeaderName] = xsrfToken;
var reqTransformFn = config.transformRequest || defaults.transformRequest,
respTransformFn = config.transformResponse || defaults.transformResponse,
defHeaders = defaults.headers,
reqHeaders = extend(xsrfHeader,
defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
promise;
@@ -489,6 +542,10 @@ function $HttpProvider() {
delete reqHeaders['Content-Type'];
}
if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
config.withCredentials = defaults.withCredentials;
}
// send request
promise = sendReq(config, reqData, reqHeaders);
@@ -620,11 +677,11 @@ function $HttpProvider() {
*
* @description
* Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
* default headers as well as request and response transformations.
* default headers, withCredentials as well as request and response transformations.
*
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
*/
$http.defaults = $config;
$http.defaults = defaults;
return $http;
@@ -659,7 +716,7 @@ function $HttpProvider() {
* Makes the request
*
* !!! ACCESSES CLOSURE VARS:
* $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
* $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
*/
function sendReq(config, reqData, reqHeaders) {
var deferred = $q.defer(),
@@ -700,7 +757,7 @@ function $HttpProvider() {
// if we won't have the response in cache, send the request to the backend
if (!cachedResp) {
$httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
config.withCredentials);
config.withCredentials, config.responseType);
}
return promise;
@@ -755,10 +812,15 @@ function $HttpProvider() {
var parts = [];
forEachSorted(params, function(value, key) {
if (value == null || value == undefined) return;
if (isObject(value)) {
value = toJson(value);
}
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
if (!isArray(value)) value = [value];
forEach(value, function(v) {
if (isObject(v)) {
v = toJson(v);
}
parts.push(encodeUriQuery(key) + '=' +
encodeUriQuery(v));
});
});
return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
}
+29 -3
View File
@@ -32,7 +32,7 @@ function $HttpBackendProvider() {
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
// TODO(vojta): fix the signature
return function(method, url, post, callback, headers, timeout, withCredentials) {
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
$browser.$$incOutstandingRequestCount();
url = url || $browser.url();
@@ -65,8 +65,30 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
// always async
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
completeRequest(
callback, status || xhr.status, xhr.responseText, xhr.getAllResponseHeaders());
var responseHeaders = xhr.getAllResponseHeaders();
// TODO(vojta): remove once Firefox 21 gets released.
// begin: workaround to overcome Firefox CORS http response headers bug
// https://bugzilla.mozilla.org/show_bug.cgi?id=608735
// Firefox already patched in nightly. Should land in Firefox 21.
// CORS "simple response headers" http://www.w3.org/TR/cors/
var value,
simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type",
"Expires", "Last-Modified", "Pragma"];
if (!responseHeaders) {
responseHeaders = "";
forEach(simpleHeaders, function (header) {
var value = xhr.getResponseHeader(header);
if (value) {
responseHeaders += header + ": " + value + "\n";
}
});
}
// end of the workaround.
completeRequest(callback, status || xhr.status, xhr.response || xhr.responseText,
responseHeaders);
}
};
@@ -74,6 +96,10 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
xhr.withCredentials = true;
}
if (responseType) {
xhr.responseType = responseType;
}
xhr.send(post || '');
if (timeout > 0) {
+16 -10
View File
@@ -52,7 +52,7 @@ function $InterpolateProvider() {
};
this.$get = ['$parse', function($parse) {
this.$get = ['$parse', '$exceptionHandler', function($parse, $exceptionHandler) {
var startSymbolLength = startSymbol.length,
endSymbolLength = endSymbol.length;
@@ -124,18 +124,24 @@ function $InterpolateProvider() {
if (!mustHaveExpression || hasInterpolation) {
concat.length = length;
fn = function(context) {
for(var i = 0, ii = length, part; i<ii; i++) {
if (typeof (part = parts[i]) == 'function') {
part = part(context);
if (part == null || part == undefined) {
part = '';
} else if (typeof part != 'string') {
part = toJson(part);
try {
for(var i = 0, ii = length, part; i<ii; i++) {
if (typeof (part = parts[i]) == 'function') {
part = part(context);
if (part == null || part == undefined) {
part = '';
} else if (typeof part != 'string') {
part = toJson(part);
}
}
concat[i] = part;
}
concat[i] = part;
return concat.join('');
}
catch(err) {
var newErr = new Error('Error while interpolating: ' + text + '\n' + err.toString());
$exceptionHandler(newErr);
}
return concat.join('');
};
fn.exp = text;
fn.parts = parts;
+45 -1
View File
@@ -33,7 +33,33 @@
</example>
*/
/**
* @ngdoc object
* @name ng.$logProvider
* @description
* Use the `$logProvider` to configure how the application logs messages
*/
function $LogProvider(){
var debug = true,
self = this;
/**
* @ngdoc property
* @name ng.$logProvider#debugEnabled
* @methodOf ng.$logProvider
* @description
* @param {string=} flag enable or disable debug level messages
* @returns {*} current value if used as getter or itself (chaining) if used as setter
*/
this.debugEnabled = function(flag) {
if (isDefined(flag)) {
debug = flag;
return this;
} else {
return debug;
}
};
this.$get = ['$window', function($window){
return {
/**
@@ -74,7 +100,25 @@ function $LogProvider(){
* @description
* Write an error message
*/
error: consoleLog('error')
error: consoleLog('error'),
/**
* @ngdoc method
* @name ng.$log#debug
* @methodOf ng.$log
*
* @description
* Write a debug message
*/
debug: (function () {
var fn = consoleLog('debug');
return function() {
if (debug) {
fn.apply(self, arguments);
}
}
}())
};
function formatError(arg) {
+57 -19
View File
@@ -20,6 +20,8 @@ var OPERATORS = {
'%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
'^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
'=':noop,
'===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
'!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
'==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
'!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
'<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
@@ -70,9 +72,14 @@ function lex(text, csp){
continue;
} else {
var ch2 = ch + peek(),
ch3 = ch2 + peek(2),
fn = OPERATORS[ch],
fn2 = OPERATORS[ch2];
if (fn2) {
fn2 = OPERATORS[ch2],
fn3 = OPERATORS[ch3];
if (fn3) {
tokens.push({index:index, text:ch3, fn:fn3});
index += 3;
} else if (fn2) {
tokens.push({index:index, text:ch2, fn:fn2});
index += 2;
} else if (fn) {
@@ -94,8 +101,9 @@ function lex(text, csp){
return chars.indexOf(lastCh) != -1;
}
function peek() {
return index + 1 < text.length ? text.charAt(index + 1) : false;
function peek(i) {
var num = i || 1;
return index + num < text.length ? text.charAt(index + num) : false;
}
function isNumber(ch) {
return '0' <= ch && ch <= '9';
@@ -295,6 +303,8 @@ function parser(text, json, $filter, csp){
if (tokens.length !== 0) {
throwError("is an unexpected token", tokens[0]);
}
value.literal = !!value.literal;
value.constant = !!value.constant;
return value;
///////////////////////////////////
@@ -342,15 +352,19 @@ function parser(text, json, $filter, csp){
}
function unaryFn(fn, right) {
return function(self, locals) {
return extend(function(self, locals) {
return fn(self, locals, right);
};
}, {
constant:right.constant
});
}
function binaryFn(left, fn, right) {
return function(self, locals) {
return extend(function(self, locals) {
return fn(self, locals, left, right);
};
}, {
constant:left.constant && right.constant
});
}
function statements() {
@@ -456,7 +470,7 @@ function parser(text, json, $filter, csp){
function equality() {
var left = relational();
var token;
if ((token = expect('==','!='))) {
if ((token = expect('==','!=','===','!=='))) {
left = binaryFn(left, token.fn, equality());
}
return left;
@@ -518,6 +532,9 @@ function parser(text, json, $filter, csp){
if (!primary) {
throwError("not a primary expression", token);
}
if (token.json) {
primary.constant = primary.literal = true;
}
}
var next, context;
@@ -606,23 +623,32 @@ function parser(text, json, $filter, csp){
// This is used with json array declaration
function arrayDeclaration () {
var elementFns = [];
var allConstant = true;
if (peekToken().text != ']') {
do {
elementFns.push(expression());
var elementFn = expression();
elementFns.push(elementFn);
if (!elementFn.constant) {
allConstant = false;
}
} while (expect(','));
}
consume(']');
return function(self, locals){
return extend(function(self, locals){
var array = [];
for ( var i = 0; i < elementFns.length; i++) {
array.push(elementFns[i](self, locals));
}
return array;
};
}, {
literal:true,
constant:allConstant
});
}
function object () {
var keyValues = [];
var allConstant = true;
if (peekToken().text != '}') {
do {
var token = expect(),
@@ -630,10 +656,13 @@ function parser(text, json, $filter, csp){
consume(":");
var value = expression();
keyValues.push({key:key, value:value});
if (!value.constant) {
allConstant = false;
}
} while (expect(','));
}
consume('}');
return function(self, locals){
return extend(function(self, locals){
var object = {};
for ( var i = 0; i < keyValues.length; i++) {
var keyValue = keyValues[i];
@@ -641,7 +670,10 @@ function parser(text, json, $filter, csp){
object[keyValue.key] = value;
}
return object;
};
}, {
literal:true,
constant:allConstant
});
}
}
@@ -840,12 +872,18 @@ function getterFn(path, csp) {
* @param {string} expression String expression to compile.
* @returns {function(context, locals)} a function which represents the compiled expression:
*
* * `context`: an object against which any expressions embedded in the strings are evaluated
* against (Topically a scope object).
* * `locals`: local variables context object, useful for overriding values in `context`.
* * `context` `{object}` an object against which any expressions embedded in the strings
* are evaluated against (tipically a scope object).
* * `locals` `{object=}` local variables context object, useful for overriding values in
* `context`.
*
* The return function also has an `assign` property, if the expression is assignable, which
* allows one to set values to expressions.
* The returned function also has the following properties:
* * `literal` `{boolean}` whether the expression's top-level node is a JavaScript
* literal.
* * `constant` `{boolean}` whether the expression is made entirely of JavaScript
* constant literals.
* * `assign` `{?function(context, value)}` if the expression is assignable, this will be
* set to a function to change its value on the given context.
*
*/
function $ParseProvider() {
+9 -9
View File
@@ -12,7 +12,7 @@
* interface for interacting with an object that represents the result of an action that is
* performed asynchronously, and may or may not be finished at any given point in time.
*
* From the perspective of dealing with error handling, deferred and promise apis are to
* From the perspective of dealing with error handling, deferred and promise APIs are to
* asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
*
* <pre>
@@ -47,7 +47,7 @@
*
* At first it might not be obvious why this extra complexity is worth the trouble. The payoff
* comes in the way of
* [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
* [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
*
* Additionally the promise api allows for composition that is very hard to do with the
* traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
@@ -59,7 +59,7 @@
*
* A new instance of deferred is constructed by calling `$q.defer()`.
*
* The purpose of the deferred object is to expose the associated Promise instance as well as apis
* The purpose of the deferred object is to expose the associated Promise instance as well as APIs
* that can be used for signaling the successful or unsuccessful completion of the task.
*
* **Methods**
@@ -102,7 +102,7 @@
* return result + 1;
* });
*
* // promiseB will be resolved immediately after promiseA is resolved and it's value will be
* // promiseB will be resolved immediately after promiseA is resolved and its value will be
* // the result of promiseA incremented by 1
* </pre>
*
@@ -127,7 +127,7 @@
* # Testing
*
* <pre>
* it('should simulate promise', inject(function($q, $rootSCope) {
* it('should simulate promise', inject(function($q, $rootScope) {
* var deferred = $q.defer();
* var promise = deferred.promise;
* var resolvedValue;
@@ -136,7 +136,7 @@
* expect(resolvedValue).toBeUndefined();
*
* // Simulate resolving of promise
* defered.resolve(123);
* deferred.resolve(123);
* // Note that the 'then' function does not get called synchronously.
* // This is because we want the promise API to always be async, whether or not
* // it got called synchronously or asynchronously.
@@ -312,12 +312,12 @@ function qFactory(nextTick, exceptionHandler) {
* @methodOf ng.$q
* @description
* Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
* This is useful when you are dealing with on object that might or might not be a promise, or if
* This is useful when you are dealing with an object that might or might not be a promise, or if
* the promise comes from a source that can't be trusted.
*
* @param {*} value Value or a promise
* @returns {Promise} Returns a single promise that will be resolved with an array of values,
* each value coresponding to the promise at the same index in the `promises` array. If any of
* each value corresponding to the promise at the same index in the `promises` array. If any of
* the promises is resolved with a rejection, this resulting promise will be resolved with the
* same rejection.
*/
@@ -379,7 +379,7 @@ function qFactory(nextTick, exceptionHandler) {
*
* @param {Array.<Promise>} promises An array of promises.
* @returns {Promise} Returns a single promise that will be resolved with an array of values,
* each value coresponding to the promise at the same index in the `promises` array. If any of
* each value corresponding to the promise at the same index in the `promises` array. If any of
* the promises is resolved with a rejection, this resulting promise will be resolved with the
* same rejection.
*/
+25 -16
View File
@@ -137,6 +137,7 @@ function $RootScopeProvider(){
this.$$destroyed = false;
this.$$asyncQueue = [];
this.$$listeners = {};
this.$$isolateBindings = {};
}
/**
@@ -196,7 +197,6 @@ function $RootScopeProvider(){
child['this'] = child;
child.$$listeners = {};
child.$parent = this;
child.$$asyncQueue = [];
child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
child.$$prevSibling = this.$$childTail;
if (this.$$childHead) {
@@ -300,6 +300,14 @@ function $RootScopeProvider(){
watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
}
if (typeof watchExp == 'string' && get.constant) {
var originalFn = watcher.fn;
watcher.fn = function(newVal, oldVal, scope) {
originalFn.call(this, newVal, oldVal, scope);
arrayRemove(array, watcher);
};
}
if (!array) {
array = scope.$$watchers = [];
}
@@ -363,7 +371,7 @@ function $RootScopeProvider(){
$digest: function() {
var watch, value, last,
watchers,
asyncQueue,
asyncQueue = this.$$asyncQueue,
length,
dirty, ttl = TTL,
next, current, target = this,
@@ -372,18 +380,19 @@ function $RootScopeProvider(){
beginPhase('$digest');
do {
do { // "while dirty" loop
dirty = false;
current = target;
do {
asyncQueue = current.$$asyncQueue;
while(asyncQueue.length) {
try {
current.$eval(asyncQueue.shift());
} catch (e) {
$exceptionHandler(e);
}
while(asyncQueue.length) {
try {
current.$eval(asyncQueue.shift());
} catch (e) {
$exceptionHandler(e);
}
}
do { // "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
// process our watches
length = watchers.length;
@@ -618,10 +627,6 @@ function $RootScopeProvider(){
* Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
* event life cycle.
*
* @param {string} name Event name to listen on.
* @param {function(event)} listener Function to call when the event is emitted.
* @returns {function()} Returns a deregistration function for this listener.
*
* The event listener function format is: `function(event, args...)`. The `event` object
* passed into the listener has the following attributes:
*
@@ -632,6 +637,10 @@ function $RootScopeProvider(){
* propagation (available only for events that were `$emit`-ed).
* - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
* - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
*
* @param {string} name Event name to listen on.
* @param {function(event, args...)} listener Function to call when the event is emitted.
* @returns {function()} Returns a deregistration function for this listener.
*/
$on: function(name, listener) {
var namedListeners = this.$$listeners[name];
@@ -809,7 +818,7 @@ function $RootScopeProvider(){
/**
* function used as an initial value for watchers.
* because it's uniqueue we can easily tell it apart from other values
* because it's unique we can easily tell it apart from other values
*/
function initWatchVal() {}
}];
+51 -14
View File
@@ -23,9 +23,18 @@ function $RouteProvider(){
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
* route definition.
*
* `path` can contain named groups starting with a colon (`:name`). All characters up to the
* next slash are matched and stored in `$routeParams` under the given `name` when the route
* matches.
* * `path` can contain named groups starting with a colon (`:name`). All characters up
* to the next slash are matched and stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain named groups starting with a star (`*name`). All characters are
* eagerly stored in `$routeParams` under the given `name` when the route matches.
*
* For example, routes like `/color/:color/largecode/*largecode/edit` will match
* `/color/brown/largecode/code/with/slashs/edit` and extract:
*
* * `color: brown`
* * `largecode: code/with/slashs`.
*
*
* @param {Object} route Mapping information to be assigned to `$route.current` on route
* match.
@@ -35,12 +44,24 @@ function $RouteProvider(){
* - `controller` `{(string|function()=}` Controller fn that should be associated with newly
* created scope or the name of a {@link angular.Module#controller registered controller}
* if passed as a string.
* - `template` `{string=}` html template as a string that should be used by
* {@link ng.directive:ngView ngView} or
* - `template` `{string=|function()=}` html template as a string or function that returns
* an html template as a string which should be used by {@link ng.directive:ngView ngView} or
* {@link ng.directive:ngInclude ngInclude} directives.
* this property takes precedence over `templateUrl`.
* - `templateUrl` `{string=}` path to an html template that should be used by
* {@link ng.directive:ngView ngView}.
* This property takes precedence over `templateUrl`.
*
* If `template` is a function, it will be called with the following parameters:
*
* - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route
*
* - `templateUrl` `{string=|function()=}` path or function that returns a path to an html
* template that should be used by {@link ng.directive:ngView ngView}.
*
* If `templateUrl` is a function, it will be called with the following parameters:
*
* - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route
*
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
* be injected into the controller. If any of these dependencies are promises, they will be
* resolved and converted to a value before the controller is instantiated and the
@@ -329,12 +350,12 @@ function $RouteProvider(){
// regex only once and then reuse it
// Escape regexp special characters.
when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$';
when = '^' + when.replace(/[-\/\\^$:*+?.()|[\]{}]/g, "\\$&") + '$';
var regex = '',
params = [],
dst = {};
var re = /:(\w+)/g,
var re = /\\([:*])(\w+)/g,
paramMatch,
lastMatchedIndex = 0;
@@ -342,8 +363,15 @@ function $RouteProvider(){
// Find each :param in `when` and replace it with a capturing group.
// Append all other sections of when unchanged.
regex += when.slice(lastMatchedIndex, paramMatch.index);
regex += '([^\\/]*)';
params.push(paramMatch[1]);
switch(paramMatch[1]) {
case ':':
regex += '([^\\/]*)';
break;
case '*':
regex += '(.*)';
break;
}
params.push(paramMatch[2]);
lastMatchedIndex = re.lastIndex;
}
// Append trailing path part.
@@ -395,9 +423,18 @@ function $RouteProvider(){
values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
});
if (isDefined(template = next.template)) {
if (isFunction(template)) {
template = template(next.params);
}
} else if (isDefined(template = next.templateUrl)) {
template = $http.get(template, {cache: $templateCache}).
then(function(response) { return response.data; });
if (isFunction(template)) {
template = template(next.params);
}
if (isDefined(template)) {
next.loadedTemplateUrl = template;
template = $http.get(template, {cache: $templateCache}).
then(function(response) { return response.data; });
}
}
if (isDefined(template)) {
keys.push('$template');
+7 -6
View File
@@ -5,6 +5,7 @@
*
* @name ng.$sniffer
* @requires $window
* @requires $document
*
* @property {boolean} history Does the browser support html5 history api ?
* @property {boolean} hashchange Does the browser support hashchange event ?
@@ -13,9 +14,10 @@
* This is very simple implementation of testing browser's features.
*/
function $SnifferProvider() {
this.$get = ['$window', function($window) {
this.$get = ['$window', '$document', function($window, $document) {
var eventSupport = {},
android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]);
android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]),
document = $document[0];
return {
// Android has history.pushState, but it does not update location correctly
@@ -25,7 +27,7 @@ function $SnifferProvider() {
history: !!($window.history && $window.history.pushState && !(android < 4)),
hashchange: 'onhashchange' in $window &&
// IE8 compatible mode lies
(!$window.document.documentMode || $window.document.documentMode > 7),
(!document.documentMode || document.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
@@ -33,14 +35,13 @@ function $SnifferProvider() {
if (event == 'input' && msie == 9) return false;
if (isUndefined(eventSupport[event])) {
var divElm = $window.document.createElement('div');
var divElm = document.createElement('div');
eventSupport[event] = 'on' + event in divElm;
}
return eventSupport[event];
},
// TODO(i): currently there is no way to feature detect CSP without triggering alerts
csp: false
csp: document.securityPolicy ? document.securityPolicy.isActive : false
};
}];
}
+12
View File
@@ -19,6 +19,18 @@ angular.module('ngCookies', ['ng']).
* this object, new cookies are created/deleted at the end of current $eval.
*
* @example
<doc:example>
<doc:source>
<script>
function ExampleController($cookies) {
// Retrieving a cookie
var favoriteCookie = $cookies.myFavorite;
// Setting a cookie
$cookies.myFavorite = 'oatmeal';
}
</script>
</doc:source>
</doc:example>
*/
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
var cookies = {},
+67 -36
View File
@@ -456,6 +456,7 @@ angular.mock.$LogProvider = function() {
* newYearInBratislava.getDate() => 1;
* newYearInBratislava.getHours() => 0;
* newYearInBratislava.getMinutes() => 0;
* newYearInBratislava.getSeconds() => 0;
* </pre>
*
*/
@@ -512,6 +513,10 @@ angular.mock.$LogProvider = function() {
return self.date.getSeconds();
};
self.getMilliseconds = function() {
return self.date.getMilliseconds();
};
self.getTimezoneOffset = function() {
return offset * 60;
};
@@ -562,7 +567,7 @@ angular.mock.$LogProvider = function() {
}
//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
var unimplementedMethods = ['getUTCDay',
'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
@@ -1352,17 +1357,49 @@ function MockXhr() {
* @description
*
* This service is just a simple decorator for {@link ng.$timeout $timeout} service
* that adds a "flush" method.
*/
* that adds a "flush" and "verifyNoPendingTasks" methods.
*/
/**
* @ngdoc method
* @name ngMock.$timeout#flush
* @methodOf ngMock.$timeout
* @description
*
* Flushes the queue of pending tasks.
*/
angular.mock.$TimeoutDecorator = function($delegate, $browser) {
/**
* @ngdoc method
* @name ngMock.$timeout#flush
* @methodOf ngMock.$timeout
* @description
*
* Flushes the queue of pending tasks.
*/
$delegate.flush = function() {
$browser.defer.flush();
};
/**
* @ngdoc method
* @name ngMock.$timeout#verifyNoPendingTasks
* @methodOf ngMock.$timeout
* @description
*
* Verifies that there are no pending tasks that need to be flushed.
*/
$delegate.verifyNoPendingTasks = function() {
if ($browser.deferredFns.length) {
throw Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
formatPendingTasksAsString($browser.deferredFns));
}
};
function formatPendingTasksAsString(tasks) {
var result = [];
angular.forEach(tasks, function(task) {
result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}');
});
return result.join(', ');
}
return $delegate;
};
/**
*
@@ -1388,15 +1425,9 @@ angular.module('ngMock', ['ng']).provider({
$httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(function($provide) {
$provide.decorator('$timeout', function($delegate, $browser) {
$delegate.flush = function() {
$browser.defer.flush();
};
return $delegate;
});
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
});
/**
* @ngdoc overview
* @name ngMockE2E
@@ -1610,14 +1641,20 @@ window.jstestdriver && (function(window) {
})(window);
window.jasmine && (function(window) {
(window.jasmine || window.mocha) && (function(window) {
var currentSpec = null;
beforeEach(function() {
currentSpec = this;
});
afterEach(function() {
var spec = getCurrentSpec();
var injector = spec.$injector;
var injector = currentSpec.$injector;
spec.$injector = null;
spec.$modules = null;
currentSpec.$injector = null;
currentSpec.$modules = null;
currentSpec = null;
if (injector) {
injector.get('$rootElement').unbind();
@@ -1639,13 +1676,8 @@ window.jasmine && (function(window) {
angular.callbacks.counter = 0;
});
function getCurrentSpec() {
return jasmine.getEnv().currentSpec;
}
function isSpecRunning() {
var spec = getCurrentSpec();
return spec && spec.queue.running;
return currentSpec && currentSpec.queue.running;
}
/**
@@ -1670,11 +1702,10 @@ window.jasmine && (function(window) {
return isSpecRunning() ? workFn() : workFn;
/////////////////////
function workFn() {
var spec = getCurrentSpec();
if (spec.$injector) {
if (currentSpec.$injector) {
throw Error('Injector already created, can not register a module!');
} else {
var modules = spec.$modules || (spec.$modules = []);
var modules = currentSpec.$modules || (currentSpec.$modules = []);
angular.forEach(moduleFns, function(module) {
modules.push(module);
});
@@ -1741,13 +1772,13 @@ window.jasmine && (function(window) {
return isSpecRunning() ? workFn() : workFn;
/////////////////////
function workFn() {
var spec = getCurrentSpec();
var modules = spec.$modules || [];
var modules = currentSpec.$modules || [];
modules.unshift('ngMock');
modules.unshift('ng');
var injector = spec.$injector;
var injector = currentSpec.$injector;
if (!injector) {
injector = spec.$injector = angular.injector(modules);
injector = currentSpec.$injector = angular.injector(modules);
}
for(var i = 0, ii = blockFns.length; i < ii; i++) {
try {
+104 -42
View File
@@ -19,12 +19,13 @@
* the need to interact with the low level {@link ng.$http $http} service.
*
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
* `/user/:username`. If you are using a URL with a port number (e.g.
* `/user/:username`. If you are using a URL with a port number (e.g.
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
* number, like this: `$resource('http://example.com\\:8080/api')`.
*
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
* `actions` methods.
* `actions` methods. If any of the parameter value is a function, it will be executed every time
* when a param value needs to be obtained for a request (unless the param was overriden).
*
* Each key value in the parameter object is first bound to url template if present and then any
* excess keys are appended to the url search query after the `?`.
@@ -36,21 +37,40 @@
* the data object (useful for non-GET operations).
*
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
* default set of resource actions. The declaration should be created in the following format:
* default set of resource actions. The declaration should be created in the format of {@link
* ng.$http#Parameters $http.config}:
*
* {action1: {method:?, params:?, isArray:?},
* action2: {method:?, params:?, isArray:?},
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
* action2: {method:?, params:?, isArray:?, headers:?, ...},
* ...}
*
* Where:
*
* - `action` {string} The name of action. This name becomes the name of the method on your
* - **`action`** {string} The name of action. This name becomes the name of the method on your
* resource object.
* - `method` {string} HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
* and `JSONP`
* - `params` {object=} Optional set of pre-bound parameters for this action.
* - isArray {boolean=} If true then the returned object for this action is an array, see
* - **`method`** {string} HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
* and `JSONP`.
* - **`params`** {Object=} Optional set of pre-bound parameters for this action. If any of the
* parameter value is a function, it will be executed every time when a param value needs to be
* obtained for a request (unless the param was overriden).
* - **`isArray`** {boolean=} If true then the returned object for this action is an array, see
* `returns` section.
* - **`transformRequest`** `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version.
* - **`transformResponse`** `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* response body and headers and returns its transformed (typically deserialized) version.
* - **`cache`** `{boolean|Cache}` If true, a default $http cache will be used to cache the
* GET request, otherwise if a cache instance built with
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
* caching.
* - **`timeout`** `{number}` timeout in milliseconds.
* - **`withCredentials`** - `{boolean}` - whether to to set the `withCredentials` flag on the
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
* requests with credentials} for more information.
* - **`responseType`** - `{string}` - see {@link
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
*
* @returns {Object} A resource "class" object with methods for the default set of resource actions
* optionally extended with custom `actions`. The default set contains these actions:
@@ -63,9 +83,9 @@
*
* Calling these methods invoke an {@link ng.$http} with the specified http method,
* destination and parameters. When the data is returned from the server then the object is an
* instance of the resource class `save`, `remove` and `delete` actions are available on it as
* methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
* update, delete) on server-side data like this:
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
* read, update, delete) on server-side data like this:
* <pre>
var User = $resource('/user/:userId', {userId:'@id'});
var user = User.get({userId:123}, function() {
@@ -90,6 +110,24 @@
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
*
*
* The Resource instances and collection have these additional properties:
*
* - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying
* {@link ng.$http $http} call.
*
* The success callback for the `$then` method will be resolved if the underlying `$http` requests
* succeeds.
*
* The success callback is called with a single object which is the {@link ng.$http http response}
* object extended with a new property `resource`. This `resource` property is a reference to the
* result of the resource action — resource object or array of resources.
*
* The error callback is called with the {@link ng.$http http response} object when an http
* error occurs.
*
* - `$resolved`: true if the promise has been resolved (either with success or rejection);
* Knowing if the Resource has been resolved is useful in data-binding.
*
* @example
*
* # Credit card resource
@@ -132,7 +170,7 @@
* The object returned from this function execution is a resource "class" which has "static" method
* for each action in the definition.
*
* Calling these methods invoke `$http` on the `url` template with the given `method` and `params`.
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`.
* When the data is returned from the server then the object is an instance of the resource type and
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
* operations (create, read, update, delete) on server-side data.
@@ -145,9 +183,9 @@
});
</pre>
*
* It's worth noting that the success callback for `get`, `query` and other method gets passed
* in the response that came from the server as well as $http header getter function, so one
* could rewrite the above example and get access to http headers as:
* It's worth noting that the success callback for `get`, `query` and other method gets passed
* in the response that came from the server as well as $http header getter function, so one
* could rewrite the above example and get access to http headers as:
*
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
@@ -270,7 +308,7 @@ angular.module('ngResource', ['ng']).
this.defaults = defaults || {};
var urlParams = this.urlParams = {};
forEach(template.split(/\W/), function(param){
if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) {
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) {
urlParams[param] = true;
}
});
@@ -278,7 +316,7 @@ angular.module('ngResource', ['ng']).
}
Route.prototype = {
url: function(params) {
setUrlParams: function(config, params) {
var self = this,
url = this.template,
val,
@@ -301,16 +339,17 @@ angular.module('ngResource', ['ng']).
});
}
});
url = url.replace(/\/?#$/, '');
var query = [];
// set the url
config.url = url.replace(/\/?#$/, '').replace(/\/*$/, '');
// set params - delegate param encoding to $http
forEach(params, function(value, key){
if (!self.urlParams[key]) {
query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
config.params = config.params || {};
config.params[key] = value;
}
});
query.sort();
url = url.replace(/\/*$/, '');
return url + (query.length ? '?' + query.join('&') : '');
}
};
@@ -324,6 +363,7 @@ angular.module('ngResource', ['ng']).
var ids = {};
actionParams = extend({}, paramDefaults, actionParams);
forEach(actionParams, function(value, key){
if (isFunction(value)) { value = value(); }
ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
});
return ids;
@@ -341,6 +381,8 @@ angular.module('ngResource', ['ng']).
var data;
var success = noop;
var error = null;
var promise;
switch(arguments.length) {
case 4:
error = a4;
@@ -376,25 +418,45 @@ angular.module('ngResource', ['ng']).
}
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
$http({
method: action.method,
url: route.url(extend({}, extractParams(data, action.params || {}), params)),
data: data
}).then(function(response) {
var data = response.data;
var httpConfig = {},
promise;
if (data) {
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
value.push(new Resource(item));
});
} else {
copy(data, value);
}
forEach(action, function(value, key) {
if (key != 'params' && key != 'isArray' ) {
httpConfig[key] = copy(value);
}
});
httpConfig.data = data;
route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params));
function markResolved() { value.$resolved = true; }
promise = $http(httpConfig);
value.$resolved = false;
promise.then(markResolved, markResolved);
value.$then = promise.then(function(response) {
var data = response.data;
var then = value.$then, resolved = value.$resolved;
if (data) {
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
value.push(new Resource(item));
});
} else {
copy(data, value);
value.$then = then;
value.$resolved = resolved;
}
(success||noop)(value, response.headers);
}, error);
}
(success||noop)(value, response.headers);
response.resource = value;
return response;
}, error).then;
return value;
};
+23 -2
View File
@@ -8,6 +8,7 @@
* plain email address links.
*
* @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
* @returns {string} Html-linkified text.
*
* @usage
@@ -24,6 +25,7 @@
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}
</script>
<div ng-controller="Ctrl">
@@ -43,6 +45,15 @@
<div ng-bind-html="snippet | linky"></div>
</td>
</tr>
<tr id="linky-target">
<td>linky target</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
</td>
</tr>
<tr id="escaped-html">
<td>no filter</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
@@ -75,6 +86,11 @@
toBe('new <a href="http://link">http://link</a>.');
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
});
it('should work with the target property', function() {
expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
});
</doc:scenario>
</doc:example>
*/
@@ -82,7 +98,7 @@ angular.module('ngSanitize').filter('linky', function() {
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
MAILTO_REGEXP = /^mailto:/;
return function(text) {
return function(text, target) {
if (!text) return text;
var match;
var raw = text;
@@ -91,6 +107,10 @@ angular.module('ngSanitize').filter('linky', function() {
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];
@@ -98,7 +118,8 @@ angular.module('ngSanitize').filter('linky', function() {
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
writer.chars(raw.substr(0, i));
writer.start('a', {href:url});
properties.href = url;
writer.start('a', properties);
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
writer.end('a');
raw = raw.substring(i + match[0].length);
+1 -1
View File
@@ -123,7 +123,7 @@ var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:
BEGING_END_TAGE_REGEXP = /^<\s*\//,
COMMENT_REGEXP = /<!--(.*?)-->/g,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/,
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/,
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
+27
View File
@@ -298,6 +298,8 @@ angular.scenario.dsl('select', function() {
option = select.find('option:contains("' + value + '")');
if (option.length) {
select.val(option.val());
} else {
return done("option '" + value + "' not found");
}
}
select.trigger('change');
@@ -325,6 +327,7 @@ angular.scenario.dsl('select', function() {
* Usage:
* element(selector, label).count() get the number of elements that match selector
* element(selector, label).click() clicks an element
* element(selector, label).mouseover() mouseover an element
* element(selector, label).query(fn) executes fn(selectedElements, done)
* element(selector, label).{method}() gets the value (as defined by jQuery, ex. val)
* element(selector, label).{method}(value) sets the value (as defined by jQuery, ex. val)
@@ -365,6 +368,30 @@ angular.scenario.dsl('element', function() {
});
};
chain.dblclick = function() {
return this.addFutureAction("element '" + this.label + "' dblclick", function($window, $document, done) {
var elements = $document.elements();
var href = elements.attr('href');
var eventProcessDefault = elements.trigger('dblclick')[0];
if (href && elements[0].nodeName.toUpperCase() === 'A' && eventProcessDefault) {
this.application.navigateTo(href, function() {
done();
}, done);
} else {
done();
}
});
};
chain.mouseover = function() {
return this.addFutureAction("element '" + this.label + "' mouseover", function($window, $document, done) {
var elements = $document.elements();
elements.trigger('mouseover');
done();
});
};
chain.query = function(fn) {
return this.addFutureAction('element ' + this.label + ' custom query', function($window, $document, done) {
fn.call(this, $document.elements(), done);
@@ -1,177 +0,0 @@
'use strict';
/**
* JSTestDriver adapter for angular scenario tests
*
* Example of jsTestDriver.conf for running scenario tests with JSTD:
<pre>
server: http://localhost:9877
load:
- lib/angular-scenario.js
- lib/jstd-scenario-adapter-config.js
- lib/jstd-scenario-adapter.js
# your test files go here #
proxy:
- {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
</pre>
*
* For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
* Note the order of files - it's important !
*
* Example of jstd-scenario-adapter-config.js
<pre>
var jstdScenarioAdapter = {
relativeUrlPrefix: '/your-prefix/'
};
</pre>
*
* Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
* You have to configure this to work together with JSTD proxy.
*
* Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
* Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
* That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
*/
/**
* Custom type of test case
*
* @const
* @see jstestdriver.TestCaseInfo
*/
var SCENARIO_TYPE = 'scenario';
/**
* Plugin for JSTestDriver
* Connection point between scenario's jstd output and jstestdriver.
*
* @see jstestdriver.PluginRegistrar
*/
function JstdPlugin() {
var nop = function() {};
this.reportResult = nop;
this.reportEnd = nop;
this.runScenario = nop;
this.name = 'Angular Scenario Adapter';
/**
* Called for each JSTD TestCase
*
* Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
* Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
*
* @param {jstestdriver.TestRunConfiguration} configuration
* @param {Function} onTestDone
* @param {Function} onAllTestsComplete
* @returns {boolean} True if this type of test is handled by this plugin, false otherwise
*/
this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
this.reportResult = onTestDone;
this.reportEnd = onAllTestsComplete;
this.runScenario();
return true;
};
this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
testRunsConfiguration.push(
new jstestdriver.TestRunConfiguration(
new jstestdriver.TestCaseInfo(
'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
return true;
};
}
/**
* Singleton instance of the plugin
* Accessed using closure by:
* - jstd output (reports to this plugin)
* - initScenarioAdapter (register the plugin to jstd)
*/
var plugin = new JstdPlugin();
/**
* Initialise scenario jstd-adapter
* (only if jstestdriver is defined)
*
* @param {Object} jstestdriver Undefined when run from browser (without jstd)
* @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
* @param {Object=} config Configuration object, supported properties:
* - relativeUrlPrefix: prefix for all relative links when navigateTo()
*/
function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
if (jstestdriver) {
// create and register ScenarioPlugin
jstestdriver.pluginRegistrar.register(plugin);
plugin.runScenario = initScenarioAndRun;
/**
* HACK (angular.scenario.Application.navigateTo)
*
* We need to navigate to relative urls when running from browser (without JSTD),
* because we want to allow running scenario tests without creating its own virtual host.
* For example: http://angular.local/build/docs/docs-scenario.html
*
* On the other hand, when running with JSTD, we need to navigate to absolute urls,
* because of JSTD proxy. (proxy, because of same domain policy)
*
* So this hack is applied only if running with JSTD and change all relative urls to absolute.
*/
var appProto = angular.scenario.Application.prototype,
navigateTo = appProto.navigateTo,
relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
appProto.navigateTo = function(url, loadFn, errorFn) {
if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
url != 'about:blank' && !url.match(/^https?/)) {
url = relativeUrlPrefix + url;
}
return navigateTo.call(this, url, loadFn, errorFn);
};
}
}
/**
* Builds proper TestResult object from given model spec
*
* TODO(vojta) report error details
*
* @param {angular.scenario.ObjectModel.Spec} spec
* @returns {jstestdriver.TestResult}
*/
function createTestResultFromSpec(spec) {
var map = {
success: 'PASSED',
error: 'ERROR',
failure: 'FAILED'
};
return new jstestdriver.TestResult(
spec.fullDefinitionName,
spec.name,
jstestdriver.TestResult.RESULT[map[spec.status]],
spec.error || '',
spec.line || '',
spec.duration);
}
/**
* Generates JSTD output (jstestdriver.TestResult)
*/
angular.scenario.output('jstd', function(context, runner, model) {
model.on('SpecEnd', function(spec) {
plugin.reportResult(createTestResultFromSpec(spec));
});
model.on('RunnerEnd', function() {
plugin.reportEnd();
});
});
@@ -1,6 +0,0 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window) {
@@ -1,2 +0,0 @@
initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
})(window);
+1 -1
View File
@@ -43,7 +43,7 @@ angular.scenario.output('xml', function(context, runner, model) {
if (step.error) {
var error = $('<error></error>');
stepContext.append(error);
error.text(formatException(stepContext.error));
error.text(formatException(step.error));
}
});
});
+123 -3
View File
@@ -31,7 +31,7 @@ describe('angular', function() {
expect(copy(date) === date).toBeFalsy();
});
it("should copy array", function() {
it("should deeply copy an array into an existing array", function() {
var src = [1, {name:"value"}];
var dst = [{key:"v"}];
expect(copy(src, dst)).toBe(dst);
@@ -40,6 +40,15 @@ describe('angular', function() {
expect(dst[1]).not.toBe(src[1]);
});
it("should deeply copy an array into a new array", function() {
var src = [1, {name:"value"}];
var dst = copy(src);
expect(src).toEqual([1, {name:"value"}]);
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
expect(dst[1]).not.toBe(src[1]);
});
it('should copy empty array', function() {
var src = [];
var dst = [{key: "v"}];
@@ -47,7 +56,7 @@ describe('angular', function() {
expect(dst).toEqual([]);
});
it("should copy object", function() {
it("should deeply copy an object into an existing object", function() {
var src = {a:{name:"value"}};
var dst = {b:{key:"v"}};
expect(copy(src, dst)).toBe(dst);
@@ -56,6 +65,16 @@ describe('angular', function() {
expect(dst.a).not.toBe(src.a);
});
it("should deeply copy an object into an existing object", function() {
var src = {a:{name:"value"}};
var dst = copy(src, dst);
expect(src).toEqual({a:{name:"value"}});
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
expect(dst.a).toEqual(src.a);
expect(dst.a).not.toBe(src.a);
});
it("should copy primitives", function() {
expect(copy(null)).toEqual(null);
expect(copy('')).toBe('');
@@ -257,7 +276,7 @@ describe('angular', function() {
function MyObj() {
this.bar = 'barVal';
this.baz = 'bazVal';
};
}
MyObj.prototype.foo = 'fooVal';
var obj = new MyObj(),
@@ -267,6 +286,77 @@ describe('angular', function() {
expect(log).toEqual(['bar:barVal', 'baz:bazVal']);
});
it('should handle JQLite and jQuery objects like arrays', function() {
var jqObject = jqLite("<p><span>s1</span><span>s2</span></p>").find("span"),
log = [];
forEach(jqObject, function(value, key) { log.push(key + ':' + value.innerHTML)});
expect(log).toEqual(['0:s1', '1:s2']);
});
it('should handle NodeList objects like arrays', function() {
var nodeList = jqLite("<p><span>a</span><span>b</span><span>c</span></p>")[0].childNodes,
log = [];
forEach(nodeList, function(value, key) { log.push(key + ':' + value.innerHTML)});
expect(log).toEqual(['0:a', '1:b', '2:c']);
});
it('should handle HTMLCollection objects like arrays', function() {
document.body.innerHTML = "<p>" +
"<a name='x'>a</a>" +
"<a name='y'>b</a>" +
"<a name='x'>c</a>" +
"</p>";
var htmlCollection = document.getElementsByName('x'),
log = [];
forEach(htmlCollection, function(value, key) { log.push(key + ':' + value.innerHTML)});
expect(log).toEqual(['0:a', '1:c']);
});
it('should handle arguments objects like arrays', function() {
var args,
log = [];
(function(){ args = arguments}('a', 'b', 'c'));
forEach(args, function(value, key) { log.push(key + ':' + value)});
expect(log).toEqual(['0:a', '1:b', '2:c']);
});
it('should handle objects with length property as objects', function() {
var obj = {
'foo' : 'bar',
'length': 2
},
log = [];
forEach(obj, function(value, key) { log.push(key + ':' + value)});
expect(log).toEqual(['foo:bar', 'length:2']);
});
it('should handle objects of custom types with length property as objects', function() {
function CustomType() {
this.length = 2;
this.foo = 'bar'
}
var obj = new CustomType(),
log = [];
forEach(obj, function(value, key) { log.push(key + ':' + value)});
expect(log).toEqual(['length:2', 'foo:bar']);
});
});
@@ -583,6 +673,13 @@ describe('angular', function() {
toBe('<ng-abc x="2A">');
});
});
describe('startingTag', function() {
it('should allow passing in Nodes instead of Elements', function() {
var txtNode = document.createTextNode('some text');
expect(startingTag(txtNode)).toBe('some text');
});
});
describe('snake_case', function(){
it('should convert to snake_case', function() {
@@ -640,4 +737,27 @@ describe('angular', function() {
expect(toJson({key: $rootScope})).toEqual('{"key":"$SCOPE"}');
}));
});
describe('noConflict', function() {
var globalAngular;
beforeEach(function() {
globalAngular = angular;
});
afterEach(function() {
angular = globalAngular;
});
it('should return angular', function() {
var a = angular.noConflict();
expect(a).toBe(globalAngular);
});
it('should restore original angular', function() {
var a = angular.noConflict();
expect(angular).toBeUndefined();
});
});
});
+1 -1
View File
@@ -175,7 +175,7 @@ describe('Binder', function() {
$rootScope.error['throw'] = function() {throw 'MyError';};
errorLogs.length = 0;
$rootScope.$apply();
expect(errorLogs.shift()).toBe('MyError');
expect(errorLogs.shift().message).toBe('Error while interpolating: {{error.throw()}}\nMyError');
$rootScope.error['throw'] = function() {return 'ok';};
$rootScope.$apply();
+15 -8
View File
@@ -3,11 +3,13 @@
describe('injector', function() {
var providers;
var injector;
var providerInjector;
beforeEach(module(function($provide) {
beforeEach(module(function($provide, $injector) {
providers = function(name, factory, annotations) {
$provide.factory(name, extend(factory, annotations||{}));
};
providerInjector = $injector;
}));
beforeEach(inject(function($injector){
injector = $injector;
@@ -72,6 +74,11 @@ describe('injector', function() {
});
it('should create a new $injector for the run phase', inject(function($injector) {
expect($injector).not.toBe(providerInjector);
}));
describe('invoke', function() {
var args;
@@ -549,26 +556,26 @@ describe('injector', function() {
it('should decorate the missing service error with module name', function() {
angular.module('TestModule', [], function($injector) {});
angular.module('TestModule', [], function(xyzzy) {});
expect(function() {
createInjector(['TestModule']);
}).toThrow('Unknown provider: $injector from TestModule');
}).toThrow('Unknown provider: xyzzy from TestModule');
});
it('should decorate the missing service error with module function', function() {
function myModule($injector){}
function myModule(xyzzy){}
expect(function() {
createInjector([myModule]);
}).toThrow('Unknown provider: $injector from ' + myModule);
}).toThrow('Unknown provider: xyzzy from ' + myModule);
});
it('should decorate the missing service error with module array function', function() {
function myModule($injector){}
function myModule(xyzzy){}
expect(function() {
createInjector([['$injector', myModule]]);
}).toThrow('Unknown provider: $injector from ' + myModule);
createInjector([['xyzzy', myModule]]);
}).toThrow('Unknown provider: xyzzy from ' + myModule);
});
+6 -1
View File
@@ -955,9 +955,14 @@ describe('jqLite', function() {
expect(root.append('text')).toEqual(root);
expect(root.html()).toEqual('text');
});
it('should not append anything if parent node is not of type element', function() {
it('should append to document fragment', function() {
var root = jqLite(document.createDocumentFragment());
expect(root.append('<p>foo</p>')).toBe(root);
expect(root.children().length).toBe(1);
});
it('should not append anything if parent node is not of type element or docfrag', function() {
var root = jqLite('<p>some text node</p>').contents();
expect(root.append('<p>foo</p>')).toBe(root);
expect(root.children().length).toBe(0);
});
});
+6 -6
View File
@@ -109,7 +109,7 @@ beforeEach(function() {
if (this.actual.callCount != 1) {
if (this.actual.callCount == 0) {
return [
'Expected spy ' + this.actual.identity + ' to have been called with ' +
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
jasmine.pp(expectedArgs) + ' but it was never called.',
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
jasmine.pp(expectedArgs) + ' but it was.'
@@ -117,16 +117,16 @@ beforeEach(function() {
}
return [
'Expected spy ' + this.actual.identity + ' to have been called with ' +
jasmine.pp(expectedArgs) + ' but it was never called.',
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
jasmine.pp(expectedArgs) + ' but it was called ' + this.actual.callCount + ' times.',
'Expected spy ' + this.actual.identity + ' not to have been called once with ' +
jasmine.pp(expectedArgs) + ' but it was.'
];
} else {
return [
'Expected spy ' + this.actual.identity + ' to have been called with ' +
'Expected spy ' + this.actual.identity + ' to have been called once with ' +
jasmine.pp(expectedArgs) + ' but was called with ' + jasmine.pp(this.actual.argsForCall),
'Expected spy ' + this.actual.identity + ' not to have been called with ' +
'Expected spy ' + this.actual.identity + ' not to have been called once with ' +
jasmine.pp(expectedArgs) + ' but was called with ' + jasmine.pp(this.actual.argsForCall)
];
}
+14 -2
View File
@@ -279,6 +279,18 @@ describe('browser', function() {
});
});
describe('put via cookies(cookieName, string), if no <base href> ', function () {
beforeEach(function () {
fakeDocument.basePath = undefined;
});
it('should default path in cookie to "" (empty string)', function () {
browser.cookies('cookie', 'bender');
// This only fails in Safari and IE when cookiePath returns undefined
// Where it now succeeds since baseHref return '' instead of undefined
expect(document.cookie).toEqual('cookie=bender');
});
});
describe('get via cookies()[cookieName]', function() {
@@ -555,9 +567,9 @@ describe('browser', function() {
expect(browser.baseHref()).toEqual('/base/path/');
});
it('should return undefined if no <base href>', function() {
it('should return \'\' (empty string) if no <base href>', function() {
fakeDocument.basePath = undefined;
expect(browser.baseHref()).toBeUndefined();
expect(browser.baseHref()).toEqual('');
});
it('should remove domain from <base href>', function() {
+6
View File
@@ -104,6 +104,12 @@ describe('$cacheFactory', function() {
cache.remove(123);
expect(cache.info().size).toBe(0);
}));
it("should return value from put", inject(function($cacheFactory) {
var obj = {};
expect(cache.put('k1', obj)).toBe(obj);
}));
});
+248 -15
View File
@@ -116,12 +116,17 @@ describe('$compile', function() {
describe('compile phase', function() {
it('should attach scope to the document node when it is compiled explicitly', inject(function($document){
$compile($document)($rootScope);
expect($document.scope()).toBe($rootScope);
}));
it('should wrap root text nodes in spans', inject(function($compile, $rootScope) {
element = jqLite('<div>A&lt;a&gt;B&lt;/a&gt;C</div>');
var text = element.contents();
expect(text[0].nodeName).toEqual('#text');
text = $compile(text)($rootScope);
expect(lowercase(text[0].nodeName)).toEqual('span');
expect(text[0].nodeName).toEqual('SPAN');
expect(element.find('span').text()).toEqual('A<a>B</a>C');
}));
@@ -137,6 +142,41 @@ describe('$compile', function() {
expect(spans.text().indexOf('C')).toEqual(0);
});
it('should not leak memory when there are top level empty text nodes', function() {
var calcCacheSize = function() {
var size = 0;
forEach(jqLite.cache, function(item, key) { size++; });
return size;
};
// We compile the contents of element (i.e. not element itself)
// Then delete these contents and check the cache has been reset to zero
// First with only elements at the top level
element = jqLite('<div><div></div></div>');
$compile(element.contents())($rootScope);
element.html('');
expect(calcCacheSize()).toEqual(0);
// Next with non-empty text nodes at the top level
// (in this case the compiler will wrap them in a <span>)
element = jqLite('<div>xxx</div>');
$compile(element.contents())($rootScope);
element.html('');
expect(calcCacheSize()).toEqual(0);
// Next with comment nodes at the top level
element = jqLite('<div><!-- comment --></div>');
$compile(element.contents())($rootScope);
element.html('');
expect(calcCacheSize()).toEqual(0);
// Finally with empty text nodes at the top level
element = jqLite('<div> \n<div></div> </div>');
$compile(element.contents())($rootScope);
element.html('');
expect(calcCacheSize()).toEqual(0);
});
describe('multiple directives per element', function() {
it('should allow multiple directives per element', inject(function($compile, $rootScope, log){
@@ -1381,9 +1421,10 @@ describe('$compile', function() {
}));
it('should set interpolated attrs to undefined', inject(function($rootScope, $compile) {
it('should set interpolated attrs to initial interpolation value', inject(function($rootScope, $compile) {
$rootScope.whatever = 'test value';
$compile('<div some-attr="{{whatever}}" observer></div>')($rootScope);
expect(directiveAttrs.someAttr).toBeUndefined();
expect(directiveAttrs.someAttr).toBe($rootScope.whatever);
}));
@@ -1538,6 +1579,25 @@ describe('$compile', function() {
expect(element.text()).toEqual('WORKS');
});
});
it('should support $observe inside link function on directive object', function() {
module(function() {
directive('testLink', valueFn({
templateUrl: 'test-link.html',
link: function(scope, element, attrs) {
attrs.$observe( 'testLink', function ( val ) {
scope.testAttr = val;
});
}
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test-link.html', '{{testAttr}}' );
element = $compile('<div test-link="{{1+2}}"></div>')($rootScope);
$rootScope.$apply();
expect(element.text()).toBe('3');
});
});
});
@@ -1793,27 +1853,21 @@ describe('$compile', function() {
describe('attribute', function() {
it('should copy simple attribute', inject(function() {
compile('<div><span my-component attr="some text">');
expect(componentScope.attr).toEqual(undefined);
expect(componentScope.attrAlias).toEqual(undefined);
$rootScope.$apply();
expect(componentScope.attr).toEqual('some text');
expect(componentScope.attrAlias).toEqual('some text');
expect(componentScope.attrAlias).toEqual(componentScope.attr);
}));
it('should set up the interpolation before it reaches the link function', inject(function() {
$rootScope.name = 'misko';
compile('<div><span my-component attr="hello {{name}}">');
expect(componentScope.attr).toEqual('hello misko');
expect(componentScope.attrAlias).toEqual('hello misko');
}));
it('should update when interpolated attribute updates', inject(function() {
compile('<div><span my-component attr="hello {{name}}">');
expect(componentScope.attr).toEqual(undefined);
expect(componentScope.attrAlias).toEqual(undefined);
$rootScope.name = 'misko';
$rootScope.$apply();
expect(componentScope.attr).toEqual('hello misko');
expect(componentScope.attrAlias).toEqual('hello misko');
$rootScope.name = 'igor';
$rootScope.$apply();
@@ -1938,6 +1992,21 @@ describe('$compile', function() {
compile('<div><span bad-declaration>');
}).toThrow('Invalid isolate scope definition for directive badDeclaration: xxx');
}));
it('should expose a $$isolateBindings property onto the scope', inject(function() {
compile('<div><span my-component>');
expect(typeof componentScope.$$isolateBindings).toBe('object');
expect(componentScope.$$isolateBindings.attr).toBe('@attr');
expect(componentScope.$$isolateBindings.attrAlias).toBe('@attr');
expect(componentScope.$$isolateBindings.ref).toBe('=ref');
expect(componentScope.$$isolateBindings.refAlias).toBe('=ref');
expect(componentScope.$$isolateBindings.reference).toBe('=reference');
expect(componentScope.$$isolateBindings.expr).toBe('&expr');
expect(componentScope.$$isolateBindings.exprAlias).toBe('&expr');
}));
});
@@ -2264,5 +2333,169 @@ describe('$compile', function() {
expect(element.text()).toBe('-->|x|');
}));
it('should add a $$transcluded property onto the transcluded scope', function() {
module(function() {
directive('trans', function() {
return {
transclude: true,
replace: true,
scope: true,
template: '<div><span>I:{{$$transcluded}}</span><div ng-transclude></div></div>'
};
});
});
inject(function(log, $rootScope, $compile) {
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')
($rootScope);
$rootScope.$apply();
expect(jqLite(element.find('span')[0]).text()).toEqual('I:');
expect(jqLite(element.find('span')[1]).text()).toEqual('T:true');
});
});
});
describe('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');
}));
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()";
$rootScope.$apply();
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()";
$rootScope.$apply();
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
}));
it('should allow reconfiguration of the href whitelist', function() {
module(function($compileProvider) {
expect($compileProvider.urlSanitizationWhitelist() instanceof RegExp).toBe(true);
var returnVal = $compileProvider.urlSanitizationWhitelist(/javascript:/);
expect(returnVal).toBe($compileProvider);
});
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');
});
});
});
});
+19 -4
View File
@@ -1,7 +1,13 @@
'use strict';
describe('a', function() {
var element;
var element, $compile, $rootScope;
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
afterEach(function(){
@@ -9,8 +15,7 @@ describe('a', function() {
});
it('should prevent default action to be executed when href is empty',
inject(function($rootScope, $compile) {
it('should prevent default action to be executed when href is empty', function() {
var orgLocation = document.location.href,
preventDefaultCalled = false,
event;
@@ -42,5 +47,15 @@ describe('a', function() {
}
expect(document.location.href).toEqual(orgLocation);
}));
});
it('should prevent IE for changing text content when setting attribute', function() {
// see issue #1949
element = jqLite('<a href="">hello@you</a>');
$compile(element);
element.attr('href', 'bye@me');
expect(element.text()).toBe('hello@you');
});
});
+10
View File
@@ -74,6 +74,16 @@ describe('boolean attr directives', function() {
$rootScope.$digest();
expect(element.attr('multiple')).toBeTruthy();
}));
it('should bind open', inject(function($rootScope, $compile) {
element = $compile('<details ng-open="isOpen"></details>')($rootScope)
$rootScope.isOpen=false;
$rootScope.$digest();
expect(element.attr('open')).toBeFalsy();
$rootScope.isOpen=true;
$rootScope.$digest();
expect(element.attr('open')).toBeTruthy();
}));
});
+106
View File
@@ -430,4 +430,110 @@ describe('form', function() {
expect(doc).toBeDirty();
});
});
describe('$setPristine', function() {
it('should reset pristine state of form and controls', function() {
doc = $compile(
'<form name="testForm">' +
'<input ng-model="named1" name="foo">' +
'<input ng-model="named2" name="bar">' +
'</form>')(scope);
scope.$digest();
var form = doc,
formCtrl = scope.testForm,
input1 = form.find('input').eq(0),
input1Ctrl = input1.controller('ngModel'),
input2 = form.find('input').eq(1),
input2Ctrl = input2.controller('ngModel');
input1Ctrl.$setViewValue('xx');
input2Ctrl.$setViewValue('yy');
scope.$apply();
expect(form).toBeDirty();
expect(input1).toBeDirty();
expect(input2).toBeDirty();
formCtrl.$setPristine();
expect(form).toBePristine();
expect(formCtrl.$pristine).toBe(true);
expect(formCtrl.$dirty).toBe(false);
expect(input1).toBePristine();
expect(input1Ctrl.$pristine).toBe(true);
expect(input1Ctrl.$dirty).toBe(false);
expect(input2).toBePristine();
expect(input2Ctrl.$pristine).toBe(true);
expect(input2Ctrl.$dirty).toBe(false);
});
it('should reset pristine state of anonymous form controls', function() {
doc = $compile(
'<form name="testForm">' +
'<input ng-model="anonymous">' +
'</form>')(scope);
scope.$digest();
var form = doc,
formCtrl = scope.testForm,
input = form.find('input').eq(0),
inputCtrl = input.controller('ngModel');
inputCtrl.$setViewValue('xx');
scope.$apply();
expect(form).toBeDirty();
expect(input).toBeDirty();
formCtrl.$setPristine();
expect(form).toBePristine();
expect(formCtrl.$pristine).toBe(true);
expect(formCtrl.$dirty).toBe(false);
expect(input).toBePristine();
expect(inputCtrl.$pristine).toBe(true);
expect(inputCtrl.$dirty).toBe(false);
});
it('should reset pristine state of nested forms', function() {
doc = $compile(
'<form name="testForm">' +
'<div ng-form>' +
'<input ng-model="named" name="foo">' +
'</div>' +
'</form>')(scope);
scope.$digest();
var form = doc,
formCtrl = scope.testForm,
nestedForm = form.find('div'),
nestedFormCtrl = nestedForm.controller('form'),
nestedInput = form.find('input').eq(0),
nestedInputCtrl = nestedInput.controller('ngModel');
nestedInputCtrl.$setViewValue('xx');
scope.$apply();
expect(form).toBeDirty();
expect(nestedForm).toBeDirty();
expect(nestedInput).toBeDirty();
formCtrl.$setPristine();
expect(form).toBePristine();
expect(formCtrl.$pristine).toBe(true);
expect(formCtrl.$dirty).toBe(false);
expect(nestedForm).toBePristine();
expect(nestedFormCtrl.$pristine).toBe(true);
expect(nestedFormCtrl.$dirty).toBe(false);
expect(nestedInput).toBePristine();
expect(nestedInputCtrl.$pristine).toBe(true);
expect(nestedInputCtrl.$dirty).toBe(false);
});
});
});
+20
View File
@@ -117,6 +117,18 @@ describe('NgModelController', function() {
});
});
describe('setPristine', function() {
it('should set control to its pristine state', function() {
ctrl.$setViewValue('edit');
expect(ctrl.$dirty).toBe(true);
expect(ctrl.$pristine).toBe(false);
ctrl.$setPristine();
expect(ctrl.$dirty).toBe(false);
expect(ctrl.$pristine).toBe(true);
});
});
describe('view -> model', function() {
@@ -382,6 +394,14 @@ describe('input', function() {
});
it('should update the model and not trim the value', function() {
compileInput('<input type="text" ng-model="name" name="alias" ng-trim="false" />');
changeInputValueTo(' a ');
expect(scope.name).toEqual(' a ');
});
it('should allow complex reference binding', function() {
compileInput('<input type="text" ng-model="obj[\'abc\'].name"/>');
+11
View File
@@ -236,6 +236,17 @@ describe('ngClass', function() {
}));
it('should not mess up class value due to observing an interpolated class attribute', inject(function($rootScope, $compile) {
$rootScope.foo = true;
$rootScope.$watch("anything", function() {
$rootScope.foo = false;
});
element = $compile('<div ng-class="{foo:foo}"></div>')($rootScope);
$rootScope.$digest();
expect(element.hasClass('foo')).toBe(false);
}));
it('should update ngClassOdd/Even when model is changed by filtering', inject(function($rootScope, $compile) {
element = $compile('<ul>' +
'<li ng-repeat="i in items" ' +
+29
View File
@@ -0,0 +1,29 @@
'use strict';
describe('ngKeyup and ngKeydown directives', function() {
var element;
afterEach(function() {
dealoc(element);
});
it('should get called on a keyup', inject(function($rootScope, $compile) {
element = $compile('<input ng-keyup="touched = true">')($rootScope);
$rootScope.$digest();
expect($rootScope.touched).toBeFalsy();
browserTrigger(element, 'keyup');
expect($rootScope.touched).toEqual(true);
}));
it('should get called on a keydown', inject(function($rootScope, $compile) {
element = $compile('<input ng-keydown="touched = true">')($rootScope);
$rootScope.$digest();
expect($rootScope.touched).toBeFalsy();
browserTrigger(element, 'keydown');
expect($rootScope.touched).toEqual(true);
}));
});
+45
View File
@@ -36,6 +36,36 @@ describe('ngSwitch', function() {
}));
it('should show all switch-whens that match the current value', inject(function($rootScope, $compile) {
element = $compile(
'<ul ng-switch="select">' +
'<li ng-switch-when="1">first:{{name}}</li>' +
'<li ng-switch-when="1">, first too:{{name}}</li>' +
'<li ng-switch-when="2">second:{{name}}</li>' +
'<li ng-switch-when="true">true:{{name}}</li>' +
'</ul>')($rootScope);
expect(element.html()).toEqual('<!-- ngSwitchWhen: 1 -->' +
'<!-- ngSwitchWhen: 1 -->' +
'<!-- ngSwitchWhen: 2 -->' +
'<!-- ngSwitchWhen: true -->');
$rootScope.select = 1;
$rootScope.$apply();
expect(element.text()).toEqual('first:, first too:');
$rootScope.name="shyam";
$rootScope.$apply();
expect(element.text()).toEqual('first:shyam, first too:shyam');
$rootScope.select = 2;
$rootScope.$apply();
expect(element.text()).toEqual('second:shyam');
$rootScope.name = 'misko';
$rootScope.$apply();
expect(element.text()).toEqual('second:misko');
$rootScope.select = true;
$rootScope.$apply();
expect(element.text()).toEqual('true:misko');
}));
it('should switch on switch-when-default', inject(function($rootScope, $compile) {
element = $compile(
'<ng:switch on="select">' +
@@ -50,6 +80,21 @@ describe('ngSwitch', function() {
}));
it('should show all switch-when-default', inject(function($rootScope, $compile) {
element = $compile(
'<ul ng-switch="select">' +
'<li ng-switch-when="1">one</li>' +
'<li ng-switch-default>other</li>' +
'<li ng-switch-default>, other too</li>' +
'</ul>')($rootScope);
$rootScope.$apply();
expect(element.text()).toEqual('other, other too');
$rootScope.select = 1;
$rootScope.$apply();
expect(element.text()).toEqual('one');
}));
it('should call change on switch', inject(function($rootScope, $compile) {
element = $compile(
'<ng:switch on="url" change="name=\'works\'">' +
+25 -1
View File
@@ -429,7 +429,7 @@ describe('ngView', function() {
$rootScope.$digest();
expect($rootScope.load).toHaveBeenCalledOnce();
});
})
});
it('should set $scope and $controllerController on the view', function() {
@@ -459,4 +459,28 @@ describe('ngView', function() {
expect(div.controller()).toBe($route.current.scope.ctrl);
});
});
it('should not set $scope or $controllerController on top level text elements in the view', function() {
function MyCtrl($scope) {}
module(function($routeProvider) {
$routeProvider.when('/foo', {templateUrl: 'tpl.html', controller: MyCtrl});
});
inject(function($templateCache, $location, $rootScope, $route) {
$templateCache.put('tpl.html', '<div></div> ');
$location.url('/foo');
$rootScope.$digest();
forEach(element.contents(), function(node) {
if ( node.nodeType == 3 ) {
expect(jqLite(node).scope()).not.toBe($route.current.scope);
expect(jqLite(node).controller()).not.toBeDefined();
} else {
expect(jqLite(node).scope()).toBe($route.current.scope);
expect(jqLite(node).controller()).toBeDefined();
}
});
});
});
});
+53
View File
@@ -66,4 +66,57 @@ describe('Filter: filter', function() {
expect(filter(items, '!isk').length).toBe(1);
expect(filter(items, '!isk')[0]).toEqual(items[1]);
});
describe('should support comparator', function() {
it('as equality when true', function() {
var items = ['misko', 'adam', 'adamson'];
var expr = 'adam';
expect(filter(items, expr, true)).toEqual([items[1]]);
expect(filter(items, expr, false)).toEqual([items[1], items[2]]);
var items = [
{key: 'value1', nonkey: 1},
{key: 'value2', nonkey: 2},
{key: 'value12', nonkey: 3},
{key: 'value1', nonkey:4},
{key: 'Value1', nonkey:5}
];
var expr = {key: 'value1'};
expect(filter(items, expr, true)).toEqual([items[0], items[3]]);
var items = [
{key: 1, nonkey: 1},
{key: 2, nonkey: 2},
{key: 12, nonkey: 3},
{key: 1, nonkey:4}
];
var expr = { key: 1 };
expect(filter(items, expr, true)).toEqual([items[0], items[3]]);
var expr = 12;
expect(filter(items, expr, true)).toEqual([items[2]]);
});
it('and use the function given to compare values', function() {
var items = [
{key: 1, nonkey: 1},
{key: 2, nonkey: 2},
{key: 12, nonkey: 3},
{key: 1, nonkey:14}
];
var expr = {key: 10};
var comparator = function (obj,value) {
return obj > value;
}
expect(filter(items, expr, comparator)).toEqual([items[2]]);
expr = 10;
expect(filter(items, expr, comparator)).toEqual([items[2], items[3]]);
});
});
});
+63 -22
View File
@@ -71,6 +71,17 @@ describe('filters', function() {
var num = formatNumber(123.1116, pattern, ',', '.');
expect(num).toBe('123.112');
});
it('should format the same with string as well as numeric fractionSize', function(){
var num = formatNumber(123.1, pattern, ',', '.', "0");
expect(num).toBe('123');
var num = formatNumber(123.1, pattern, ',', '.', 0);
expect(num).toBe('123');
var num = formatNumber(123.1, pattern, ',', '.', "3");
expect(num).toBe('123.100');
var num = formatNumber(123.1, pattern, ',', '.', 3);
expect(num).toBe('123.100');
});
});
describe('currency', function() {
@@ -162,9 +173,9 @@ describe('filters', function() {
describe('date', function() {
var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am
var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.001Z'); //7am
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.012Z'); //12pm
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.123Z'); //12am
var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z');
var date;
@@ -192,14 +203,23 @@ describe('filters', function() {
expect(date(morning, "yy-MM-dd HH:mm:ss")).
toEqual('10-09-03 07:05:08');
expect(date(morning, "yy-MM-dd HH:mm:ss.sss")).
toEqual('10-09-03 07:05:08.001');
expect(date(midnight, "yyyy-M-d h=H:m:saZ")).
toEqual('2010-9-3 12=0:5:8AM0500');
toEqual('2010-9-3 12=0:5:8AM-0500');
expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")).
toEqual('2010-09-03 12=00:05:08AM0500');
toEqual('2010-09-03 12=00:05:08AM-0500');
expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ss.sssaZ")).
toEqual('2010-09-03 12=00:05:08.123AM-0500');
expect(date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
toEqual('2010-09-03 12=12:05:08PM0500');
toEqual('2010-09-03 12=12:05:08PM-0500');
expect(date(noon, "yyyy-MM-dd hh=HH:mm:ss.sssaZ")).
toEqual('2010-09-03 12=12:05:08.012PM-0500');
expect(date(noon, "EEE, MMM d, yyyy")).
toEqual('Fri, Sep 3, 2010');
@@ -211,14 +231,30 @@ describe('filters', function() {
toEqual('September 03, 1');
});
it('should format timezones correctly (as per ISO_8601)', function() {
//Note: TzDate's first argument is offset, _not_ timezone.
var utc = new angular.mock.TzDate( 0, '2010-09-03T12:05:08.000Z');
var eastOfUTC = new angular.mock.TzDate(-5, '2010-09-03T12:05:08.000Z');
var westOfUTC = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z');
expect(date(utc, "yyyy-MM-ddTHH:mm:ssZ")).
toEqual('2010-09-03T12:05:08+0000')
expect(date(eastOfUTC, "yyyy-MM-ddTHH:mm:ssZ")).
toEqual('2010-09-03T17:05:08+0500')
expect(date(westOfUTC, "yyyy-MM-ddTHH:mm:ssZ")).
toEqual('2010-09-03T07:05:08-0500')
});
it('should treat single quoted strings as string literals', function() {
expect(date(midnight, "yyyy'de' 'a'x'dd' 'adZ' h=H:m:saZ")).
toEqual('2010de axdd adZ 12=0:5:8AM0500');
toEqual('2010de axdd adZ 12=0:5:8AM-0500');
});
it('should treat a sequence of two single quotes as a literal single quote', function() {
expect(date(midnight, "yyyy'de' 'a''dd' 'adZ' h=H:m:saZ")).
toEqual("2010de a'dd adZ 12=0:5:8AM0500");
toEqual("2010de a'dd adZ 12=0:5:8AM-0500");
});
it('should accept default formats', function() {
@@ -253,32 +289,37 @@ describe('filters', function() {
});
it('should support various iso8061 date strings as input', function() {
var format = 'yyyy-MM ss';
it('should support various iso8061 date strings with timezone as input', function() {
var format = 'yyyy-MM-dd ss';
//full ISO8061
expect(date('2003-09-10T13:02:03.000Z', format)).toEqual('2003-09 03');
expect(date('2003-09-10T13:02:03.000Z', format)).toEqual('2003-09-10 03');
expect(date('2003-09-10T13:02:03.000+00:00', format)).toEqual('2003-09 03');
expect(date('2003-09-10T13:02:03.000+00:00', format)).toEqual('2003-09-10 03');
expect(date('2003-09-10T13:02:03-08:00', format)).toEqual('2003-09 03');
expect(date('20030910T033203-0930', format)).toEqual('2003-09 03');
//no timezone
expect(date('2003-09-10T13:02:03.000', format)).toEqual('2003-09 03');
expect(date('20030910T033203-0930', format)).toEqual('2003-09-10 03');
//no millis
expect(date('2003-09-10T13:02:03Z', format)).toEqual('2003-09 03');
expect(date('2003-09-10T13:02:03Z', format)).toEqual('2003-09-10 03');
//no seconds
expect(date('2003-09-10T13:02Z', format)).toEqual('2003-09 00');
expect(date('2003-09-10T13:02Z', format)).toEqual('2003-09-10 00');
//no minutes
expect(date('2003-09-10T13Z', format)).toEqual('2003-09 00');
expect(date('2003-09-10T13Z', format)).toEqual('2003-09-10 00');
});
it('should parse iso8061 date strings without timezone as local time', function() {
var format = 'yyyy-MM-dd HH-mm-ss';
//full ISO8061 without timezone
expect(date('2003-09-10T03:02:04.000', format)).toEqual('2003-09-10 03-02-04');
expect(date('20030910T030204', format)).toEqual('2003-09-10 03-02-04');
//no time
expect(date('2003-09-10', format)).toEqual('2003-09 00');
expect(date('2003-09-10', format)).toEqual('2003-09-10 00-00-00');
});
it('should support different degrees of subsecond precision', function () {
+24 -5
View File
@@ -2,10 +2,12 @@
describe('Filter: limitTo', function() {
var items;
var str
var limitTo;
beforeEach(inject(function($filter) {
items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
str = "tuvwxyz";
limitTo = $filter('limitTo');
}));
@@ -13,12 +15,16 @@ describe('Filter: limitTo', function() {
it('should return the first X items when X is positive', function() {
expect(limitTo(items, 3)).toEqual(['a', 'b', 'c']);
expect(limitTo(items, '3')).toEqual(['a', 'b', 'c']);
expect(limitTo(str, 3)).toEqual("tuv");
expect(limitTo(str, '3')).toEqual("tuv");
});
it('should return the last X items when X is negative', function() {
expect(limitTo(items, -3)).toEqual(['f', 'g', 'h']);
expect(limitTo(items, '-3')).toEqual(['f', 'g', 'h']);
expect(limitTo(str, -3)).toEqual("xyz");
expect(limitTo(str, '-3')).toEqual("xyz");
});
@@ -30,11 +36,17 @@ describe('Filter: limitTo', function() {
expect(limitTo(items, undefined)).toEqual([]);
});
it('should return an empty string when X cannot be parsed', function() {
expect(limitTo(str, 'bogus')).toEqual("");
expect(limitTo(str, 'null')).toEqual("");
expect(limitTo(str, 'undefined')).toEqual("");
expect(limitTo(str, null)).toEqual("");
expect(limitTo(str, undefined)).toEqual("");
});
it('should return an empty array when input is not Array type', function() {
expect(limitTo('bogus', 1)).toEqual('bogus');
expect(limitTo(null, 1)).toEqual(null);
expect(limitTo(undefined, 1)).toEqual(undefined);
it('should return input if not String or Array', function() {
expect(limitTo(1,1)).toEqual(1);
expect(limitTo(null, 1)).toEqual(null);
expect(limitTo(undefined, 1)).toEqual(undefined);
expect(limitTo({}, 1)).toEqual({});
@@ -42,11 +54,18 @@ describe('Filter: limitTo', function() {
it('should return a copy of input array if X is exceeds array length', function () {
expect(limitTo(items, 19)).toEqual(items);
expect(limitTo(items, 9)).toEqual(items);
expect(limitTo(items, '9')).toEqual(items);
expect(limitTo(items, -9)).toEqual(items);
expect(limitTo(items, '-9')).toEqual(items);
expect(limitTo(items, 9)).not.toBe(items);
});
it('should return the entire string if X exceeds input length', function() {
expect(limitTo(str, 9)).toEqual(str);
expect(limitTo(str, '9')).toEqual(str);
expect(limitTo(str, -9)).toEqual(str);
expect(limitTo(str, '-9')).toEqual(str);
})
});
+21
View File
@@ -116,6 +116,9 @@ describe('$httpBackend', function() {
};
this.getAllResponseHeaders = valueFn('');
// for temporary Firefox CORS workaround
// see https://github.com/angular/angular.js/issues/1468
this.getResponseHeader = valueFn('');
}
callback.andCallFake(function(status, response) {
@@ -135,6 +138,24 @@ describe('$httpBackend', function() {
});
it('should set responseType and return xhr.response', function() {
$backend('GET', '/whatever', null, callback, {}, null, null, 'blob');
var xhrInstance = MockXhr.$$lastInstance;
expect(xhrInstance.responseType).toBe('blob');
callback.andCallFake(function(status, response) {
expect(response).toBe(xhrInstance.response);
});
xhrInstance.response = {some: 'object'};
xhrInstance.readyState = 4;
xhrInstance.onreadystatechange();
expect(callback).toHaveBeenCalledOnce();
});
describe('JSONP', function() {
var SCRIPT_URL = /([^\?]*)\?cb=angular\.callbacks\.(.*)/;
+88 -13
View File
@@ -144,9 +144,26 @@ describe('$http', function() {
it('should jsonify objects in params map', inject(function($httpBackend, $http) {
$httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22%3A3%7D').respond('');
$httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22:3%7D').respond('');
$http({url: '/url', params: {a:1, b:{c:3}}, method: 'GET'});
}));
it('should expand arrays in params map', inject(function($httpBackend, $http) {
$httpBackend.expect('GET', '/url?a=1&a=2&a=3').respond('');
$http({url: '/url', params: {a: [1,2,3]}, method: 'GET'});
}));
it('should not encode @ in url params', function() {
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
//with regards to the character set (pchar) allowed in path segments
//so we need this test to make sure that we don't over-encode the params and break stuff
//like buzz api which uses @self
$httpBackend.expect('GET', '/Path?!do%26h=g%3Da+h&:bar=$baz@1').respond('');
$http({url: '/Path', params: {':bar': '$baz@1', '!do&h': 'g=a h'}, method: 'GET'});
});
});
@@ -371,8 +388,7 @@ describe('$http', function() {
it('should set default headers for GET request', function() {
$httpBackend.expect('GET', '/url', undefined, function(headers) {
return headers['Accept'] == 'application/json, text/plain, */*' &&
headers['X-Requested-With'] == 'XMLHttpRequest';
return headers['Accept'] == 'application/json, text/plain, */*';
}).respond('');
$http({url: '/url', method: 'GET', headers: {}});
@@ -383,7 +399,6 @@ describe('$http', function() {
it('should set default headers for POST request', function() {
$httpBackend.expect('POST', '/url', 'messageBody', function(headers) {
return headers['Accept'] == 'application/json, text/plain, */*' &&
headers['X-Requested-With'] == 'XMLHttpRequest' &&
headers['Content-Type'] == 'application/json;charset=utf-8';
}).respond('');
@@ -395,7 +410,6 @@ describe('$http', function() {
it('should set default headers for PUT request', function() {
$httpBackend.expect('PUT', '/url', 'messageBody', function(headers) {
return headers['Accept'] == 'application/json, text/plain, */*' &&
headers['X-Requested-With'] == 'XMLHttpRequest' &&
headers['Content-Type'] == 'application/json;charset=utf-8';
}).respond('');
@@ -406,8 +420,7 @@ describe('$http', function() {
it('should set default headers for custom HTTP method', function() {
$httpBackend.expect('FOO', '/url', undefined, function(headers) {
return headers['Accept'] == 'application/json, text/plain, */*' &&
headers['X-Requested-With'] == 'XMLHttpRequest';
return headers['Accept'] == 'application/json, text/plain, */*';
}).respond('');
$http({url: '/url', method: 'FOO', headers: {}});
@@ -418,7 +431,6 @@ describe('$http', function() {
it('should override default headers with custom', function() {
$httpBackend.expect('POST', '/url', 'messageBody', function(headers) {
return headers['Accept'] == 'Rewritten' &&
headers['X-Requested-With'] == 'XMLHttpRequest' &&
headers['Content-Type'] == 'Rewritten';
}).respond('');
@@ -429,6 +441,17 @@ describe('$http', function() {
$httpBackend.flush();
});
it('should not set XSRF cookie for cross-domain requests', inject(function($browser) {
$browser.cookies('XSRF-TOKEN', 'secret');
$browser.url('http://host.com/base');
$httpBackend.expect('GET', 'http://www.test.com/url', undefined, function(headers) {
return headers['X-XSRF-TOKEN'] === undefined;
}).respond('');
$http({url: 'http://www.test.com/url', method: 'GET', headers: {}});
$httpBackend.flush();
}));
it('should not send Content-Type header if request data/body is undefined', function() {
$httpBackend.expect('POST', '/url', undefined, function(headers) {
@@ -441,22 +464,27 @@ describe('$http', function() {
it('should set the XSRF cookie into a XSRF header', inject(function($browser) {
function checkXSRF(secret) {
function checkXSRF(secret, header) {
return function(headers) {
return headers['X-XSRF-TOKEN'] == secret;
return headers[header || 'X-XSRF-TOKEN'] == secret;
};
}
$browser.cookies('XSRF-TOKEN', 'secret');
$browser.cookies('aCookie', 'secret2');
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret')).respond('');
$httpBackend.expect('POST', '/url', undefined, checkXSRF('secret')).respond('');
$httpBackend.expect('PUT', '/url', undefined, checkXSRF('secret')).respond('');
$httpBackend.expect('DELETE', '/url', undefined, checkXSRF('secret')).respond('');
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret', 'aHeader')).respond('');
$httpBackend.expect('GET', '/url', undefined, checkXSRF('secret2')).respond('');
$http({url: '/url', method: 'GET'});
$http({url: '/url', method: 'POST', headers: {'S-ome': 'Header'}});
$http({url: '/url', method: 'PUT', headers: {'Another': 'Header'}});
$http({url: '/url', method: 'DELETE', headers: {}});
$http({url: '/url', method: 'GET', xsrfHeaderName: 'aHeader'})
$http({url: '/url', method: 'GET', xsrfCookieName: 'aCookie'})
$httpBackend.flush();
}));
@@ -959,12 +987,35 @@ describe('$http', function() {
});
it('should pass timeout and withCredentials', function() {
it('should pass timeout, withCredentials and responseType', function() {
var $httpBackend = jasmine.createSpy('$httpBackend');
$httpBackend.andCallFake(function(m, u, d, c, h, timeout, withCredentials) {
$httpBackend.andCallFake(function(m, u, d, c, h, timeout, withCredentials, responseType) {
expect(timeout).toBe(12345);
expect(withCredentials).toBe(true);
expect(responseType).toBe('json');
});
module(function($provide) {
$provide.value('$httpBackend', $httpBackend);
});
inject(function($http) {
$http({
method: 'GET', url: 'some.html', timeout: 12345, withCredentials: true, responseType: 'json'
});
expect($httpBackend).toHaveBeenCalledOnce();
});
$httpBackend.verifyNoOutstandingExpectation = noop;
});
it('should use withCredentials from default', function() {
var $httpBackend = jasmine.createSpy('$httpBackend');
$httpBackend.andCallFake(function(m, u, d, c, h, timeout, withCredentials, responseType) {
expect(withCredentials).toBe(true);
});
module(function($provide) {
@@ -972,10 +1023,34 @@ describe('$http', function() {
});
inject(function($http) {
$http({method: 'GET', url: 'some.html', timeout: 12345, withCredentials: true});
$http.defaults.withCredentials = true;
$http({
method: 'GET', url: 'some.html', timeout: 12345, responseType: 'json'
});
expect($httpBackend).toHaveBeenCalledOnce();
});
$httpBackend.verifyNoOutstandingExpectation = noop;
});
describe('isSameDomain', function() {
it('should support various combinations of urls', function() {
expect(isSameDomain('path/morepath',
'http://www.adomain.com')).toBe(true);
expect(isSameDomain('http://www.adomain.com/path',
'http://www.adomain.com')).toBe(true);
expect(isSameDomain('//www.adomain.com/path',
'http://www.adomain.com')).toBe(true);
expect(isSameDomain('//www.adomain.com/path',
'https://www.adomain.com')).toBe(true);
expect(isSameDomain('//www.adomain.com/path',
'http://www.adomain.com:1234')).toBe(false);
expect(isSameDomain('https://www.adomain.com/path',
'http://www.adomain.com')).toBe(false);
expect(isSameDomain('http://www.adomain.com:1234/path',
'http://www.adomain.com')).toBe(false);
expect(isSameDomain('http://www.anotherdomain.com/path',
'http://www.adomain.com')).toBe(false);
});
});
});
+23
View File
@@ -26,6 +26,29 @@ describe('$interpolate', function() {
expect($interpolate('{{ false }}')()).toEqual('false');
}));
it('should rethrow exceptions', inject(function($interpolate, $rootScope) {
$rootScope.err = function () {
throw new Error('oops');
};
expect(function () {
$interpolate('{{err()}}')($rootScope);
}).toThrow('Error while interpolating: {{err()}}\nError: oops');
}));
it('should stop interpolation when encountering an exception', inject(function($interpolate, $compile, $rootScope) {
$rootScope.err = function () {
throw new Error('oops');
};
var dom = jqLite('<div>{{1 + 1}}</div><div>{{err()}}</div><div>{{1 + 2}}</div>');
$compile(dom)($rootScope);
expect(function () {
$rootScope.$apply();
}).toThrow('Error while interpolating: {{err()}}\nError: oops');
expect(dom[0].innerHTML).toEqual('2');
expect(dom[1].innerHTML).toEqual('{{err()}}');
expect(dom[2].innerHTML).toEqual('{{1 + 2}}');
}));
it('should return interpolation function', inject(function($interpolate, $rootScope) {
$rootScope.name = 'Misko';
+45 -8
View File
@@ -1,17 +1,24 @@
'use strict';
function initService(debugEnabled) {
return module(function($logProvider){
$logProvider.debugEnabled(debugEnabled);
});
}
describe('$log', function() {
var $window, logger, log, warn, info, error;
var $window, logger, log, warn, info, error, debug;
beforeEach(module(function($provide){
$window = {navigator: {}};
$window = {navigator: {}, document: {}};
logger = '';
log = function() { logger+= 'log;'; };
warn = function() { logger+= 'warn;'; };
info = function() { logger+= 'info;'; };
error = function() { logger+= 'error;'; };
debug = function() { logger+= 'debug;'; };
$provide.provider('$log', $LogProvider);
$provide.value('$exceptionHandler', angular.mock.rethrow);
@@ -23,14 +30,16 @@ describe('$log', function() {
$window.console = {log: log,
warn: warn,
info: info,
error: error};
error: error,
debug: debug};
},
function($log) {
$log.log();
$log.warn();
$log.info();
$log.error();
expect(logger).toEqual('log;warn;info;error;');
$log.debug();
expect(logger).toEqual('log;warn;info;error;debug;');
}
));
@@ -44,7 +53,8 @@ describe('$log', function() {
$log.warn();
$log.info();
$log.error();
expect(logger).toEqual('log;log;log;log;');
$log.debug();
expect(logger).toEqual('log;log;log;log;log;');
}
));
@@ -55,6 +65,7 @@ describe('$log', function() {
$log.warn();
$log.info();
$log.error();
$log.debug();
}
));
@@ -64,22 +75,48 @@ describe('$log', function() {
log.apply = log.call =
warn.apply = warn.call =
info.apply = info.call =
error.apply = error.call = null;
error.apply = error.call =
debug.apply = debug.call = null;
$window.console = {log: log,
warn: warn,
info: info,
error: error};
error: error,
debug: debug};
},
function($log) {
$log.log.apply($log);
$log.warn.apply($log);
$log.info.apply($log);
$log.error.apply($log);
expect(logger).toEqual('log;warn;info;error;');
$log.debug.apply($log);
expect(logger).toEqual('log;warn;info;error;debug;');
})
);
describe("$log.debug", function () {
beforeEach(initService(false));
it("should skip debugging output if disabled", inject(
function(){
$window.console = {log: log,
warn: warn,
info: info,
error: error,
debug: debug};
},
function($log) {
$log.log();
$log.warn();
$log.info();
$log.error();
$log.debug();
expect(logger).toEqual('log;warn;info;error;');
}
));
});
describe('$log.error', function() {
var e, $log, errorArgs;
+81 -3
View File
@@ -91,8 +91,8 @@ describe('parser', function() {
expect(tokens[1].text).toEqual('b');
});
it('should tokenize relation', function() {
var tokens = lex("! == != < > <= >=");
it('should tokenize relation and equality', function() {
var tokens = lex("! == != < > <= >= === !==");
expect(tokens[0].text).toEqual('!');
expect(tokens[1].text).toEqual('==');
expect(tokens[2].text).toEqual('!=');
@@ -100,6 +100,8 @@ describe('parser', function() {
expect(tokens[4].text).toEqual('>');
expect(tokens[5].text).toEqual('<=');
expect(tokens[6].text).toEqual('>=');
expect(tokens[7].text).toEqual('===');
expect(tokens[8].text).toEqual('!==');
});
it('should tokenize statements', function() {
@@ -197,12 +199,20 @@ describe('parser', function() {
expect(scope.$eval("false")).toBeFalsy();
expect(scope.$eval("!true")).toBeFalsy();
expect(scope.$eval("1==1")).toBeTruthy();
expect(scope.$eval("1==true")).toBeTruthy();
expect(scope.$eval("1===1")).toBeTruthy();
expect(scope.$eval("1==='1'")).toBeFalsy();
expect(scope.$eval("1===true")).toBeFalsy();
expect(scope.$eval("'true'===true")).toBeFalsy();
expect(scope.$eval("1!==2")).toBeTruthy();
expect(scope.$eval("1!=='1'")).toBeTruthy();
expect(scope.$eval("1!=2")).toBeTruthy();
expect(scope.$eval("1<2")).toBeTruthy();
expect(scope.$eval("1<=1")).toBeTruthy();
expect(scope.$eval("1>2")).toEqual(1>2);
expect(scope.$eval("2>=1")).toEqual(2>=1);
expect(scope.$eval("true==2<3")).toEqual(true === 2<3);
expect(scope.$eval("true==2<3")).toEqual(true == 2<3);
expect(scope.$eval("true===2<3")).toEqual(true === 2<3);
});
it('should parse logical', function() {
@@ -661,6 +671,74 @@ describe('parser', function() {
expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
}));
});
describe('literal', function() {
it('should mark scalar value expressions as literal', inject(function($parse) {
expect($parse('0').literal).toBe(true);
expect($parse('"hello"').literal).toBe(true);
expect($parse('true').literal).toBe(true);
expect($parse('false').literal).toBe(true);
expect($parse('null').literal).toBe(true);
expect($parse('undefined').literal).toBe(true);
}));
it('should mark array expressions as literal', inject(function($parse) {
expect($parse('[]').literal).toBe(true);
expect($parse('[1, 2, 3]').literal).toBe(true);
expect($parse('[1, identifier]').literal).toBe(true);
}));
it('should mark object expressions as literal', inject(function($parse) {
expect($parse('{}').literal).toBe(true);
expect($parse('{x: 1}').literal).toBe(true);
expect($parse('{foo: bar}').literal).toBe(true);
}));
it('should not mark function calls or operator expressions as literal', inject(function($parse) {
expect($parse('1 + 1').literal).toBe(false);
expect($parse('call()').literal).toBe(false);
expect($parse('[].length').literal).toBe(false);
}));
});
describe('constant', function() {
it('should mark scalar value expressions as constant', inject(function($parse) {
expect($parse('12.3').constant).toBe(true);
expect($parse('"string"').constant).toBe(true);
expect($parse('true').constant).toBe(true);
expect($parse('false').constant).toBe(true);
expect($parse('null').constant).toBe(true);
expect($parse('undefined').constant).toBe(true);
}));
it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
expect($parse('[]').constant).toBe(true);
expect($parse('[1, 2, 3]').constant).toBe(true);
expect($parse('["string", null]').constant).toBe(true);
expect($parse('[[]]').constant).toBe(true);
expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
}));
it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
expect($parse('[foo]').constant).toBe(false);
expect($parse('[x + 1]').constant).toBe(false);
expect($parse('[bar[0]]').constant).toBe(false);
}));
it('should mark complex expressions involving constant values as constant', inject(function($parse) {
expect($parse('!true').constant).toBe(true);
expect($parse('1 - 1').constant).toBe(true);
expect($parse('"foo" + "bar"').constant).toBe(true);
expect($parse('5 != null').constant).toBe(true);
expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
}));
it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
expect($parse('true.toString()').constant).toBe(false);
expect($parse('foo(1, 2, 3)').constant).toBe(false);
expect($parse('"name" + id').constant).toBe(false);
}));
});
});
});
});
+16 -4
View File
@@ -99,6 +99,14 @@ describe('Scope', function() {
expect(spy).wasCalled();
}));
it('should not keep constant expressions on watch queue', inject(function($rootScope) {
$rootScope.$watch('1 + 1', function() {});
expect($rootScope.$$watchers.length).toEqual(1);
$rootScope.$digest();
expect($rootScope.$$watchers.length).toEqual(0);
}));
it('should delegate exceptions', function() {
module(function($exceptionHandlerProvider) {
@@ -119,10 +127,14 @@ describe('Scope', function() {
var log = '';
$rootScope.$watch('a', function() { log += 'a'; });
$rootScope.$watch('b', function() { log += 'b'; });
// constant expressions have slightly different handling,
// let's ensure they are kept in the same list as others
$rootScope.$watch('1', function() { log += '1'; });
$rootScope.$watch('c', function() { log += 'c'; });
$rootScope.$watch('2', function() { log += '2'; });
$rootScope.a = $rootScope.b = $rootScope.c = 1;
$rootScope.$digest();
expect(log).toEqual('abc');
expect(log).toEqual('ab1c2');
}));
@@ -216,7 +228,7 @@ describe('Scope', function() {
});
it('should prevent infinite recursion and print print watcher function name or body',
it('should prevent infinite recursion and print watcher function name or body',
inject(function($rootScope) {
$rootScope.$watch(function watcherA() {return $rootScope.a;}, function() {$rootScope.b++;});
$rootScope.$watch(function() {return $rootScope.b;}, function() {$rootScope.a++;});
@@ -328,7 +340,7 @@ describe('Scope', function() {
}));
it('should always call the watchr with newVal and oldVal equal on the first run',
it('should always call the watcher with newVal and oldVal equal on the first run',
inject(function($rootScope) {
var log = [];
function logger(scope, newVal, oldVal) {
@@ -457,7 +469,7 @@ describe('Scope', function() {
child.$evalAsync(function(scope) { log += 'child.async;'; });
child.$watch('value', function() { log += 'child.$digest;'; });
$rootScope.$digest();
expect(log).toEqual('parent.async;parent.$digest;child.async;child.$digest;');
expect(log).toEqual('parent.async;child.async;parent.$digest;child.$digest;');
}));
it('should cause a $digest rerun', inject(function($rootScope) {
+104
View File
@@ -59,6 +59,63 @@ describe('$route', function() {
});
});
it('should route and fire change event when catch-all params are used', function() {
var log = '',
lastRoute,
nextRoute;
module(function($routeProvider) {
$routeProvider.when('/Book1/:book/Chapter/:chapter/*highlight/edit',
{controller: noop, templateUrl: 'Chapter.html'});
$routeProvider.when('/Book2/:book/*highlight/Chapter/:chapter',
{controller: noop, templateUrl: 'Chapter.html'});
$routeProvider.when('/Blank', {});
});
inject(function($route, $location, $rootScope) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
log += 'before();';
expect(current).toBe($route.current);
lastRoute = current;
nextRoute = next;
});
$rootScope.$on('$routeChangeSuccess', function(event, current, last) {
log += 'after();';
expect(current).toBe($route.current);
expect(lastRoute).toBe(last);
expect(nextRoute).toBe(current);
});
$location.path('/Book1/Moby/Chapter/Intro/one/edit').search('p=123');
$rootScope.$digest();
$httpBackend.flush();
expect(log).toEqual('before();after();');
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one', p:'123'});
log = '';
$location.path('/Blank').search('ignore');
$rootScope.$digest();
expect(log).toEqual('before();after();');
expect($route.current.params).toEqual({ignore:true});
log = '';
$location.path('/Book1/Moby/Chapter/Intro/one/two/edit').search('p=123');
$rootScope.$digest();
expect(log).toEqual('before();after();');
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'});
log = '';
$location.path('/Book2/Moby/one/two/Chapter/Intro').search('p=123');
$rootScope.$digest();
expect(log).toEqual('before();after();');
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'});
log = '';
$location.path('/NONE');
$rootScope.$digest();
expect(log).toEqual('before();after();');
expect($route.current).toEqual(null);
});
});
it('should not change route when location is canceled', function() {
module(function($routeProvider) {
@@ -715,6 +772,53 @@ describe('$route', function() {
});
it('should allow using a function as a template', function() {
var customTemplateWatcher = jasmine.createSpy('customTemplateWatcher');
function customTemplateFn(routePathParams) {
customTemplateWatcher(routePathParams);
expect(routePathParams).toEqual({id: 'id3'});
return '<h1>' + routePathParams.id + '</h1>';
}
module(function($routeProvider){
$routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'});
$routeProvider.when('/foo/:id', {template: customTemplateFn});
});
inject(function($route, $location, $rootScope) {
$location.path('/foo/id3');
$rootScope.$digest();
expect(customTemplateWatcher).toHaveBeenCalledWith({id: 'id3'});
});
});
it('should allow using a function as a templateUrl', function() {
var customTemplateUrlWatcher = jasmine.createSpy('customTemplateUrlWatcher');
function customTemplateUrlFn(routePathParams) {
customTemplateUrlWatcher(routePathParams);
expect(routePathParams).toEqual({id: 'id3'});
return 'foo.html';
}
module(function($routeProvider){
$routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'});
$routeProvider.when('/foo/:id', {templateUrl: customTemplateUrlFn});
});
inject(function($route, $location, $rootScope) {
$location.path('/foo/id3');
$rootScope.$digest();
expect(customTemplateUrlWatcher).toHaveBeenCalledWith({id: 'id3'});
expect($route.current.loadedTemplateUrl).toEqual('foo.html');
});
});
describe('reload', function() {
it('should reload even if reloadOnSearch is false', function() {
+24 -6
View File
@@ -2,9 +2,10 @@
describe('$sniffer', function() {
function sniffer($window) {
function sniffer($window, $document) {
$window.navigator = {};
return new $SnifferProvider().$get[1]($window);
$document = jqLite($document || {});
return new $SnifferProvider().$get[2]($window, $document);
}
describe('history', function() {
@@ -20,15 +21,15 @@ describe('$sniffer', function() {
describe('hashchange', function() {
it('should be true if onhashchange property defined', function() {
expect(sniffer({onhashchange: true, document: {}}).hashchange).toBe(true);
expect(sniffer({onhashchange: true}, {}).hashchange).toBe(true);
});
it('should be false if onhashchange property not defined', function() {
expect(sniffer({document: {}}).hashchange).toBe(false);
expect(sniffer({}, {}).hashchange).toBe(false);
});
it('should be false if documentMode is 7 (IE8 comp mode)', function() {
expect(sniffer({onhashchange: true, document: {documentMode: 7}}).hashchange).toBe(false);
expect(sniffer({onhashchange: true}, {documentMode: 7}).hashchange).toBe(false);
});
});
@@ -42,7 +43,7 @@ describe('$sniffer', function() {
if (elm === 'div') return mockDivElement;
});
$sniffer = sniffer({document: mockDocument});
$sniffer = sniffer({}, mockDocument);
});
@@ -78,4 +79,21 @@ describe('$sniffer', function() {
expect($sniffer.hasEvent('input')).toBe((msie == 9) ? false : true);
});
});
describe('csp', function() {
it('should be false if document.securityPolicy.isActive not available', function() {
expect(sniffer({}, {}).csp).toBe(false);
});
it('should use document.securityPolicy.isActive if available', function() {
var createDocumentWithCSP = function(csp) {
return {securityPolicy: {isActive: csp}};
};
expect(sniffer({}, createDocumentWithCSP(false)).csp).toBe(false);
expect(sniffer({}, createDocumentWithCSP(true)).csp).toBe(true);
});
});
});
+24
View File
@@ -109,6 +109,13 @@ describe('ngMock', function() {
});
it('should fake getMilliseconds method', function() {
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.003Z').getMilliseconds()).toBe(3);
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getMilliseconds()).toBe(23);
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.123Z').getMilliseconds()).toBe(123);
});
it('should create a date representing new year in Bratislava', function() {
var newYearInBratislava = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z');
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
@@ -117,6 +124,7 @@ describe('ngMock', function() {
expect(newYearInBratislava.getDate()).toBe(1);
expect(newYearInBratislava.getHours()).toBe(0);
expect(newYearInBratislava.getMinutes()).toBe(0);
expect(newYearInBratislava.getSeconds()).toBe(0);
});
@@ -327,6 +335,22 @@ describe('ngMock', function() {
$timeout.flush();
expect(logger).toEqual(['t1', 't3', 't2']);
}));
it('should throw an exception when not flushed', inject(function($timeout){
$timeout(noop);
var expectedError = 'Deferred tasks to flush (1): {id: 0, time: 0}';
expect(function() {$timeout.verifyNoPendingTasks();}).toThrow(expectedError);
}));
it('should do nothing when all tasks have been flushed', inject(function($timeout) {
$timeout(noop);
$timeout.flush();
expect(function() {$timeout.verifyNoPendingTasks();}).not.toThrow();
}));
});
+297 -7
View File
@@ -14,7 +14,14 @@ describe("resource", function() {
},
patch: {
method: 'PATCH'
},
conditionalPut: {
method: 'PUT',
headers: {
'If-None-Match': '*'
}
}
});
callback = jasmine.createSpy();
}));
@@ -112,14 +119,33 @@ describe("resource", function() {
it('should not encode @ in url params', function() {
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
//with regards to the character set (pchar) allowed in path segments
//so we need this test to make sure that we don't over-encode the params and break stuff like
//buzz api which uses @self
//encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt
//with regards to the character set (pchar) allowed in path segments
//so we need this test to make sure that we don't over-encode the params and break stuff like
//buzz api which uses @self
var R = $resource('/Path/:a');
$httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}');
R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'});
});
it('should encode array params', function() {
var R = $resource('/Path/:a');
$httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}');
R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'});
$httpBackend.expect('GET', '/Path/doh&foo?bar=baz1&bar=baz2').respond('{}');
R.get({a: 'doh&foo', bar: ['baz1', 'baz2']});
});
it('should allow relative paths in resource url', function () {
var R = $resource(':relativePath');
$httpBackend.expect('GET', 'data.json').respond('{}');
R.get({ relativePath: 'data.json' });
});
it('should handle + in url params', function () {
var R = $resource('/api/myapp/:myresource?from=:from&to=:to&histlen=:histlen');
$httpBackend.expect('GET', '/api/myapp/pear+apple?from=2012-04-01&to=2012-04-29&histlen=3').respond('{}');
R.get({ myresource: 'pear+apple', from : '2012-04-01', to : '2012-04-29', histlen : 3 });
});
@@ -213,6 +239,15 @@ describe("resource", function() {
});
it('should send correct headers', function() {
$httpBackend.expectPUT('/CreditCard/123', undefined, function(headers) {
return headers['If-None-Match'] == "*";
}).respond({id:123});
CreditCard.conditionalPut({id: {key:123}});
});
it("should read partial resource", function() {
$httpBackend.expect('GET', '/CreditCard').respond([{id:{key:123}}]);
var ccs = CreditCard.query();
@@ -248,7 +283,7 @@ describe("resource", function() {
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
var ccs = CreditCard.query({key: 'value'}, callback);
expect(ccs).toEqual([]);
expect(ccs).toEqualData([]);
expect(callback).not.toHaveBeenCalled();
$httpBackend.flush();
@@ -365,6 +400,35 @@ describe("resource", function() {
});
it('should support dynamic default parameters (global)', function() {
var currentGroup = 'students',
Person = $resource('/Person/:group/:id', { group: function() { return currentGroup; }});
$httpBackend.expect('GET', '/Person/students/fedor').respond({id: 'fedor', email: 'f@f.com'});
var fedor = Person.get({id: 'fedor'});
$httpBackend.flush();
expect(fedor).toEqualData({id: 'fedor', email: 'f@f.com'});
});
it('should support dynamic default parameters (action specific)', function() {
var currentGroup = 'students',
Person = $resource('/Person/:group/:id', {}, {
fetch: {method: 'GET', params: {group: function() { return currentGroup; }}}
});
$httpBackend.expect('GET', '/Person/students/fedor').respond({id: 'fedor', email: 'f@f.com'});
var fedor = Person.fetch({id: 'fedor'});
$httpBackend.flush();
expect(fedor).toEqualData({id: 'fedor', email: 'f@f.com'});
});
it('should exercise full stack', function() {
var Person = $resource('/Person/:id');
@@ -375,6 +439,210 @@ describe("resource", function() {
});
describe('promise api', function() {
var $rootScope;
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
}));
describe('single resource', function() {
it('should add promise $then method to the result object', function() {
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
var cc = CreditCard.get({id: 123});
cc.$then(callback);
expect(callback).not.toHaveBeenCalled();
$httpBackend.flush();
var response = callback.mostRecentCall.args[0];
expect(response).toEqualData({
data: {id: 123, number: '9876'},
status: 200,
config: {method: 'GET', data: undefined, url: '/CreditCard/123'},
resource: {id: 123, number: '9876', $resolved: true}
});
expect(typeof response.resource.$save).toBe('function');
});
it('should keep $then around after promise resolution', function() {
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
var cc = CreditCard.get({id: 123});
cc.$then(callback);
$httpBackend.flush();
var response = callback.mostRecentCall.args[0];
callback.reset();
cc.$then(callback);
$rootScope.$apply(); //flush async queue
expect(callback).toHaveBeenCalledOnceWith(response);
});
it('should allow promise chaining via $then method', function() {
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
var cc = CreditCard.get({id: 123});
cc.$then(function(response) { return 'new value'; }).then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnceWith('new value');
});
it('should allow error callback registration via $then method', function() {
$httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found');
var cc = CreditCard.get({id: 123});
cc.$then(null, callback);
$httpBackend.flush();
var response = callback.mostRecentCall.args[0];
expect(response).toEqualData({
data : 'resource not found',
status : 404,
config : { method : 'GET', data : undefined, url : '/CreditCard/123' }
});
});
it('should add $resolved boolean field to the result object', function() {
$httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'});
var cc = CreditCard.get({id: 123});
expect(cc.$resolved).toBe(false);
cc.$then(callback);
expect(cc.$resolved).toBe(false);
$httpBackend.flush();
expect(cc.$resolved).toBe(true);
});
it('should set $resolved field to true when an error occurs', function() {
$httpBackend.expect('GET', '/CreditCard/123').respond(404, 'resource not found');
var cc = CreditCard.get({id: 123});
cc.$then(null, callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
expect(cc.$resolved).toBe(true);
});
});
describe('resource collection', function() {
it('should add promise $then method to the result object', function() {
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
var ccs = CreditCard.query({key: 'value'});
ccs.$then(callback);
expect(callback).not.toHaveBeenCalled();
$httpBackend.flush();
var response = callback.mostRecentCall.args[0];
expect(response).toEqualData({
data: [{id: 1}, {id :2}],
status: 200,
config: {method: 'GET', data: undefined, url: '/CreditCard', params: {key: 'value'}},
resource: [ { id : 1 }, { id : 2 } ]
});
expect(typeof response.resource[0].$save).toBe('function');
expect(typeof response.resource[1].$save).toBe('function');
});
it('should keep $then around after promise resolution', function() {
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
var ccs = CreditCard.query({key: 'value'});
ccs.$then(callback);
$httpBackend.flush();
var response = callback.mostRecentCall.args[0];
callback.reset();
ccs.$then(callback);
$rootScope.$apply(); //flush async queue
expect(callback).toHaveBeenCalledOnceWith(response);
});
it('should allow promise chaining via $then method', function() {
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
var ccs = CreditCard.query({key: 'value'});
ccs.$then(function(response) { return 'new value'; }).then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnceWith('new value');
});
it('should allow error callback registration via $then method', function() {
$httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found');
var ccs = CreditCard.query({key: 'value'});
ccs.$then(null, callback);
$httpBackend.flush();
var response = callback.mostRecentCall.args[0];
expect(response).toEqualData({
data : 'resource not found',
status : 404,
config : { method : 'GET', data : undefined, url : '/CreditCard', params: {key: 'value'}}
});
});
it('should add $resolved boolean field to the result object', function() {
$httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]);
var ccs = CreditCard.query({key: 'value'}, callback);
expect(ccs.$resolved).toBe(false);
ccs.$then(callback);
expect(ccs.$resolved).toBe(false);
$httpBackend.flush();
expect(ccs.$resolved).toBe(true);
});
it('should set $resolved field to true when an error occurs', function() {
$httpBackend.expect('GET', '/CreditCard?key=value').respond(404, 'resource not found');
var ccs = CreditCard.query({key: 'value'});
ccs.$then(null, callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
expect(ccs.$resolved).toBe(true);
});
});
});
describe('failure mode', function() {
var ERROR_CODE = 500,
ERROR_RESPONSE = 'Server Error',
@@ -407,4 +675,26 @@ describe("resource", function() {
expect(callback).not.toHaveBeenCalled();
});
});
it('should transform request/response', function() {
var Person = $resource('/Person/:id', {}, {
save: {
method: 'POST',
params: {id: '@id'},
transformRequest: function(data) {
return angular.toJson({ __id: data.id });
},
transformResponse: function(data) {
return { id: data.__id };
}
}
});
$httpBackend.expect('POST', '/Person/123', { __id: 123 }).respond({ __id: 456 });
var person = new Person({id:123});
person.$save();
$httpBackend.flush();
expect(person.id).toEqual(456);
});
});
+5 -2
View File
@@ -1,8 +1,11 @@
'use strict';
describe('ngBindHtml', function() {
beforeEach(module('ngSanitize'));
it('should set html', inject(function($rootScope, $compile) {
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
var element = $compile('<div ng-bind-html="html"></div>')($rootScope);
$rootScope.html = '<div unknown>hello</div>';
$rootScope.$digest();
expect(angular.lowercase(element.html())).toEqual('<div>hello</div>');
@@ -10,7 +13,7 @@ describe('ngBindHtml', function() {
it('should reset html when value is null or undefined', inject(function($compile, $rootScope) {
element = $compile('<div ng-bind-html="html"></div>')($rootScope);
var element = $compile('<div ng-bind-html="html"></div>')($rootScope);
angular.forEach([null, undefined, ''], function(val) {
$rootScope.html = 'some val';
+7
View File
@@ -24,4 +24,11 @@ describe('linky', function() {
expect(linky("send email to me@example.com, but")).
toEqual('send email to <a href="mailto:me@example.com">me@example.com</a>, but');
});
it('should handle target:', function() {
expect(linky("http://example.com", "_blank")).
toEqual('<a target="_blank" href="http://example.com">http://example.com</a>')
expect(linky("http://example.com", "someNamedIFrame")).
toEqual('<a target="someNamedIFrame" href="http://example.com">http://example.com</a>')
});
});
+3 -2
View File
@@ -188,7 +188,7 @@ describe('HTML', function() {
expect(html).toEqual('<div>');
});
describe('explicitly dissallow', function() {
describe('explicitly disallow', function() {
it('should not allow attributes', function() {
writer.start('div', {id:'a', name:'a', style:'a'});
expect(html).toEqual('<div>');
@@ -230,10 +230,11 @@ describe('HTML', function() {
expect(isUri('https://abc')).toBeTruthy();
expect(isUri('ftp://abc')).toBeTruthy();
expect(isUri('mailto:me@example.com')).toBeTruthy();
expect(isUri('tel:123-123-1234')).toBeTruthy();
expect(isUri('#anchor')).toBeTruthy();
});
it('should not be UIR', function() {
it('should not be URI', function() {
expect(isUri('')).toBeFalsy();
expect(isUri('javascript:alert')).toBeFalsy();
});
+62
View File
@@ -270,6 +270,16 @@ describe("angular.scenario.dsl", function() {
expect($root.futureError).toMatch(/did not match/);
});
it('should fail to select an option that does not exist', function(){
doc.append(
'<select ng-model="test">' +
' <option value=A>one</option>' +
' <option value=B selected>two</option>' +
'</select>'
);
$root.dsl.select('test').option('three');
expect($root.futureError).toMatch(/not found/);
});
});
describe('Element', function() {
@@ -305,6 +315,58 @@ describe("angular.scenario.dsl", function() {
dealoc(elm);
});
it('should execute dblclick', function() {
var clicked;
// Hash is important, otherwise we actually
// go to a different page and break the runner
doc.append('<a href="#"></a>');
doc.find('a').dblclick(function() {
clicked = true;
});
$root.dsl.element('a').dblclick();
});
it('should navigate page if dblclick on anchor', function() {
expect($window.location).not.toEqual('#foo');
doc.append('<a href="#foo"></a>');
$root.dsl.element('a').dblclick();
expect($window.location).toMatch(/#foo$/);
});
it('should not navigate if dblclick event was cancelled', function() {
var initLocation = $window.location,
elm = jqLite('<a href="#foo"></a>');
doc.append(elm);
elm.bind('dblclick', function(event) {
event.preventDefault();
});
$root.dsl.element('a').dblclick();
expect($window.location).toBe(initLocation);
dealoc(elm);
});
it('should execute mouseover', function() {
var mousedOver;
doc.append('<div></div>');
doc.find('div').mouseover(function() {
mousedOver = true;
});
$root.dsl.element('div').mouseover();
expect(mousedOver).toBe(true);
});
it('should bubble up the mouseover event', function() {
var mousedOver;
doc.append('<div id="outer"><div id="inner"></div></div>');
doc.find('#outer').mouseover(function() {
mousedOver = true;
});
$root.dsl.element('#inner').mouseover();
expect(mousedOver).toBe(true);
});
it('should count matching elements', function() {
doc.append('<span></span><span></span>');
$root.dsl.element('span').count();
+14 -1
View File
@@ -1,6 +1,6 @@
'use strict';
describe('angular.scenario.output.json', function() {
describe('angular.scenario.output.xml', function() {
var output, context;
var runner, model, $window;
var spec, step;
@@ -33,4 +33,17 @@ describe('angular.scenario.output.json', function() {
expect(context.find('it').attr('status')).toEqual('success');
expect(context.find('it step').attr('status')).toEqual('success');
});
it('should output errors to the XML', function() {
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepFailure', spec, step, 'error reason');
runner.emit('StepEnd', spec, step);
runner.emit('SpecEnd', spec);
runner.emit('RunnerEnd');
expect(context.find('it').attr('status')).toEqual('failure');
expect(context.find('it step').attr('status')).toEqual('failure');
expect(context.find('it step').text()).toEqual('error reason');
});
});
+2 -2
View File
@@ -13,7 +13,7 @@ var util = require('util');
var MAX_LENGTH = 70;
var PATTERN = /^(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/;
var PATTERN = /^(?:fixup!\s*)?(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/;
var IGNORED = /^WIP\:/;
var TYPES = {
feat: true,
@@ -51,7 +51,7 @@ var validateMessage = function(message) {
var match = PATTERN.exec(message);
if (!match) {
error('does not match "<type>(<scope>): <subject>" !');
error('does not match "<type>(<scope>): <subject>" ! was: ' + message);
return false;
}
+2 -1
View File
@@ -22,6 +22,7 @@ describe('validate-commit-msg.js', function() {
describe('validateMessage', function() {
it('should be valid', function() {
expect(m.validateMessage('fixup! fix($compile): something')).toBe(VALID);
expect(m.validateMessage('fix($compile): something')).toBe(VALID);
expect(m.validateMessage('feat($location): something')).toBe(VALID);
expect(m.validateMessage('docs($filter): something')).toBe(VALID);
@@ -50,7 +51,7 @@ describe('validate-commit-msg.js', function() {
var msg = 'not correct format';
expect(m.validateMessage(msg)).toBe(INVALID);
expect(errors).toEqual(['INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" !']);
expect(errors).toEqual(['INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" ! was: not correct format']);
});
+3 -3
View File
@@ -1,5 +1,5 @@
# AngularJS build config file
---
version: 1.0.4
codename: bewildering-hair
stable: 1.0.3
version: 1.1.3
codename: radioactive-gargle
stable: 1.0.4