Compare commits

...

68 Commits

Author SHA1 Message Date
Lucas Galfaso 2e5a7e52a0 fix($parse): handle null targets at assign
When assigning to a sub property of a property that its value is `null`
then write an empty object to the property that will contain the sub-property
2015-02-03 19:46:22 +01:00
Peter Bacon Darwin 5dfa630555 docs(CHANGELOG): update changelog for 1.4.0-beta.3 and 1.3.12 2015-02-02 20:27:32 +00:00
Peter Bacon Darwin 452d1cd66e chore(compare-master-to-stable): support beta release versions 2015-02-02 19:56:27 +00:00
Shahar Talmi c6d8512a1d feat(filter): support conversion to timezone other than UTC
Closes #10858
2015-02-02 14:57:42 +00:00
Richard Zschech 8a2c80ce7f style(*) add curly braces to multiline if and for statements
Closes #10865
2015-02-02 14:48:55 +00:00
Chris 2f3633d68f docs(guide): update "AngularJS" book link
Added latest 2014 revision "AngularJS: Up and Running" with Amazon link.
"AngularJS" was previous 2013 version.

Closes #10920
2015-02-02 14:03:03 +00:00
Hannah Howard 6c6a4086b7 fix ($compile): keep prototype properties for template URL directives
Previously, if a directive definition object was defined with methods like `compile`
provided on the prototype rather than the instance, the Angular compiler failed
to use these methods when the directive had a `templateURL`. This change ensures
that these prototypical methods are not lost.

This enables developers to define their directives using "classes" such as
in CoffeeScript or ES6.

Closes #10926
2015-02-02 11:49:14 +00:00
Lucas Galfaso 0d424263ea refactor($parse): new and more performant $parse
Change the way parse works from the old mechanism to a multiple stages
parsing and code generation. The new parse is a four stages parsing
* Lexer
* AST building
* AST processing
* Cacheing, one-time binding and `$watch` optimizations

The Lexer phase remains unchanged.

AST building phase follows Mozilla Parse API [1] and generates an AST that
is compatible. The only exception was needed for `filters` as JavaScript
does not support filters, in this case, a filter is transformed into a
`CallExpression` that has an extra property named `filter` with the value
of `true`.

The AST processing phase transforms the AST into a function that can be
executed to evaluate the expression. The logic for expressions remains
unchanged. The AST processing phase works in two different ways depending
if csp is enabled or disabled. If csp is enabled, the processing phase
returns pre-generated function that interpret specific parts of the AST.
When csp is disabled, then the entire expression is compiled into a single
function that is later evaluated using `Function`. In both cases, the
returning function has the properties `constant`, `literal` and `inputs`
as in the previous implementation. These are used in the next phase to
perform different optimizations.

The cacheing, one-time binding and `$watch` optimizations phase remains
mostly unchanged.

[1] https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API
2015-01-31 11:19:09 +01:00
Peter Bacon Darwin 2003fcf0de chore(version-info): silence the HTTP curl to get the cdn version 2015-01-30 21:55:46 +00:00
Peter Bacon Darwin 3130a82b21 chore(version-info): remove console.logs and fix code style 2015-01-30 21:25:14 +00:00
Caitlin Potter 6a38dbfd3c fix($compile): do not initialize optional '&' binding if attribute not specified
BREAKING CHANGE:

Previously, '&' expressions would always set up a function in the isolate scope. Now, if the binding
is marked as optional and the attribute is not specified, no function will be added to the isolate scope.

Closes #6404
Closes #9216
2015-01-30 21:25:14 +00:00
Henry Zhu 30e5b52344 style(*): add jscs rule disallowKeywordsOnNewLine: "else" 2015-01-30 12:46:55 +00:00
Henry Zhu ebde4681bd style(*): add jscs rule requireSpaceBeforeKeywords
Closes #10772
2015-01-30 12:36:19 +00:00
Henry Zhu ad68a41e88 style(*): add jscs rule requireSpacesInForStatement
Closes #10772
2015-01-30 12:36:19 +00:00
Henry Zhu 1f3ab484a7 style(*): add jscs rule disallowSpacesInCallExpression
Closes #10772
2015-01-30 12:36:18 +00:00
Henry Zhu d9498a173c style(*): add jscs rule disallowKeywordsOnNewLine: "else"
Closes #10772
2015-01-30 12:36:18 +00:00
Peter Bacon Darwin 29ad3b7f36 chore(npm): update grunt-jscs to 1.2.0 (jscs to 1.10.0), fix styles
Closes #10772
2015-01-30 12:36:18 +00:00
Lucas Galfaso c1500ea775 perf($scope): Add a property $$watchersCount to scope
Add a property $$watchersCount to scope that keeps the number of
watchers in the scope plus all the child scopes. Use this property
when traversing scopes looking for watches

Closes: #5799
2015-01-30 12:29:24 +01:00
Peter Bacon Darwin 4bc89bfe6d chore(doc-gen): update to dgeni-packages 0.10.8
Closes https://github.com/angular/dgeni-packages/pull/105
2015-01-30 10:53:34 +00:00
Rohit Kandhal 54097f63d5 docs(tutorial/step-11): update link to Jasmine matchers
Closes #10909
2015-01-29 22:33:26 +00:00
Ian Young 2264413beb docs(ngHide): use proper selector when overriding the CSS
The animation selector gives the default styles greater specificity that
should be matched when overriding.

Closes #10902
Closes #10913
2015-01-29 22:13:35 +00:00
Martin Staffa 8a6cbb3c94 test(validators): minlength and required must use viewValue in $isEmpty 2015-01-29 23:06:24 +01:00
Martin Staffa bfcf9946e1 fix(validators): maxlength should use viewValue for $isEmpty
Closes #10898
2015-01-29 23:06:23 +01:00
Caitlin Potter b462f5dcf2 docs($compile,$route): reword "controller alias" to clarify
Clarify that these aliases are identifier names used to reference the controller.
2015-01-29 16:48:06 -05:00
Caitlin Potter bb1c379b36 docs($compile): create new error for "missing controller identifier" 2015-01-29 16:48:06 -05:00
Caitlin Potter 62d514b069 fix($compile): respect return value from controller constructor
The return value of the controller constructor is now respected in all cases.

If controllerAs is used, the controller will be re-bound to scope. If bindToController is used,
the previous binding $watches (if any) will be unwatched, and bindings re-installed on the new
controller.
2015-01-29 16:48:06 -05:00
Caitlin Potter 35498d7045 feat($compile): allow using bindToController as object, support both new/isolate scopes
bindToController is now able to be specified as a convenient object notation:

```
bindToController: {
  text: '@text',
  obj: '=obj',
  expr: '&expr'
},
scope: {}
```

It can also be used in conjunction with new scopes, rather than exclusively isolate scopes:

```
bindToController: {
  text: '@text',
  obj: '=obj',
  expr: '&expr'
},
scope: true
```

Closes #10420
Closes #10467
2015-01-29 16:47:30 -05:00
Caitlin Potter 630b80fc00 chore($controller): don't use new for minErr instance
minErr creates a new error anyways, it's not meant to be called as a constructor.
2015-01-29 16:26:54 -05:00
Martin Staffa a80d9449b7 docs(guide/production): clarification in disabling debug data
Closes #10762
2015-01-29 19:59:32 +01:00
Caitlin Potter dda65e992b fix($controller): throw better error when controller expression is bad
Previously, the error was a JS runtime error when trying to access a property of `null`. But, it's
a bit nicer to throw a real error and provide a description of how to fix it. Developer ergonomics
and all that.

Closes #10875
Closes #10910
2015-01-29 12:52:25 -05:00
Justin Schiff aa0f64496a fix(angular.copy): support copying %TypedArray%s
angular.copy can now copy a %TypedArray%s.

Limitations: It is not possible to update the length of a %TypedArray%, so currently an error is thrown
if the destination object is a %TypedArray%. However, it is possible to change values in a typed array,
so in the future this may only be a problem if the length of the source and destination is different.

Closes #10745
2015-01-29 12:17:10 -05:00
Vojta Jina e61eae1b1f fix($parse): remove references to last arguments to a fn call
This can be an issue if running (and killing) multiple apps/injectors on
the same page. The `args` array holds references to all previous arguments
to a function call and thus they cannot be garbage-collected.

In a regular (one app/injector on a page) app, this is not an issue.

Closes #10894
2015-01-29 14:44:42 +00:00
Wes Alvaro 400fbbf1d6 routeParams are {!Object<string, string>}
Calling Object.keys on null is a TypeError. And the mapping should be string -> string.

Closes #10896
2015-01-29 14:38:16 +00:00
Owen Smith 27bf2ce40c fix(ngRoute): dont duplicate optional params into query
When calling updateParams with properties which were optional, but
previously undefined, they would be duplicated into the query params as
well as into the path.

Closes #10689
2015-01-29 10:25:57 +00:00
marc c69caa7bee fix(ngScenario): Allow ngScenario to handle lazy-loaded and manually bootstrapped applications
I know protractor is preferred, and ngScenario is only in maintenance mode. But, we are limited to
ngScenario based on the devices/browsers we are targeting (no web-driver available). So, we need
to address the bug where ngScenario does not work with manual bootstrap and also has issues if
angular.resumeBootstrap is not yet defined (race condition when lazy-loading).

Closes #10723
2015-01-29 10:15:45 +00:00
Shahar Talmi 8c46919199 fix(filter): format timezone correctly
This fixes timezone formatting in case UTC timezone is used

Closes #9359
2015-01-29 01:18:22 +02:00
Peter Bacon Darwin d729fcf030 fix(a): don't reload if there is only a name attribute
Closes #6273
Closes #10880
2015-01-28 14:30:51 +00:00
Shahar Talmi 0baa17a3b7 feat(ngMocks): cleanup $inject annotations after each test
this will help in detecting unannotated functions both in apps and angular core in case only part of the tests are run with strictDi
2015-01-28 13:47:46 +00:00
Pablo Villoslada Puigcerber 2ece1c927b docs(notarray): add error example and code blocks with suggested fixes
Add code examples for the error and the suggested fixes.
Followup of #10352.

Closes #10872
2015-01-27 21:58:57 +01:00
Alex Yursha 7602cd5e7e docs(guide/location): replace invalid link
Closes #10882
2015-01-27 21:25:21 +01:00
Jason Bedard 560951e988 refactor($interpolate): move standalone functions to the service scope
Closes #10413
2015-01-27 15:42:11 +01:00
Agrumas 1735d5e8d5 chore(i18n): regenerate locales due to closure library update
This includes change to the currency symbol of Lithuania.

Closes #10855
Closes #10856
2015-01-27 10:30:55 +00:00
Peter Bacon Darwin c88b119ef5 chore(i18n): update closure library to latest
This includes changed to Lithuanian currency and Mynamar Burmese date formats

Closes #10855
Closes #10856
2015-01-27 10:27:46 +00:00
Caitlin Potter 6cf6a1b975 docs(CHANGELOG.md): remove reverted form change 2015-01-26 18:57:50 -05:00
Jeff Cross 76df116574 revert: fix(form): ignore properties in $error prototype chain
This reverts commit 31a5b8353a.
2015-01-26 14:50:48 -08:00
Caitlin Potter 440c122556 docs(CHANGELOG.md): add changelog notes for v1.3.11 and v1.4.0-beta.2
Closes #10876
2015-01-26 16:11:54 -05:00
cmsdkfz f7114d0c1c refactor($q): change variable name
Change the variable name to be more accurate describing what it stands for.
2015-01-26 11:04:44 +01:00
Caitlin Potter 2958cd308b fix(htmlAnchorDirective): remove "element !== target element" check
It's not really needed due to the way click events are dispatched and propagated

Closes #10866
2015-01-25 23:39:54 -05:00
Vinti Maheshwari 9f7c5ceba7 chore(gruntFile): ensure build is run before test:modules
Closes #10188
2015-01-25 02:33:09 +00:00
Peter Bacon Darwin 2f32614378 test($location): ensure that link rewriting is actually being tested
If the link URL is not within the given base URL then the link would not
be rewritten anyway.

See https://github.com/angular/angular.js/pull/9906/files#r19813651
2015-01-25 00:35:22 +00:00
Caitlin Potter 8b33de6fd0 fix($location): don't rewrite when link is shift-clicked
Closes #9904
Closes #9906
2015-01-25 00:34:35 +00:00
Peter Bacon Darwin d40749caab docs(input): update example to use ngModel best practices
Update the rest of the directives to use object properties for models.

Closes #10851
2015-01-24 22:41:29 +00:00
Mark Hoffmeyer e13224b2df docs(input[checkbox]): update example to use ngModel best practices
It's not required for the example to function, but it prevents scope weirdness/unexpected
behavior when using directives (especially with ngTransclude!). I think it's a good pattern
to encourage and might prevent a bug down the road for for people who just scan for the
monospace font. See
[Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
for mgModel best practices.

Closes #10851
2015-01-24 22:40:54 +00:00
Marcin Wosinek 36a14e65d1 docs(select): link to ngOptions
Closes #10854
2015-01-24 19:20:16 +01:00
Georgios Kalpakas 3228d3b499 fix(ngPluralize): fix wrong text content when count is null/undefined
When `lastCount` was evaluated to an non-numeric value (e.g. "other") and
`count` was evaluated to `NaN` (e.g. `null`/`undefined`), the text content
would be (wrongly) based on the previous template.
This commits makes sure the text content is updated correctly.

In order to customize the message shown upon `null`/`undefined` one can
specify a `'NaN'` property on the `when` exression object.

Closes #10836

Closes #10841
2015-01-24 14:01:33 +01:00
Jake Harclerode 6d173aeb5d docs($httpBackend): enhance readability
Closes #10852
2015-01-24 12:46:28 +01:00
samilamti d0ceeaa37e docs(guide/Introduction): define CRUD and add punctuation
Added description of what CRUD means.
Improved readability: Ensured that colons were followed by a capital letter
and added some sprinkled commas.

Closes #10804
2015-01-24 10:57:37 +00:00
Caitlin Potter 301663e734 fix(openPlunkr): enable cmd+click for us mac users :> 2015-01-23 16:47:19 -05:00
Mike Haggerty dca5fa7b81 feat(openPlunkr): enable ctrl+click
This change allows users to ctrl+click on the "Edit in Plunker"
button which will set the posted form's target attribute to
"_blank" instead of "_self" which is the default.

Closes #10641
Closes #10826
2015-01-23 16:47:19 -05:00
Rouven Weßling d557875a8d test($rootScope) test the correct setting of the constructor in Internet Explorer 11
Closes #10759
2015-01-23 21:32:35 +00:00
Caitlin Potter b146af1127 fix(htmlAnchorDirective): don't add event listener if replaced, ignore event if target is different element
Previously, when an `a` tag element used a directive with a replacing template, and did not include an `href` or `name` attribute
before linkage, the anchor directive would always prevent default.

Now, the anchor directive will not register an event listener at all if the original directive is replaced with a non-anchor, and
will ignore events which do not target the linked element.

Closes #4262
Closes #10849
2015-01-23 15:39:59 -05:00
Pablo Villoslada Puigcerber cea8e75144 fix(filterFilter): throw error if input is not an array
Throw error if filter is not used with an array.

BREAKING CHANGE: Previously, the filter was not applied if used with a non array.
Now, it throws an error. This can be worked around by converting an object to an array, using
a filter such as https://github.com/petebacondarwin/angular-toArrayFilter

Closes #9992
Closes #10352
2015-01-23 14:21:58 -05:00
Pawel Kozlowski b3a9bd3ae0 fix($templateRequest): cache downloaded templates as strings
Fixes #10630
Closes #10646
2015-01-23 18:43:26 +01:00
Caitlin Potter 31a5b8353a fix(form): ignore properties in $error prototype chain
Closes #10469
Closes #10727
2015-01-23 14:37:17 +00:00
Rouven Weßling 301e7aae24 refactor(Angular): inline the only call to sortedKeys()
Closes #10757
2015-01-21 21:21:44 +01:00
Nick Van Dyck f2e2b31ece docs(ngMessages): fix typo
Closes #10821
2015-01-21 20:18:42 +01:00
Boshen Chen 331cac233f docs(ngInit): fix code block not being displayed in the note section
Closes #10791
2015-01-20 21:34:43 +01:00
Pawel Kozlowski 7e3557e96b docs(CHANGELOG): add changes for 1.3.10 and 1.4.0-beta.1 2015-01-20 20:43:54 +01:00
85 changed files with 5198 additions and 1281 deletions
+9 -2
View File
@@ -1,6 +1,7 @@
{
"excludeFiles": ["src/ngLocale/**"],
"disallowKeywords": ["with"],
"disallowKeywordsOnNewLine": ["else"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineStrings": true,
"disallowNewlineBeforeBlockStatements": true,
@@ -11,6 +12,7 @@
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInCallExpression": true,
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
@@ -18,6 +20,11 @@
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"requireSpaceBeforeKeywords": [
"else",
"while",
"catch"
],
"disallowSpacesInsideParentheses": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
@@ -33,9 +40,9 @@
"afterConsequent": true,
"beforeAlternate": true
},
"requireSpacesInForStatement": true,
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"validateLineBreaks": "LF",
"validateParameterSeparator": ", "
"validateLineBreaks": "LF"
}
+1 -1
View File
@@ -4,10 +4,10 @@
// that correct the existing code base issues and make the new check pass.
{
"validateParameterSeparator": ", ", // Re-assert this rule when JSCS allows multiple spaces
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowKeywordsOnNewLine": ["else"],
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
+223
View File
@@ -1,3 +1,226 @@
<a name="1.4.0-beta.3"></a>
# 1.4.0-beta.3 substance-mimicry (2015-02-02)
## Bug Fixes
- **$compile:**
- do not initialize optional '&' binding if attribute not specified
([6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
[#6404](https://github.com/angular/angular.js/issues/6404), [#9216](https://github.com/angular/angular.js/issues/9216))
- respect return value from controller constructor
([62d514b0](https://github.com/angular/angular.js/commit/62d514b06937cc7dd86e973ea11165c88343b42d))
- **$controller:** throw better error when controller expression is bad
([dda65e99](https://github.com/angular/angular.js/commit/dda65e992b72044c0fa0c8f5f33184028c0e3ad7),
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
- **$parse:** remove references to last arguments to a fn call
([e61eae1b](https://github.com/angular/angular.js/commit/e61eae1b1f2351c51bcfe4142749a4e68a2806ff),
[#10894](https://github.com/angular/angular.js/issues/10894))
- **a:** don't reload if there is only a name attribute
([d729fcf0](https://github.com/angular/angular.js/commit/d729fcf030be1d3ef37196d36ea3bf3249ee3318),
[#6273](https://github.com/angular/angular.js/issues/6273), [#10880](https://github.com/angular/angular.js/issues/10880))
- **angular.copy:** support copying `TypedArray`s
([aa0f6449](https://github.com/angular/angular.js/commit/aa0f64496a66d2a5d1a4d033f2eb075a8b084a78),
[#10745](https://github.com/angular/angular.js/issues/10745))
- **filter:** format timezone correctly in the case that UTC timezone is used
([8c469191](https://github.com/angular/angular.js/commit/8c46919199090a05634789774124b38983430c76),
[#9359](https://github.com/angular/angular.js/issues/9359))
- **ngRoute:** dont duplicate optional params into query
([27bf2ce4](https://github.com/angular/angular.js/commit/27bf2ce40c5adfb1494d69c9d0ac9cf433834a12),
[#10689](https://github.com/angular/angular.js/issues/10689))
- **ngScenario:** allow ngScenario to handle lazy-loaded and manually bootstrapped applications
([c69caa7b](https://github.com/angular/angular.js/commit/c69caa7beee4e920f8f587eb3e943be99864a14f),
[#10723](https://github.com/angular/angular.js/issues/10723))
- **validators:** maxlength should use viewValue for $isEmpty
([bfcf9946](https://github.com/angular/angular.js/commit/bfcf9946e16d21b55dde50d4d21c71c898b10215),
[#10898](https://github.com/angular/angular.js/issues/10898))
## Features
- **$compile:** allow using bindToController as object, support both new/isolate scopes
([35498d70](https://github.com/angular/angular.js/commit/35498d7045ba9138016464a344e2c145ce5264c1),
[#10420](https://github.com/angular/angular.js/issues/10420), [#10467](https://github.com/angular/angular.js/issues/10467))
- **ngMocks:** cleanup $inject annotations after each test
([0baa17a3](https://github.com/angular/angular.js/commit/0baa17a3b7ad2b242df2b277b81cebdf75b04287))
## Performance Improvements
- **$scope:** Add a property $$watchersCount to scope
([c1500ea7](https://github.com/angular/angular.js/commit/c1500ea775c4cb130088b7d5bb5fb872bda50bae))
## Breaking Changes
- **$compile:** due to [6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
Previously, '&' expressions would always set up a function in the isolate scope. Now, if the binding
is marked as optional and the attribute is not specified, no function will be added to the isolate scope.
<a name="1.3.12"></a>
# 1.3.12 outlandish-knitting (2015-02-02)
## Bug Fixes
- **$controller:** throw better error when controller expression is bad
([632b2ddd](https://github.com/angular/angular.js/commit/632b2ddd34c07b3b5a207bd83ca3a5e6e613e63b),
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
- **$parse:** remove references to last arguments to a fn call
([7caad220](https://github.com/angular/angular.js/commit/7caad2205a6e9927890192a3638f55532bdaaf75),
[#10894](https://github.com/angular/angular.js/issues/10894))
- **ngRoute:** dont duplicate optional params into query
([f41ca4a5](https://github.com/angular/angular.js/commit/f41ca4a53ed53f172fb334911be56e42aad58794),
[#10689](https://github.com/angular/angular.js/issues/10689))
- **ngScenario:** Allow ngScenario to handle lazy-loaded and manually bootstrapped applications
([0bcd0872](https://github.com/angular/angular.js/commit/0bcd0872d8d2e37e6cb7aa5bc5cb0c742b4294f9),
[#10723](https://github.com/angular/angular.js/issues/10723))
- **validators:** maxlength should use viewValue for $isEmpty
([abd8e2a9](https://github.com/angular/angular.js/commit/abd8e2a9eb2d21ac67989c2f7b64c4c6547a1585),
[#10898](https://github.com/angular/angular.js/issues/10898))
## Features
- **ngMocks:** cleanup $inject annotations after each test
([6ec59460](https://github.com/angular/angular.js/commit/6ec5946094ee92b820bbacc886fa2367715e60b4))
<a name="1.4.0-beta.2"></a>
# 1.4.0-beta.2 holographic-rooster (2015-01-26)
## Bug Fixes
- **$location:** don't rewrite when link is shift-clicked
([8b33de6f](https://github.com/angular/angular.js/commit/8b33de6fd0ec0eb785fed697f062763b5c1d8d23),
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
- **$templateRequest:** cache downloaded templates as strings
([b3a9bd3a](https://github.com/angular/angular.js/commit/b3a9bd3ae043e3042ea7ccfe08e3b36a84feb35e),
[#10630](https://github.com/angular/angular.js/issues/10630), [#10646](https://github.com/angular/angular.js/issues/10646))
- **filterFilter:** throw error if input is not an array
([cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
[#9992](https://github.com/angular/angular.js/issues/9992), [#10352](https://github.com/angular/angular.js/issues/10352))
- **htmlAnchorDirective:**
- remove "element !== target element" check
([2958cd30](https://github.com/angular/angular.js/commit/2958cd308b5ebaf223a3e5df3fb5bf0f23408447),
[#10866](https://github.com/angular/angular.js/issues/10866))
- don't add event listener if replaced, ignore event if target is different element
([b146af11](https://github.com/angular/angular.js/commit/b146af11271de8fa4c51c6db87df104269f41a33),
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
- **ngPluralize:** fix wrong text content when count is null/undefined
([3228d3b4](https://github.com/angular/angular.js/commit/3228d3b4991af681e57de5ab079c1e1c11cf35cb),
[#10836](https://github.com/angular/angular.js/issues/10836), [#10841](https://github.com/angular/angular.js/issues/10841))
## Breaking Changes
- **filterFilter:** due to [cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
Previously, the filter was not applied if used with a non array.
Now, it throws an error. This can be worked around by converting an object to an array, using
a filter such as https://github.com/petebacondarwin/angular-toArrayFilter
Closes #9992
Closes #10352
<a name="1.3.11"></a>
# 1.3.11 spiffy-manatee (2015-01-26)
## Bug Fixes
- **$location:** don't rewrite when link is shift-clicked
([939ca37c](https://github.com/angular/angular.js/commit/939ca37cfe5f6fc35b09b6705caabd1fcc3cf9d3),
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
- **htmlAnchorDirective:**
- remove "element !== target element" check
([779e3f6b](https://github.com/angular/angular.js/commit/779e3f6b5f8d2550e758cb0c5f64187ba8e00e29),
[#10866](https://github.com/angular/angular.js/issues/10866))
- don't add event listener if replaced, ignore event if target is different element
([837a0775](https://github.com/angular/angular.js/commit/837a077578081bbd07863bef85241537d19fa652),
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
<a name="1.4.0-beta.1"></a>
# 1.4.0-beta.1 trepidatious-salamander (2015-01-20)
## Bug Fixes
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
([0db5b21b](https://github.com/angular/angular.js/commit/0db5b21b1d09431535e0c0bf8ac63d4b5b24d349),
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
- **$compile:** support class directives on SVG elements
([23c8a90d](https://github.com/angular/angular.js/commit/23c8a90d22f7c7b41b5a756b89498ffac828980a),
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
- **form:** clean up success state of controls when they are removed
([2408f2de](https://github.com/angular/angular.js/commit/2408f2ded5ead6e678c241e38ef474c1fadff92b),
[#10509](https://github.com/angular/angular.js/issues/10509))
- **ngController:** allow bound constructor fns as controllers
([d17fbc38](https://github.com/angular/angular.js/commit/d17fbc3862e0a2e646db1222f184dbe663da4a1f),
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
- **ngRepeat:** do not sort object keys alphabetically
([c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
[#6210](https://github.com/angular/angular.js/issues/6210), [#10538](https://github.com/angular/angular.js/issues/10538))
## Features
- **$http:** provide a config object as an argument to header functions
([d435464c](https://github.com/angular/angular.js/commit/d435464c51d3912f56cfc830d86bfc64a1578327),
[#7235](https://github.com/angular/angular.js/issues/7235), [#10622](https://github.com/angular/angular.js/issues/10622))
## Breaking Changes
- **ngRepeat:** due to [c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
Previously, the order of items when using ngRepeat to iterate
over object properties was guaranteed to be consistent by sorting the
keys into alphabetic order.
Now, the order of the items is browser dependent based on the order returned
from iterating over the object using the `for key in obj` syntax.
It seems that browsers generally follow the strategy of providing
keys in the order in which they were defined, although there are exceptions
when keys are deleted and reinstated. See
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
The best approach is to convert Objects into Arrays by a filter such as
https://github.com/petebacondarwin/angular-toArrayFilter
or some other mechanism, and then sort them manually in the order you need.
Closes #6210
Closes #10538
<a name="1.3.10"></a>
# 1.3.10 heliotropic-sundial (2015-01-20)
## Bug Fixes
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
([9b8df52a](https://github.com/angular/angular.js/commit/9b8df52aa960b9b6288fc150d55ea2e35f56555e),
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
- **$compile:** support class directives on SVG elements
([7a9e3360](https://github.com/angular/angular.js/commit/7a9e3360284d58197a1fe34de57f5e0f6d1f4a76),
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
- **form:** clean up success state of controls when they are removed
([cdc7280d](https://github.com/angular/angular.js/commit/cdc7280dd3d5a2ded784c06dd55fe36c2053fb6f),
[#10509](https://github.com/angular/angular.js/issues/10509))
- **ngController:** allow bound constructor fns as controllers
([d015c8a8](https://github.com/angular/angular.js/commit/d015c8a80b28754633c846fc50d11c9437519486),
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
<a name="1.4.0-beta.0"></a>
# 1.4.0-beta.0 photonic-umbrakinesis (2015-01-13)
+2 -2
View File
@@ -316,9 +316,9 @@ module.exports = function(grunt) {
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['tests:modules']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']);
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['tests:jqlite', 'tests:jquery', 'tests:modules']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:modules']);
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']);
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
+9 -2
View File
@@ -14,6 +14,7 @@
<div>ngBind: <input type="radio" ng-model="benchmarkType" value="ngBind"></div>
<div>ngBindOnce: <input type="radio" ng-model="benchmarkType" value="ngBindOnce"></div>
<div>interpolation: <input type="radio" ng-model="benchmarkType" value="interpolation"></div>
<div>interpolation + bind-once: <input type="radio" ng-model="benchmarkType" value="bindOnceInterpolation"></div>
<div>attribute interpolation: <input type="radio" ng-model="benchmarkType" value="interpolationAttr"></div>
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
@@ -35,7 +36,7 @@
</div>
<div ng-switch-when="ngBindOnce">
<h2>baseline binding once</h2>
<div ng-repeat="row in data">
<div ng-repeat="row in ::data">
<span ng-repeat="column in ::row">
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
</span>
@@ -47,6 +48,12 @@
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
</div>
</div>
<div ng-switch-when="bindOnceInterpolation">
<h2>baseline one-time interpolation</h2>
<div ng-repeat="row in ::data">
<span ng-repeat="column in ::row">{{::column.i}}:{{::column.j}}|</span>
</div>
</div>
<div ng-switch-when="interpolationAttr">
<h2>attribute interpolation</h2>
<div ng-repeat="row in data">
@@ -80,4 +87,4 @@
</ng-switch>
</div>
</div>
</div>
</div>
+2 -2
View File
@@ -103,10 +103,10 @@ then(function (tags) {
sort(semver.rcompare);
}).
then(function (tags) {
var major = tags[0].split('.')[0] + '.x';
var major = tags[0].split('.')[0];
return tags.
filter(function (ver) {
return semver.satisfies(ver, major);
return semver(ver).major == major;
});
}).
then(function (tags) {
+11 -7
View File
@@ -1,13 +1,16 @@
angular.module('examples', [])
.factory('formPostData', ['$document', function($document) {
return function(url, fields) {
return function(url, newWindow, fields) {
/**
* Form previously posted to target="_blank", but pop-up blockers were causing this to not work.
* If a user chose to bypass pop-up blocker one time and click the link, they would arrive at
* a new default plnkr, not a plnkr with the desired template.
* If the form posts to target="_blank", pop-up blockers can cause it not to work.
* If a user choses to bypass pop-up blocker one time and click the link, they will arrive at
* a new default plnkr, not a plnkr with the desired template. Given this undesired behavior,
* some may still want to open the plnk in a new window by opting-in via ctrl+click. The
* newWindow param allows for this possibility.
*/
var form = angular.element('<form style="display: none;" method="post" action="' + url + '"></form>');
var target = newWindow ? '_blank' : '_self';
var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="' + target + '"></form>');
angular.forEach(fields, function(value, name) {
var input = angular.element('<input type="hidden" name="' + name + '">');
input.attr('value', value);
@@ -21,9 +24,10 @@ angular.module('examples', [])
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
return function(exampleFolder) {
return function(exampleFolder, clickEvent) {
var exampleName = 'AngularJS Example';
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
// Load the manifest for the example
$http.get(exampleFolder + '/manifest.json')
@@ -71,7 +75,7 @@ angular.module('examples', [])
postData.private = true;
postData.description = exampleName;
formPostData('http://plnkr.co/edit/?p=preview', postData);
formPostData('http://plnkr.co/edit/?p=preview', newWindow, postData);
});
};
}]);
@@ -2,7 +2,7 @@
is HTML and wrap each line in a <p> - thus breaking the HTML #}
<div>
<a ng-click="openPlunkr('{$ doc.path $}')" class="btn pull-right">
<a ng-click="openPlunkr('{$ doc.path $}', $event)" class="btn pull-right">
<i class="glyphicon glyphicon-edit">&nbsp;</i>
Edit in Plunker</a>
+12
View File
@@ -0,0 +1,12 @@
@ngdoc error
@name $compile:noctrl
@fullName Controller is required.
@description
When using the `bindToController` feature of AngularJS, a directive is required
to have a Controller. A controller may be specified by adding a "controller"
property to the directive definition object. Its value should be either a
string, or an invokable object (a function, or an array whose last element is a
function).
For more information, see the {@link guide/directive directives guide}.
+71
View File
@@ -0,0 +1,71 @@
@ngdoc error
@name $compile:noident
@fullName Controller identifier is required.
@description
When using the `bindToController` feature of AngularJS, a directive is required
to have a Controller identifier, which is initialized in scope with the value of
the controller instance. This can be supplied using the "controllerAs" property
of the directive object, or alternatively by adding " as IDENTIFIER" to the controller
name.
For example, the following directives are valid:
```js
// OKAY, because controller is a string with an identifier component.
directive("okay", function() {
return {
bindToController: true,
controller: "myCtrl as $ctrl"
scope: {
text: "@text"
}
};
});
// OKAY, because the directive uses the controllerAs property to override
// the controller identifier.
directive("okay2", function() {
return {
bindToController: true,
controllerAs: "$ctrl",
controller: function() {
},
scope: {
text: "@text"
}
};
});
```
While the following are invalid:
```js
// BAD, because the controller property is a string with no identifier.
directive("bad", function() {
return {
bindToController: true,
controller: "noIdentCtrl",
scope: {
text: "@text"
}
};
});
// BAD because the controller is not a string (therefore has no identifier),
// and there is no controllerAs property.
directive("bad2", function() {
return {
bindToController: true,
controller: function noControllerAs() {
},
scope: {
text: "@text"
}
};
});
```
@@ -0,0 +1,46 @@
@ngdoc error
@name $controller:ctrlfmt
@fullName Badly formed controller string
@description
This error occurs when {@link ng.$controller $controller} service is called
with a string that does not match the supported controller string formats.
Supported formats:
1. `__name__`
2. `__name__ as __identifier__`
N'either `__name__` or `__identifier__` may contain spaces.
Example of incorrect usage that leads to this error:
```html
<!-- unclosed ng-controller attribute messes up the format -->
<div ng-controller="myController>
```
or
```js
// does not match `__name__` or `__name__ as __identifier__`
var myCtrl = $controller("mY contRoller", { $scope: newScope });
```
or
```js
directive("myDirective", function() {
return {
// does not match `__name__` or `__name__ as __identifier__`
controller: "mY contRoller",
link: function() {}
};
});
```
To fix the examples above, ensure that the controller string matches the supported
formats, and that any html attributes which are used as controller expressions are
closed.
Please consult the {@link ng.$controller $controller} service api docs to learn more.
+51
View File
@@ -0,0 +1,51 @@
@ngdoc error
@name filter:notarray
@fullName Not an array
@description
This error occurs when {@link ng.filter filter} is not used with an array:
```html
<input ng-model="search">
<div ng-repeat="(key, value) in myObj | filter:search">
{{ key }} : {{ value }}
</div>
```
Filter must be used with an array so a subset of items can be returned.
The array can be initialized asynchronously and therefore null or undefined won't throw this error.
To filter an object by the value of its properties you can create your own custom filter:
```js
angular.module('customFilter',[])
.filter('custom', function() {
return function(input, search) {
if (!input) return input;
if (!search) return input;
var expected = ('' + search).toLowerCase();
var result = {};
angular.forEach(input, function(value, key) {
var actual = ('' + value).toLowerCase();
if (actual.indexOf(expected) !== -1) {
result[key] = value;
}
});
return result;
}
});
```
That can be used as:
```html
<input ng-model="search">
<div ng-repeat="(key, value) in myObj | custom:search">
{{ key }} : {{ value }}
</div>
```
You could as well convert the object to an array using a filter such as
[toArrayFilter](https://github.com/petebacondarwin/angular-toArrayFilter):
```html
<input ng-model="search">
<div ng-repeat="item in myObj | toArray:false | filter:search">
{{ item }}
</div>
```
+7
View File
@@ -0,0 +1,7 @@
@ngdoc error
@name ng:cpta
@fullName Copying TypedArray
@description
Copying TypedArray's with a destination is not supported because TypedArray
objects can not be mutated, they are fixed length.
+1 -1
View File
@@ -165,7 +165,7 @@ encoded.
`$location` service has two configuration modes which control the format of the URL in the browser
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
HTML5 [History API](http://www.w3.org/TR/html5/introduction.html#history-0). Applications use the same API in
[HTML5 History API](https://html.spec.whatwg.org/multipage/browsers.html#the-history-interface). Applications use the same API in
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
facilitate the browser URL change and history management.
+1 -1
View File
@@ -102,7 +102,7 @@ This is a short list of libraries with specific support and documentation for wo
## Learning Resources
###Books
* [AngularJS](http://www.amazon.com/AngularJS-Brad-Green/dp/1449344852) by Brad Green and Shyam Seshadri
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
+12 -12
View File
@@ -12,7 +12,7 @@ succinctly. Angular's data binding and dependency injection eliminate much of th
would otherwise have to write. And it all happens within the browser, making it
an ideal partner with any server technology.
Angular is what HTML would have been had it been designed for applications. HTML is a great
Angular is what HTML would have been, had it been designed for applications. HTML is a great
declarative language for static documents. It does not contain much in the way of creating
applications, and as a result building web applications is an exercise in *what do I have to do
to trick the browser into doing what I want?*
@@ -28,10 +28,10 @@ The impedance mismatch between dynamic applications and static documents is ofte
Angular takes another approach. It attempts to minimize the impedance mismatch between document
centric HTML and what an application needs by creating new HTML constructs. Angular teaches the
browser new syntax through a construct we call directives. Examples include:
browser new syntax through a construct we call *directives*. Examples include:
* Data binding, as in `{{}}`.
* DOM control structures for repeating/hiding DOM fragments.
* DOM control structures for repeating, showing and hiding DOM fragments.
* Support for forms and form validation.
* Attaching new behavior to DOM elements, such as DOM event handling.
* Grouping of HTML into reusable components.
@@ -42,20 +42,20 @@ browser new syntax through a construct we call directives. Examples include:
Angular is not a single piece in the overall puzzle of building the client-side of a web
application. It handles all of the DOM and AJAX glue code you once wrote by hand and puts it in a
well-defined structure. This makes Angular opinionated about how a CRUD application should be
built. But while it is opinionated, it also tries to make sure that its opinion is just a
starting point you can easily change. Angular comes with the following out-of-the-box:
well-defined structure. This makes Angular opinionated about how a CRUD (Create, Read, Update, Delete)
application should be built. But while it is opinionated, it also tries to make sure that its opinion
is just a starting point you can easily change. Angular comes with the following out-of-the-box:
* Everything you need to build a CRUD app in a cohesive set: data-binding, basic templating
directives, form validation, routing, deep-linking, reusable components, dependency injection.
* Testability story: unit-testing, end-to-end testing, mocks, test harnesses.
* Everything you need to build a CRUD app in a cohesive set: Data-binding, basic templating
directives, form validation, routing, deep-linking, reusable components and dependency injection.
* Testability story: Unit-testing, end-to-end testing, mocks and test harnesses.
* Seed application with directory layout and test scripts as a starting point.
## Angular Sweet Spot
## Angular's sweet spot
Angular simplifies application development by presenting a higher level of abstraction to the
developer. Like any abstraction, it comes at a cost of flexibility. In other words not every app
developer. Like any abstraction, it comes at a cost of flexibility. In other words, not every app
is a good fit for Angular. Angular was built with the CRUD application in mind. Luckily CRUD
applications represent the majority of web applications. To understand what Angular is
good at, though, it helps to understand when an app is not a good fit for Angular.
@@ -78,7 +78,7 @@ expressing business logic.
* It is an excellent idea to decouple the client side of an app from the server side. This
allows development work to progress in parallel, and allows for reuse of both sides.
* It is very helpful indeed if the framework guides developers through the entire journey of
building an app: from designing the UI, through writing the business logic, to testing.
building an app: From designing the UI, through writing the business logic, to testing.
* It is always good to make common tasks trivial and difficult tasks possible.
+5 -5
View File
@@ -10,13 +10,13 @@ There are a few things you might consider when running your AngularJS applicatio
## Disabling Debug Data
By default AngularJS attaches information about scopes to DOM nodes, and adds CSS classes
to data-bound elements. The information that is not included is:
By default AngularJS attaches information about binding and scopes to DOM nodes,
and adds CSS classes to data-bound elements:
As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
`ng-class` is attached to the corresponding element.
- As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
`ng-binding` are attached to the corresponding element.
Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
- Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
CSS class are attached to the corresponding element. These scope references can then be accessed via
`element.scope()` and `element.isolateScope()`.
+1 -1
View File
@@ -288,5 +288,5 @@ learn how to improve this application with animations.
<ul doc-tutorial-nav="11"></ul>
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
[jasmine-matchers]: http://jasmine.github.io/1.3/introduction.html#section-Matchers
[bower]: http://bower.io/
+7 -11
View File
@@ -2912,16 +2912,12 @@ goog.i18n.DateTimeSymbols_my = {
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
'ဒီဇင်ဘာ'],
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူ', '', 'စက်', 'အောက်',
'နို', 'ဒီ'],
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
@@ -2945,7 +2941,7 @@ goog.i18n.DateTimeSymbols_my = {
'တတိယ သုံးလပတ်',
'စတုတ္ထ သုံးလပတ်'],
AMPMS: ['နံနက်', 'ညနေ'],
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
FIRSTDAYOFWEEK: 6,
+7 -11
View File
@@ -14493,16 +14493,12 @@ goog.i18n.DateTimeSymbols_my_MM = {
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
'ဒီဇင်ဘာ'],
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူ', '', 'စက်', 'အောက်',
'နို', 'ဒီ'],
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
@@ -14526,7 +14522,7 @@ goog.i18n.DateTimeSymbols_my_MM = {
'တတိယ သုံးလပတ်',
'စတုတ္ထ သုံးလပတ်'],
AMPMS: ['နံနက်', 'ညနေ'],
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
FIRSTDAYOFWEEK: 6,
+1 -1
View File
@@ -2112,7 +2112,7 @@ goog.i18n.NumberFormatSymbols_lt = {
SCIENTIFIC_PATTERN: '#E0',
PERCENT_PATTERN: '#,##0\u00A0%',
CURRENCY_PATTERN: '#,##0.00\u00A0\u00A4',
DEF_CURRENCY_CODE: 'LTL'
DEF_CURRENCY_CODE: 'EUR'
};
+13 -17
View File
@@ -16,9 +16,9 @@ var currentPackage, previousVersions, cdnVersion, gitRepoInfo;
var getPackage = function() {
// Search up the folder hierarchy for the first package.json
var packageFolder = path.resolve('.');
while ( !fs.existsSync(path.join(packageFolder, 'package.json')) ) {
while (!fs.existsSync(path.join(packageFolder, 'package.json'))) {
var parent = path.dirname(packageFolder);
if ( parent === packageFolder) { break; }
if (parent === packageFolder) { break; }
packageFolder = parent;
}
return JSON.parse(fs.readFileSync(path.join(packageFolder,'package.json'), 'UTF-8'));
@@ -48,11 +48,11 @@ var getGitRepoInfo = function() {
* @return {String} The codename if found, otherwise null/undefined
*/
var getCodeName = function(tagName) {
var gitCatOutput = shell.exec('git cat-file -p '+ tagName, {silent:true}).output;
var gitCatOutput = shell.exec('git cat-file -p ' + tagName, {silent:true}).output;
var tagMessage = gitCatOutput.match(/^.*codename.*$/mg)[0];
var codeName = tagMessage && tagMessage.match(/codename\((.*)\)/)[1];
if (!codeName) {
throw new Error("Could not extract release code name. The message of tag "+tagName+
throw new Error("Could not extract release code name. The message of tag " + tagName +
" must match '*codename(some release name)*'");
}
return codeName;
@@ -65,7 +65,7 @@ var getCodeName = function(tagName) {
*/
function getBuild() {
var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', '');
return 'sha.'+hash;
return 'sha.' + hash;
}
@@ -76,11 +76,11 @@ function getBuild() {
var getTaggedVersion = function() {
var gitTagResult = shell.exec('git describe --exact-match', {silent:true});
if ( gitTagResult.code === 0 ) {
if (gitTagResult.code === 0) {
var tag = gitTagResult.output.trim();
var version = semver.parse(tag);
if ( version && semver.satisfies(version, currentPackage.branchVersion)) {
if (version && semver.satisfies(version, currentPackage.branchVersion)) {
version.codeName = getCodeName(tag);
version.full = version.version;
version.branch = 'v' + currentPackage.branchPattern.replace('*', 'x');
@@ -102,7 +102,7 @@ var getPreviousVersions = function() {
var repo_url = currentPackage.repository.url;
var tagResults = shell.exec('git ls-remote --tags ' + repo_url,
{silent: true});
if ( tagResults.code === 0 ) {
if (tagResults.code === 0) {
return _(tagResults.output.match(/v[0-9].*[0-9]$/mg))
.map(function(tag) {
var version = semver.parse(tag);
@@ -112,7 +112,7 @@ var getPreviousVersions = function() {
.map(function(version) {
version.docsUrl = 'http://code.angularjs.org/' + version.version + '/docs';
// Versions before 1.0.2 had a different docs folder name
if ( version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2 ) ) {
if (version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2)) {
version.docsUrl += '-' + version.version;
}
return version;
@@ -130,18 +130,14 @@ var getCdnVersion = function() {
return semver.satisfies(tag, currentPackage.branchVersion);
})
.reverse()
.tap(function(versions) {
console.log(versions);
})
.reduce(function(cdnVersion, version) {
if (!cdnVersion) {
// Note: need to use shell.exec and curl here
// as version-infos returns its result synchronously...
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/'+version+'/angular.min.js '+
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/' + version + '/angular.min.js ' +
'--head --write-out "%{http_code}" -o /dev/null -silent',
{silent: false});
console.log('http://ajax.googleapis.com/ajax/libs/angularjs/'+version+'/angular.min.js');
if ( cdnResult.code === 0 ) {
{silent: true});
if (cdnResult.code === 0) {
var statusCode = cdnResult.output.trim();
if (statusCode === '200') {
cdnVersion = version;
@@ -163,7 +159,7 @@ var getSnapshotVersion = function() {
})
.last();
if ( !version ) {
if (!version) {
// a snapshot version before the first tag on the branch
version = semver(currentPackage.branchPattern.replace('*','0-beta.1'));
}
+90 -66
View File
@@ -1395,66 +1395,66 @@
}
},
"dgeni-packages": {
"version": "0.10.7",
"version": "0.10.8",
"dependencies": {
"catharsis": {
"version": "0.7.1"
},
"change-case": {
"version": "2.1.5",
"version": "2.2.0",
"dependencies": {
"camel-case": {
"version": "1.0.2"
"version": "1.1.1"
},
"constant-case": {
"version": "1.0.0"
},
"dot-case": {
"version": "1.0.1"
},
"is-lower-case": {
"version": "1.0.0"
},
"is-upper-case": {
"version": "1.0.1"
},
"lower-case": {
"version": "1.0.2"
},
"param-case": {
"version": "1.0.1"
},
"pascal-case": {
"version": "1.0.0"
},
"path-case": {
"version": "1.0.1"
},
"sentence-case": {
"version": "1.1.0"
},
"dot-case": {
"version": "1.1.0"
},
"is-lower-case": {
"version": "1.1.0"
},
"is-upper-case": {
"version": "1.1.0"
},
"lower-case": {
"version": "1.1.1"
},
"param-case": {
"version": "1.1.0"
},
"pascal-case": {
"version": "1.1.0"
},
"path-case": {
"version": "1.1.0"
},
"sentence-case": {
"version": "1.1.1"
},
"snake-case": {
"version": "1.0.1"
"version": "1.1.0"
},
"swap-case": {
"version": "1.0.2"
"version": "1.1.0"
},
"title-case": {
"version": "1.0.1"
"version": "1.1.0"
},
"upper-case": {
"version": "1.0.3"
"version": "1.1.1"
},
"upper-case-first": {
"version": "1.0.1"
"version": "1.1.0"
}
}
},
"esprima": {
"version": "1.2.2"
"version": "1.2.3"
},
"estraverse": {
"version": "1.7.1"
"version": "1.9.1"
},
"glob": {
"version": "3.2.11",
@@ -1626,7 +1626,7 @@
}
},
"node-uuid": {
"version": "1.4.1"
"version": "1.4.2"
},
"cookie-jar": {
"version": "0.2.0"
@@ -2342,26 +2342,32 @@
},
"grunt-jasmine-node": {
"version": "0.1.0",
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7",
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"resolved": "git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7"
},
"grunt-jscs": {
"version": "0.7.1",
"version": "1.2.0",
"dependencies": {
"hooker": {
"version": "0.2.3"
},
"jscs": {
"version": "1.6.2",
"version": "1.10.0",
"dependencies": {
"colors": {
"version": "0.6.2"
"version": "1.0.3"
},
"commander": {
"version": "2.3.0"
"version": "2.5.1"
},
"esprima": {
"version": "1.2.2"
"version": "1.2.3"
},
"esprima-harmony-jscs": {
"version": "1.1.0-regex-token-fix"
},
"estraverse": {
"version": "1.9.1"
},
"exit": {
"version": "0.1.2"
@@ -2370,11 +2376,22 @@
"version": "4.0.6",
"dependencies": {
"graceful-fs": {
"version": "3.0.4"
"version": "3.0.5"
},
"inherits": {
"version": "2.0.1"
},
"minimatch": {
"version": "1.0.0",
"dependencies": {
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
}
}
},
"once": {
"version": "1.3.1",
"dependencies": {
@@ -2386,54 +2403,61 @@
}
},
"minimatch": {
"version": "1.0.0",
"version": "2.0.1",
"dependencies": {
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
"brace-expansion": {
"version": "1.1.0",
"dependencies": {
"balanced-match": {
"version": "0.2.0"
},
"concat-map": {
"version": "0.0.1"
}
}
}
}
},
"strip-json-comments": {
"version": "1.0.1"
"version": "1.0.2"
},
"vow-fs": {
"version": "0.3.2",
"version": "0.3.4",
"dependencies": {
"node-uuid": {
"version": "1.4.0"
},
"vow": {
"version": "0.4.4"
"version": "1.4.2"
},
"vow-queue": {
"version": "0.3.1"
"version": "0.4.1"
},
"glob": {
"version": "3.2.8",
"version": "4.3.5",
"dependencies": {
"minimatch": {
"version": "0.2.14",
"inflight": {
"version": "1.0.4",
"dependencies": {
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
"wrappy": {
"version": "1.0.1"
}
}
},
"inherits": {
"version": "2.0.1"
},
"once": {
"version": "1.3.1",
"dependencies": {
"wrappy": {
"version": "1.0.1"
}
}
}
}
}
}
},
"xmlbuilder": {
"version": "2.4.4",
"version": "2.4.6",
"dependencies": {
"lodash-node": {
"version": "2.4.1"
@@ -2441,12 +2465,12 @@
}
},
"supports-color": {
"version": "1.1.0"
"version": "1.2.0"
}
}
},
"vow": {
"version": "0.4.5"
"version": "0.4.8"
}
}
},
+1 -1
View File
@@ -25,7 +25,7 @@
"grunt-contrib-jshint": "~0.10.0",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-jscs": "~0.7.1",
"grunt-jscs": "~1.2.0",
"grunt-merge-conflict": "~0.0.1",
"grunt-shell": "~1.1.1",
"gulp": "~3.8.0",
+3 -1
View File
@@ -28,7 +28,6 @@
"manualUppercase": false,
"isArrayLike": false,
"forEach": false,
"sortedKeys": false,
"forEachSorted": false,
"reverseParams": false,
"nextUid": false,
@@ -153,6 +152,9 @@
"urlResolve": false,
"urlIsSameOrigin": false,
/* ng/controller.js */
"identifierForController": false,
/* ng/compile.js */
"directiveNormalize": false,
+25 -13
View File
@@ -22,7 +22,6 @@
nodeName_: true,
isArrayLike: true,
forEach: true,
sortedKeys: true,
forEachSorted: true,
reverseParams: true,
nextUid: true,
@@ -272,12 +271,8 @@ function forEach(obj, iterator, context) {
return obj;
}
function sortedKeys(obj) {
return Object.keys(obj).sort();
}
function forEachSorted(obj, iterator, context) {
var keys = sortedKeys(obj);
var keys = Object.keys(obj).sort();
for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
@@ -317,8 +312,7 @@ function nextUid() {
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
}
else {
} else {
delete obj.$$hashKey;
}
}
@@ -589,6 +583,12 @@ function isPromiseLike(obj) {
}
var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
function isTypedArray(value) {
return TYPED_ARRAY_REGEXP.test(toString.call(value));
}
var trim = function(value) {
return isString(value) ? value.trim() : value;
};
@@ -626,8 +626,9 @@ function isElement(node) {
*/
function makeMap(str) {
var obj = {}, items = str.split(","), i;
for (i = 0; i < items.length; i++)
obj[ items[i] ] = true;
for (i = 0; i < items.length; i++) {
obj[items[i]] = true;
}
return obj;
}
@@ -642,9 +643,10 @@ function includes(array, obj) {
function arrayRemove(array, value) {
var index = array.indexOf(value);
if (index >= 0)
if (index >= 0) {
array.splice(index, 1);
return value;
}
return index;
}
/**
@@ -710,12 +712,18 @@ function copy(source, destination, stackSource, stackDest) {
throw ngMinErr('cpws',
"Can't copy! Making copies of Window or Scope instances is not supported.");
}
if (isTypedArray(destination)) {
throw ngMinErr('cpta',
"Can't copy! TypedArray destination cannot be mutated.");
}
if (!destination) {
destination = source;
if (source) {
if (isArray(source)) {
destination = copy(source, [], stackSource, stackDest);
} else if (isTypedArray(source)) {
destination = new source.constructor(source);
} else if (isDate(source)) {
destination = new Date(source.getTime());
} else if (isRegExp(source)) {
@@ -1408,8 +1416,12 @@ function bootstrap(element, modules, config) {
forEach(extraModules, function(module) {
modules.push(module);
});
doBootstrap();
return doBootstrap();
};
if (isFunction(angular.resumeDeferredBootstrap)) {
angular.resumeDeferredBootstrap();
}
}
/**
+2 -2
View File
@@ -800,7 +800,7 @@ function createInjector(modulesToLoad, strictDi) {
}
var args = [],
$inject = annotate(fn, strictDi, serviceName),
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
@@ -839,7 +839,7 @@ function createInjector(modulesToLoad, strictDi) {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
+2 -1
View File
@@ -851,8 +851,9 @@ forEach({
children: function(element) {
var children = [];
forEach(element.childNodes, function(element) {
if (element.nodeType === NODE_TYPE_ELEMENT)
if (element.nodeType === NODE_TYPE_ELEMENT) {
children.push(element);
}
});
return children;
},
+190 -94
View File
@@ -64,7 +64,7 @@
* templateNamespace: 'html',
* scope: false,
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
* controllerAs: 'stringAlias',
* controllerAs: 'stringIdentifier',
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
* compile: function compile(tElement, tAttrs, transclude) {
* return {
@@ -224,9 +224,10 @@
*
*
* #### `controllerAs`
* Controller alias at the directive scope. An alias for the controller so it
* can be referenced at the directive template. The directive needs to define a scope for this
* configuration to be used. Useful in the case when directive is used as component.
* Identifier name for a reference to the controller in the directive's scope.
* This allows the controller to be referenced from the directive template. The directive
* needs to define a scope for this configuration to be used. Useful in the case when
* directive is used as component.
*
*
* #### `restrict`
@@ -712,7 +713,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// 'on' and be composed of only English letters.
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
function parseIsolateBindings(scope, directiveName) {
function parseIsolateBindings(scope, directiveName, isController) {
var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
var bindings = {};
@@ -722,9 +723,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (!match) {
throw $compileMinErr('iscp',
"Invalid isolate scope definition for directive '{0}'." +
"Invalid {3} for directive '{0}'." +
" Definition: {... {1}: '{2}' ...}",
directiveName, scopeName, definition);
directiveName, scopeName, definition,
(isController ? "controller bindings definition" :
"isolate scope definition"));
}
bindings[scopeName] = {
@@ -738,6 +741,43 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return bindings;
}
function parseDirectiveBindings(directive, directiveName) {
var bindings = {
isolateScope: null,
bindToController: null
};
if (isObject(directive.scope)) {
if (directive.bindToController === true) {
bindings.bindToController = parseIsolateBindings(directive.scope,
directiveName, true);
bindings.isolateScope = {};
} else {
bindings.isolateScope = parseIsolateBindings(directive.scope,
directiveName, false);
}
}
if (isObject(directive.bindToController)) {
bindings.bindToController =
parseIsolateBindings(directive.bindToController, directiveName, true);
}
if (isObject(bindings.bindToController)) {
var controller = directive.controller;
var controllerAs = directive.controllerAs;
if (!controller) {
// There is no controller, there may or may not be a controllerAs property
throw $compileMinErr('noctrl',
"Cannot bind to controller without directive '{0}'s controller.",
directiveName);
} else if (!identifierForController(controller, controllerAs)) {
// There is a controller, but no identifier or controllerAs property
throw $compileMinErr('noident',
"Cannot bind to controller without identifier for directive '{0}'.",
directiveName);
}
}
return bindings;
}
/**
* @ngdoc method
* @name $compileProvider#directive
@@ -775,8 +815,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name);
directive.restrict = directive.restrict || 'EA';
if (isObject(directive.scope)) {
directive.$$isolateBindings = parseIsolateBindings(directive.scope, directive.name);
var bindings = directive.$$bindings =
parseDirectiveBindings(directive, directive.name);
if (isObject(bindings.isolateScope)) {
directive.$$isolateBindings = bindings.isolateScope;
}
directives.push(directive);
} catch (e) {
@@ -1338,6 +1380,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (nodeLinkFn.scope) {
childScope = scope.$new();
compile.$$addScopeInfo(jqLite(node), childScope);
var destroyBindings = nodeLinkFn.$$destroyBindings;
if (destroyBindings) {
nodeLinkFn.$$destroyBindings = null;
childScope.$on('$destroyed', destroyBindings);
}
} else {
childScope = scope;
}
@@ -1357,7 +1404,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
childBoundTranscludeFn = null;
}
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
nodeLinkFn);
} else if (childLinkFn) {
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
@@ -1838,7 +1886,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
thisLinkFn) {
var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
attrs;
@@ -1895,91 +1944,42 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
if (newIsolateScopeDirective) {
// Initialize isolate scope bindings for new isolate scope directive.
compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
templateDirective === newIsolateScopeDirective.$$originalDirective)));
compile.$$addScopeClass($element, true);
var isolateScopeController = controllers && controllers[newIsolateScopeDirective.name];
var isolateBindingContext = isolateScope;
if (isolateScopeController && isolateScopeController.identifier &&
newIsolateScopeDirective.bindToController === true) {
isolateBindingContext = isolateScopeController.instance;
}
forEach(isolateScope.$$isolateBindings = newIsolateScopeDirective.$$isolateBindings, function(definition, scopeName) {
var attrName = definition.attrName,
optional = definition.optional,
mode = definition.mode, // @, =, or &
lastValue,
parentGet, parentSet, compare;
switch (mode) {
case '@':
attrs.$observe(attrName, function(value) {
isolateBindingContext[scopeName] = value;
});
attrs.$$observers[attrName].$$scope = scope;
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
isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope);
}
break;
case '=':
if (optional && !attrs[attrName]) {
return;
}
parentGet = $parse(attrs[attrName]);
if (parentGet.literal) {
compare = equals;
} else {
compare = function(a, b) { return a === b || (a !== a && b !== b); };
}
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
lastValue = isolateBindingContext[scopeName] = parentGet(scope);
throw $compileMinErr('nonassign',
"Expression '{0}' used with directive '{1}' is non-assignable!",
attrs[attrName], newIsolateScopeDirective.name);
};
lastValue = isolateBindingContext[scopeName] = parentGet(scope);
var parentValueWatch = function parentValueWatch(parentValue) {
if (!compare(parentValue, isolateBindingContext[scopeName])) {
// we are out of sync and need to copy
if (!compare(parentValue, lastValue)) {
// parent changed and it has precedence
isolateBindingContext[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
parentSet(scope, parentValue = isolateBindingContext[scopeName]);
}
}
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
var unwatch;
if (definition.collection) {
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else {
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
}
isolateScope.$on('$destroy', unwatch);
break;
case '&':
parentGet = $parse(attrs[attrName]);
isolateBindingContext[scopeName] = function(locals) {
return parentGet(scope, locals);
};
break;
}
});
isolateScope.$$isolateBindings =
newIsolateScopeDirective.$$isolateBindings;
initializeDirectiveBindings(scope, attrs, isolateScope,
isolateScope.$$isolateBindings,
newIsolateScopeDirective, isolateScope);
}
if (controllers) {
// Initialize bindToController bindings for new/isolate scopes
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
var bindings;
var controllerForBindings;
if (scopeDirective && controllers[scopeDirective.name]) {
bindings = scopeDirective.$$bindings.bindToController;
controller = controllers[scopeDirective.name];
if (controller && controller.identifier && bindings) {
controllerForBindings = controller;
thisLinkFn.$$destroyBindings =
initializeDirectiveBindings(scope, attrs, controller.instance,
bindings, scopeDirective);
}
}
forEach(controllers, function(controller) {
controller();
var result = controller();
if (result !== controller.instance &&
controller === controllerForBindings) {
// Remove and re-install bindToController bindings
thisLinkFn.$$destroyBindings();
thisLinkFn.$$destroyBindings =
initializeDirectiveBindings(scope, attrs, result,
bindings, scopeDirective);
}
});
controllers = null;
}
@@ -2155,8 +2155,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
afterTemplateChildLinkFn,
beforeTemplateCompileNode = $compileNode[0],
origAsyncDirective = directives.shift(),
// The fact that we have to copy and patch the directive seems wrong!
derivedSyncDirective = extend({}, origAsyncDirective, {
derivedSyncDirective = inherit(origAsyncDirective, {
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
}),
templateUrl = (isFunction(origAsyncDirective.templateUrl))
@@ -2240,7 +2239,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
childBoundTranscludeFn = boundTranscludeFn;
}
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
childBoundTranscludeFn);
childBoundTranscludeFn, afterTemplateNodeLinkFn);
}
linkQueue = null;
});
@@ -2257,7 +2256,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
}
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
afterTemplateNodeLinkFn);
}
};
}
@@ -2504,6 +2504,102 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
$exceptionHandler(e, startingTag($element));
}
}
// Set up $watches for isolate scope and controller bindings. This process
// only occurs for isolate scopes and new scopes with controllerAs.
function initializeDirectiveBindings(scope, attrs, destination, bindings,
directive, newScope) {
var onNewScopeDestroyed;
forEach(bindings, function(definition, scopeName) {
var attrName = definition.attrName,
optional = definition.optional,
mode = definition.mode, // @, =, or &
lastValue,
parentGet, parentSet, compare;
switch (mode) {
case '@':
attrs.$observe(attrName, function(value) {
destination[scopeName] = value;
});
attrs.$$observers[attrName].$$scope = scope;
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
destination[scopeName] = $interpolate(attrs[attrName])(scope);
}
break;
case '=':
if (optional && !attrs[attrName]) {
return;
}
parentGet = $parse(attrs[attrName]);
if (parentGet.literal) {
compare = equals;
} else {
compare = function(a, b) { return a === b || (a !== a && b !== b); };
}
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
lastValue = destination[scopeName] = parentGet(scope);
throw $compileMinErr('nonassign',
"Expression '{0}' used with directive '{1}' is non-assignable!",
attrs[attrName], directive.name);
};
lastValue = destination[scopeName] = parentGet(scope);
var parentValueWatch = function parentValueWatch(parentValue) {
if (!compare(parentValue, destination[scopeName])) {
// we are out of sync and need to copy
if (!compare(parentValue, lastValue)) {
// parent changed and it has precedence
destination[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
parentSet(scope, parentValue = destination[scopeName]);
}
}
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
var unwatch;
if (definition.collection) {
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else {
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
}
onNewScopeDestroyed = (onNewScopeDestroyed || []);
onNewScopeDestroyed.push(unwatch);
break;
case '&':
// Don't assign Object.prototype method to scope
if (!attrs.hasOwnProperty(attrName) && optional) break;
parentGet = $parse(attrs[attrName]);
// Don't assign noop to destination if expression is not valid
if (parentGet === noop && optional) break;
destination[scopeName] = function(locals) {
return parentGet(scope, locals);
};
break;
}
});
var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
onNewScopeDestroyed[i]();
}
} : noop;
if (newScope && destroyBindings !== noop) {
newScope.$on('$destroy', destroyBindings);
return noop;
}
return destroyBindings;
}
}];
}
+30 -6
View File
@@ -1,5 +1,18 @@
'use strict';
var $controllerMinErr = minErr('$controller');
var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
function identifierForController(controller, ident) {
if (ident && isString(ident)) return ident;
if (isString(controller)) {
var match = CNTRL_REG.exec(controller);
if (match) return match[3];
}
}
/**
* @ngdoc provider
* @name $controllerProvider
@@ -12,9 +25,7 @@
*/
function $ControllerProvider() {
var controllers = {},
globals = false,
CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
globals = false;
/**
* @ngdoc method
@@ -87,7 +98,12 @@ function $ControllerProvider() {
}
if (isString(expression)) {
match = expression.match(CNTRL_REG),
match = expression.match(CNTRL_REG);
if (!match) {
throw $controllerMinErr('ctrlfmt',
"Badly formed controller string '{0}'. " +
"Must match `__name__ as __id__` or `__name__`.", expression);
}
constructor = match[1],
identifier = identifier || match[3];
expression = controllers.hasOwnProperty(constructor)
@@ -117,8 +133,16 @@ function $ControllerProvider() {
addIdentifier(locals, identifier, instance, constructor || expression.name);
}
return extend(function() {
$injector.invoke(expression, instance, locals, constructor);
var instantiate;
return instantiate = extend(function() {
var result = $injector.invoke(expression, instance, locals, constructor);
if (result !== instance && (isObject(result) || isFunction(result))) {
instance = result;
if (identifier) {
// If result changed, re-assign controllerAs value to scope.
addIdentifier(locals, identifier, instance, constructor || expression.name);
}
}
return instance;
}, {
instance: instance,
+4 -1
View File
@@ -16,8 +16,11 @@
var htmlAnchorDirective = valueFn({
restrict: 'E',
compile: function(element, attr) {
if (!attr.href && !attr.xlinkHref && !attr.name) {
if (!attr.href && !attr.xlinkHref) {
return function(scope, element) {
// If the linked element is not an anchor tag anymore, do nothing
if (element[0].nodeName.toLowerCase() !== 'a') return;
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
'xlink:href' : 'href';
+1 -1
View File
@@ -68,7 +68,7 @@
}, 5000, 'page should navigate to /123');
});
xit('should execute ng-click but not reload when href empty string and name specified', function() {
it('should execute ng-click but not reload when href empty string and name specified', function() {
element(by.id('link-4')).click();
expect(element(by.model('value')).getAttribute('value')).toEqual('4');
expect(element(by.id('link-4')).getAttribute('href')).toBe('');
+86 -64
View File
@@ -61,19 +61,21 @@ var inputType = {
<script>
angular.module('textInputExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.text = 'guest';
$scope.word = /^\s*\w*\s*$/;
$scope.example = {
text: 'guest',
word: /^\s*\w*\s*$/
};
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
Single word: <input type="text" name="input" ng-model="text"
ng-pattern="word" required ng-trim="false">
Single word: <input type="text" name="input" ng-model="example.text"
ng-pattern="example.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">
Single word only!</span>
<tt>text = {{text}}</tt><br/>
<tt>text = {{example.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -81,9 +83,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var text = element(by.binding('text'));
var text = element(by.binding('example.text'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('text'));
var input = element(by.model('example.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('guest');
@@ -145,18 +147,20 @@ var inputType = {
<script>
angular.module('dateInputExample', [])
.controller('DateController', ['$scope', function($scope) {
$scope.value = new Date(2013, 9, 22);
$scope.example = {
value: new Date(2013, 9, 22)
};
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date in 2013:
<input type="date" id="exampleInput" name="input" ng-model="value"
<input type="date" id="exampleInput" name="input" ng-model="example.value"
placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.date">
Not a valid date!</span>
<tt>value = {{value | date: "yyyy-MM-dd"}}</tt><br/>
<tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -164,9 +168,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var value = element(by.binding('value | date: "yyyy-MM-dd"'));
var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('value'));
var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -236,18 +240,20 @@ var inputType = {
<script>
angular.module('dateExample', [])
.controller('DateController', ['$scope', function($scope) {
$scope.value = new Date(2010, 11, 28, 14, 57);
$scope.example = {
value: new Date(2010, 11, 28, 14, 57)
};
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date between in 2013:
<input type="datetime-local" id="exampleInput" name="input" ng-model="value"
<input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.datetimelocal">
Not a valid date!</span>
<tt>value = {{value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
<tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -255,9 +261,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var value = element(by.binding('value | date: "yyyy-MM-ddTHH:mm:ss"'));
var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('value'));
var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -328,18 +334,20 @@ var inputType = {
<script>
angular.module('timeExample', [])
.controller('DateController', ['$scope', function($scope) {
$scope.value = new Date(1970, 0, 1, 14, 57, 0);
$scope.example = {
value: new Date(1970, 0, 1, 14, 57, 0)
};
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a between 8am and 5pm:
<input type="time" id="exampleInput" name="input" ng-model="value"
<input type="time" id="exampleInput" name="input" ng-model="example.value"
placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.time">
Not a valid date!</span>
<tt>value = {{value | date: "HH:mm:ss"}}</tt><br/>
<tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -347,9 +355,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var value = element(by.binding('value | date: "HH:mm:ss"'));
var value = element(by.binding('example.value | date: "HH:mm:ss"'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('value'));
var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -419,18 +427,20 @@ var inputType = {
<script>
angular.module('weekExample', [])
.controller('DateController', ['$scope', function($scope) {
$scope.value = new Date(2013, 0, 3);
$scope.example = {
value: new Date(2013, 0, 3)
};
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date between in 2013:
<input id="exampleInput" type="week" name="input" ng-model="value"
<input id="exampleInput" type="week" name="input" ng-model="example.value"
placeholder="YYYY-W##" min="2012-W32" max="2013-W52" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.week">
Not a valid date!</span>
<tt>value = {{value | date: "yyyy-Www"}}</tt><br/>
<tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -438,9 +448,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var value = element(by.binding('value | date: "yyyy-Www"'));
var value = element(by.binding('example.value | date: "yyyy-Www"'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('value'));
var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -510,18 +520,20 @@ var inputType = {
<script>
angular.module('monthExample', [])
.controller('DateController', ['$scope', function($scope) {
$scope.value = new Date(2013, 9, 1);
$scope.example = {
value: new Date(2013, 9, 1)
};
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a month in 2013:
<input id="exampleInput" type="month" name="input" ng-model="value"
<input id="exampleInput" type="month" name="input" ng-model="example.value"
placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.month">
Not a valid month!</span>
<tt>value = {{value | date: "yyyy-MM"}}</tt><br/>
<tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -529,9 +541,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var value = element(by.binding('value | date: "yyyy-MM"'));
var value = element(by.binding('example.value | date: "yyyy-MM"'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('value'));
var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -607,17 +619,19 @@ var inputType = {
<script>
angular.module('numberExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.value = 12;
$scope.example = {
value: 12
};
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
Number: <input type="number" name="input" ng-model="value"
Number: <input type="number" name="input" ng-model="example.value"
min="0" max="99" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.number">
Not valid number!</span>
<tt>value = {{value}}</tt><br/>
<tt>value = {{example.value}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -625,9 +639,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var value = element(by.binding('value'));
var value = element(by.binding('example.value'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('value'));
var input = element(by.model('example.value'));
it('should initialize to model', function() {
expect(value.getText()).toContain('12');
@@ -695,16 +709,18 @@ var inputType = {
<script>
angular.module('urlExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.text = 'http://google.com';
$scope.url = {
text: 'http://google.com'
};
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
URL: <input type="url" name="input" ng-model="text" required>
URL: <input type="url" name="input" ng-model="url.text" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.url">
Not valid url!</span>
<tt>text = {{text}}</tt><br/>
<tt>text = {{url.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -713,9 +729,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var text = element(by.binding('text'));
var text = element(by.binding('url.text'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('text'));
var input = element(by.model('url.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('http://google.com');
@@ -784,16 +800,18 @@ var inputType = {
<script>
angular.module('emailExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.text = 'me@example.com';
$scope.email = {
text: 'me@example.com'
};
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
Email: <input type="email" name="input" ng-model="text" required>
Email: <input type="email" name="input" ng-model="email.text" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.email">
Not valid email!</span>
<tt>text = {{text}}</tt><br/>
<tt>text = {{email.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -802,9 +820,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
var text = element(by.binding('text'));
var text = element(by.binding('email.text'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('text'));
var input = element(by.model('email.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('me@example.com');
@@ -851,7 +869,9 @@ var inputType = {
<script>
angular.module('radioExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.color = 'blue';
$scope.color = {
name: 'blue'
};
$scope.specialValue = {
"id": "12345",
"value": "green"
@@ -859,20 +879,20 @@ var inputType = {
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
<input type="radio" ng-model="color" value="red"> Red <br/>
<input type="radio" ng-model="color" ng-value="specialValue"> Green <br/>
<input type="radio" ng-model="color" value="blue"> Blue <br/>
<tt>color = {{color | json}}</tt><br/>
<input type="radio" ng-model="color.name" value="red"> Red <br/>
<input type="radio" ng-model="color.name" ng-value="specialValue"> Green <br/>
<input type="radio" ng-model="color.name" value="blue"> Blue <br/>
<tt>color = {{color.name | json}}</tt><br/>
</form>
Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
</file>
<file name="protractor.js" type="protractor">
it('should change state', function() {
var color = element(by.binding('color'));
var color = element(by.binding('color.name'));
expect(color.getText()).toContain('blue');
element.all(by.model('color')).get(0).click();
element.all(by.model('color.name')).get(0).click();
expect(color.getText()).toContain('red');
});
@@ -902,28 +922,30 @@ var inputType = {
<script>
angular.module('checkboxExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.value1 = true;
$scope.value2 = 'YES'
$scope.checkboxModel = {
value1 : true,
value2 : 'YES'
};
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
Value1: <input type="checkbox" ng-model="value1"> <br/>
Value2: <input type="checkbox" ng-model="value2"
Value1: <input type="checkbox" ng-model="checkboxModel.value1"> <br/>
Value2: <input type="checkbox" ng-model="checkboxModel.value2"
ng-true-value="'YES'" ng-false-value="'NO'"> <br/>
<tt>value1 = {{value1}}</tt><br/>
<tt>value2 = {{value2}}</tt><br/>
<tt>value1 = {{checkboxModel.value1}}</tt><br/>
<tt>value2 = {{checkboxModel.value2}}</tt><br/>
</form>
</file>
<file name="protractor.js" type="protractor">
it('should change state', function() {
var value1 = element(by.binding('value1'));
var value2 = element(by.binding('value2'));
var value1 = element(by.binding('checkboxModel.value1'));
var value2 = element(by.binding('checkboxModel.value2'));
expect(value1.getText()).toContain('true');
expect(value2.getText()).toContain('YES');
element(by.model('value1')).click();
element(by.model('value2')).click();
element(by.model('checkboxModel.value1')).click();
element(by.model('checkboxModel.value2')).click();
expect(value1.getText()).toContain('false');
expect(value2.getText()).toContain('NO');
+1 -1
View File
@@ -19,7 +19,7 @@
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
* sure you have parenthesis for correct precedence:
* <pre class="prettyprint">
* <div ng-init="test1 = (data | orderBy:'name')"></div>
* `<div ng-init="test1 = (data | orderBy:'name')"></div>`
* </pre>
* </div>
*
+4 -2
View File
@@ -217,11 +217,13 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale,
// If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
// In JS `NaN !== NaN`, so we have to exlicitly check.
if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) {
if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
watchRemover();
var whenExpFn = whensExpFns[count];
if (isUndefined(whenExpFn)) {
$log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
if (newVal != null) {
$log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
}
watchRemover = noop;
updateElementText();
} else {
+3 -2
View File
@@ -40,10 +40,11 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
*
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class in CSS:
* class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
* with extra animation classes that can be added.
*
* ```css
* .ng-hide {
* .ng-hide:not(.ng-hide-animate) {
* /&#42; this is just another form of hiding an element &#42;/
* display: block!important;
* position: absolute;
+4 -4
View File
@@ -114,12 +114,12 @@ var SelectController =
* @description
* HTML `SELECT` element with angular data-binding.
*
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
* similar result. However, `ngOptions` provides some benefits such as reducing memory and
* increasing speed by not creating a new scope for each repeated instance, as well as providing
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits such as reducing
* memory and increasing speed by not creating a new scope for each repeated instance, as well as providing
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
* comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
* to a non-string value. This is because an option element can only be bound to string values at
* to a non-string value. This is because an option element can only be bound to string values at
* present.
*
* When an item in the `<select>` menu is selected, the array element or object property
+1 -1
View File
@@ -65,7 +65,7 @@ var maxlengthDirective = function() {
ctrl.$validate();
});
ctrl.$validators.maxlength = function(modelValue, viewValue) {
return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
};
}
};
+7 -1
View File
@@ -124,7 +124,13 @@
*/
function filterFilter() {
return function(array, expression, comparator) {
if (!isArray(array)) return array;
if (!isArray(array)) {
if (array == null) {
return array;
} else {
throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
}
}
var predicateFn;
var matchAgainstAnyProp;
+15 -8
View File
@@ -234,8 +234,9 @@ function padNumber(num, digits, trim) {
}
num = '' + num;
while (num.length < digits) num = '0' + num;
if (trim)
if (trim) {
num = num.substr(num.length - digits);
}
return neg + num;
}
@@ -244,8 +245,9 @@ function dateGetter(name, size, offset, trim) {
offset = offset || 0;
return function(date) {
var value = date['get' + name]();
if (offset > 0 || value > -offset)
if (offset > 0 || value > -offset) {
value += offset;
}
if (value === 0 && offset == -12) value = 12;
return padNumber(value, size, trim);
};
@@ -260,8 +262,8 @@ function dateStrGetter(name, shortForm) {
};
}
function timeZoneGetter(date) {
var zone = -1 * date.getTimezoneOffset();
function timeZoneGetter(date, formats, offset) {
var zone = -1 * offset;
var paddedZone = (zone >= 0) ? "+" : "";
paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
@@ -482,13 +484,18 @@ function dateFilter($locale) {
}
}
if (timezone && timezone === 'UTC') {
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
var dateTimezoneOffset = date.getTimezoneOffset();
if (timezone) {
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
if (!isNaN(requestedTimezoneOffset)) {
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + dateTimezoneOffset - requestedTimezoneOffset);
dateTimezoneOffset = requestedTimezoneOffset;
}
}
forEach(parts, function(value) {
fn = DATE_FORMATS[value];
text += fn ? fn(date, $locale.DATETIME_FORMATS)
text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
: value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
});
+2 -1
View File
@@ -98,8 +98,9 @@ function headersGetter(headers) {
* @returns {*} Transformed data.
*/
function transformData(data, headers, status, fns) {
if (isFunction(fns))
if (isFunction(fns)) {
return fns(data, headers, status);
}
forEach(fns, function(fn) {
data = fn(data, headers, status);
+22 -22
View File
@@ -88,6 +88,28 @@ function $InterpolateProvider() {
return '\\\\\\' + ch;
}
function unescapeText(text) {
return text.replace(escapedStartRegexp, startSymbol).
replace(escapedEndRegexp, endSymbol);
}
function stringify(value) {
if (value == null) { // null || undefined
return '';
}
switch (typeof value) {
case 'string':
break;
case 'number':
value = '' + value;
break;
default:
value = toJson(value);
}
return value;
}
/**
* @ngdoc service
* @name $interpolate
@@ -243,23 +265,6 @@ function $InterpolateProvider() {
$sce.valueOf(value);
};
var stringify = function(value) {
if (value == null) { // null || undefined
return '';
}
switch (typeof value) {
case 'string':
break;
case 'number':
value = '' + value;
break;
default:
value = toJson(value);
}
return value;
};
return extend(function interpolationFn(context) {
var i = 0;
var ii = expressions.length;
@@ -294,11 +299,6 @@ function $InterpolateProvider() {
});
}
function unescapeText(text) {
return text.replace(escapedStartRegexp, startSymbol).
replace(escapedEndRegexp, endSymbol);
}
function parseStringifyInterceptor(value) {
try {
value = getValue(value);
+7 -4
View File
@@ -373,8 +373,9 @@ var locationPrototype = {
* @return {string} url
*/
url: function(url) {
if (isUndefined(url))
if (isUndefined(url)) {
return this.$$url;
}
var match = PATH_MATCH.exec(url);
if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
@@ -613,8 +614,9 @@ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], fun
* @return {object} state
*/
Location.prototype.state = function(state) {
if (!arguments.length)
if (!arguments.length) {
return this.$$state;
}
if (Location !== LocationHtml5Url || !this.$$html5) {
throw $locationMinErr('nostate', 'History API state support is available only ' +
@@ -639,8 +641,9 @@ function locationGetter(property) {
function locationGetterSetter(property, preprocess) {
return function(value) {
if (isUndefined(value))
if (isUndefined(value)) {
return this[property];
}
this[property] = preprocess(value);
this.$$compose();
@@ -837,7 +840,7 @@ function $LocationProvider() {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2 || event.button == 2) return;
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
var elm = jqLite(event.target);
+1271 -627
View File
File diff suppressed because it is too large Load Diff
+7 -8
View File
@@ -302,24 +302,24 @@ function qFactory(nextTick, exceptionHandler) {
}
function processQueue(state) {
var fn, promise, pending;
var fn, deferred, pending;
pending = state.pending;
state.processScheduled = false;
state.pending = undefined;
for (var i = 0, ii = pending.length; i < ii; ++i) {
promise = pending[i][0];
deferred = pending[i][0];
fn = pending[i][state.status];
try {
if (isFunction(fn)) {
promise.resolve(fn(state.value));
deferred.resolve(fn(state.value));
} else if (state.status === 1) {
promise.resolve(state.value);
deferred.resolve(state.value);
} else {
promise.reject(state.value);
deferred.reject(state.value);
}
} catch (e) {
promise.reject(e);
deferred.reject(e);
exceptionHandler(e);
}
}
@@ -347,8 +347,7 @@ function qFactory(nextTick, exceptionHandler) {
'qcycle',
"Expected promise to be resolved with value other than itself '{0}'",
val));
}
else {
} else {
this.$$resolve(val);
}
+16 -5
View File
@@ -135,6 +135,7 @@ function $RootScopeProvider() {
this.$$destroyed = false;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$$isolateBindings = null;
}
@@ -210,6 +211,7 @@ function $RootScopeProvider() {
this.$$childHead = this.$$childTail = null;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$id = nextUid();
this.$$ChildScope = null;
};
@@ -356,11 +358,11 @@ function $RootScopeProvider() {
* comparing for reference equality.
* @returns {function()} Returns a deregistration function for this listener.
*/
$watch: function(watchExp, listener, objectEquality) {
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
var get = $parse(watchExp);
if (get.$$watchDelegate) {
return get.$$watchDelegate(this, listener, objectEquality, get);
return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
}
var scope = this,
array = scope.$$watchers,
@@ -368,7 +370,7 @@ function $RootScopeProvider() {
fn: listener,
last: initWatchVal,
get: get,
exp: watchExp,
exp: prettyPrintExpression || watchExp,
eq: !!objectEquality
};
@@ -384,9 +386,12 @@ function $RootScopeProvider() {
// we use unshift since we use a while loop in $digest for speed.
// the while loop reads in reverse order.
array.unshift(watcher);
incrementWatchersCount(this, 1);
return function deregisterWatch() {
arrayRemove(array, watcher);
if (arrayRemove(array, watcher) >= 0) {
incrementWatchersCount(scope, -1);
}
lastDirtyWatch = null;
};
},
@@ -794,7 +799,7 @@ function $RootScopeProvider() {
// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $broadcast
if (!(next = (current.$$childHead ||
if (!(next = ((current.$$watchersCount && current.$$childHead) ||
(current !== target && current.$$nextSibling)))) {
while (current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
@@ -869,6 +874,7 @@ function $RootScopeProvider() {
this.$$destroyed = true;
if (this === $rootScope) return;
incrementWatchersCount(this, -this.$$watchersCount);
for (var eventName in this.$$listenerCount) {
decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
}
@@ -1290,6 +1296,11 @@ function $RootScopeProvider() {
$rootScope.$$phase = null;
}
function incrementWatchersCount(current, count) {
do {
current.$$watchersCount += count;
} while ((current = current.$parent));
}
function decrementListenerCount(current, count, name) {
do {
+1
View File
@@ -44,6 +44,7 @@ function $TemplateRequestProvider() {
handleRequestFn.totalPendingRequests--;
})
.then(function(response) {
$templateCache.put(tpl, response.data);
return response.data;
}, handleError);
+1 -2
View File
@@ -1327,8 +1327,7 @@ angular.module('ngAnimate', ['ng'])
} else if (lastAnimation.event == 'setClass') {
animationsToCancel.push(lastAnimation);
cleanup(element, className);
}
else if (runningAnimations[className]) {
} else if (runningAnimations[className]) {
var current = runningAnimations[className];
if (current.event == animationEvent) {
skipAnimation = true;
+1 -1
View File
@@ -81,7 +81,7 @@ $provide.value("$locale", {
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "Lt",
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": "\u00a0",
"PATTERNS": [
+1 -1
View File
@@ -81,7 +81,7 @@ $provide.value("$locale", {
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "Lt",
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": "\u00a0",
"PATTERNS": [
+14 -14
View File
@@ -40,26 +40,26 @@ $provide.value("$locale", {
"\u1005\u1014\u1031"
],
"SHORTMONTH": [
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
"\u1007\u1014\u103a",
"\u1016\u1031",
"\u1019\u1010\u103a",
"\u1027\u1015\u103c\u102e",
"\u1019\u1031",
"\u1007\u103d\u1014\u103a",
"\u1007\u1030\u101c\u102d\u102f\u1004\u103a",
"\u1029\u1002\u102f\u1010\u103a",
"\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c",
"\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c",
"\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c",
"\u1012\u102e\u1007\u1004\u103a\u1018\u102c"
"\u1007\u1030",
"\u1029",
"\u1005\u1000\u103a",
"\u1021\u1031\u102c\u1000\u103a",
"\u1014\u102d\u102f",
"\u1012\u102e"
],
"fullDate": "EEEE, y MMMM dd",
"longDate": "y MMMM d",
"medium": "y MMM d HH:mm:ss",
"mediumDate": "y MMM d",
"fullDate": "EEEE, dd MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"mediumTime": "HH:mm:ss",
"short": "yy/MM/dd HH:mm",
"shortDate": "yy/MM/dd",
"short": "dd-MM-yy HH:mm",
"shortDate": "dd-MM-yy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
+14 -14
View File
@@ -40,26 +40,26 @@ $provide.value("$locale", {
"\u1005\u1014\u1031"
],
"SHORTMONTH": [
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
"\u1007\u1014\u103a",
"\u1016\u1031",
"\u1019\u1010\u103a",
"\u1027\u1015\u103c\u102e",
"\u1019\u1031",
"\u1007\u103d\u1014\u103a",
"\u1007\u1030\u101c\u102d\u102f\u1004\u103a",
"\u1029\u1002\u102f\u1010\u103a",
"\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c",
"\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c",
"\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c",
"\u1012\u102e\u1007\u1004\u103a\u1018\u102c"
"\u1007\u1030",
"\u1029",
"\u1005\u1000\u103a",
"\u1021\u1031\u102c\u1000\u103a",
"\u1014\u102d\u102f",
"\u1012\u102e"
],
"fullDate": "EEEE, y MMMM dd",
"longDate": "y MMMM d",
"medium": "y MMM d HH:mm:ss",
"mediumDate": "y MMM d",
"fullDate": "EEEE, dd MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"mediumTime": "HH:mm:ss",
"short": "yy/MM/dd HH:mm",
"shortDate": "yy/MM/dd",
"short": "dd-MM-yy HH:mm",
"shortDate": "dd-MM-yy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
+1 -1
View File
@@ -70,7 +70,7 @@
*
* However, including generic messages may not be useful enough to match all input fields, therefore,
* `ngMessages` provides the ability to override messages defined in the remote template by redefining
* then within the directive container.
* them within the directive container.
*
* ```html
* <!-- a generic template of error messages known as "my-custom-messages" -->
+68 -50
View File
@@ -599,8 +599,9 @@ function padNumber(num, digits, trim) {
}
num = '' + num;
while (num.length < digits) num = '0' + num;
if (trim)
if (trim) {
num = num.substr(num.length - digits);
}
return neg + num;
}
@@ -650,11 +651,12 @@ angular.mock.TzDate = function(offset, timestamp) {
self.origDate = jsonStringToDate(timestamp);
timestamp = self.origDate.getTime();
if (isNaN(timestamp))
if (isNaN(timestamp)) {
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
}
} else {
self.origDate = new Date(timestamp);
}
@@ -1184,14 +1186,16 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
}
if (expectation && expectation.match(method, url)) {
if (!expectation.matchData(data))
if (!expectation.matchData(data)) {
throw new Error('Expected ' + expectation + ' with different data\n' +
'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
}
if (!expectation.matchHeaders(headers))
if (!expectation.matchHeaders(headers)) {
throw new Error('Expected ' + expectation + ' with different headers\n' +
'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
prettyPrint(headers));
}
expectations.shift();
@@ -1227,8 +1231,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* Creates a new backend definition.
*
* @param {string} method HTTP method.
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
@@ -1273,8 +1277,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for GET requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1287,8 +1291,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for HEAD requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1301,8 +1305,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for DELETE requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1315,8 +1319,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for POST requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
@@ -1331,8 +1335,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for PUT requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
@@ -1347,8 +1351,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for JSONP requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
@@ -1363,8 +1367,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* Creates a new request expectation.
*
* @param {string} method HTTP method.
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
@@ -1402,8 +1406,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for GET requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1416,8 +1420,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1430,8 +1434,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1444,8 +1448,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for POST requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
@@ -1461,8 +1465,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for PUT requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
@@ -1478,8 +1482,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
@@ -1495,8 +1499,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives an url
* and returns true if the url matches the current definition.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
@@ -1906,8 +1910,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* Creates a new backend definition.
*
* @param {string} method HTTP method.
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp)=} data HTTP request body.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
* object and returns true if the headers match the current definition.
@@ -1934,8 +1938,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for GET requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. You can save this object for later use and invoke
@@ -1949,8 +1953,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for HEAD requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. You can save this object for later use and invoke
@@ -1964,8 +1968,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for DELETE requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. You can save this object for later use and invoke
@@ -1979,8 +1983,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for POST requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp)=} data HTTP request body.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
@@ -1995,8 +1999,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for PUT requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp)=} data HTTP request body.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
@@ -2011,8 +2015,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for PATCH requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp)=} data HTTP request body.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
@@ -2027,8 +2031,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for JSONP requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. You can save this object for later use and invoke
* `respond` or `passThrough` again in order to change how a matched request is handled.
@@ -2127,18 +2131,32 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
if (window.jasmine || window.mocha) {
var currentSpec = null,
annotatedFunctions,
isSpecRunning = function() {
return !!currentSpec;
};
angular.mock.$$annotate = angular.injector.$$annotate;
angular.injector.$$annotate = function(fn) {
if (typeof fn === 'function' && !fn.$inject) {
annotatedFunctions.push(fn);
}
return angular.mock.$$annotate.apply(this, arguments);
};
(window.beforeEach || window.setup)(function() {
annotatedFunctions = [];
currentSpec = this;
});
(window.afterEach || window.teardown)(function() {
var injector = currentSpec.$injector;
annotatedFunctions.forEach(function(fn) {
delete fn.$inject;
});
angular.forEach(currentSpec.$modules, function(module) {
if (module && module.$$hashKey) {
module.$$hashKey = undefined;
+6 -12
View File
@@ -73,8 +73,8 @@ 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.
* - `controllerAs` `{string=}` A controller alias name. If present the controller will be
* published to scope under the `controllerAs` name.
* - `controllerAs` `{string=}` An identifier name for a reference to the controller.
* If present, the controller will be published to scope under the `controllerAs` name.
* - `template` `{string=|function()=}` html template as a string or a function that
* returns an html template as a string which should be used by {@link
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
@@ -477,21 +477,15 @@ function $RouteProvider() {
* definitions will be interpolated into the location's path, while
* remaining properties will be treated as query params.
*
* @param {Object} newParams mapping of URL parameter names to values
* @param {!Object<string, string>} newParams mapping of URL parameter names to values
*/
updateParams: function(newParams) {
if (this.current && this.current.$$route) {
var searchParams = {}, self=this;
angular.forEach(Object.keys(newParams), function(key) {
if (!self.current.pathParams[key]) searchParams[key] = newParams[key];
});
newParams = angular.extend({}, this.current.params, newParams);
$location.path(interpolate(this.current.$$route.originalPath, newParams));
$location.search(angular.extend({}, $location.search(), searchParams));
}
else {
// interpolate modifies newParams, only query params are left
$location.search(newParams);
} else {
throw $routeMinErr('norout', 'Tried updating route when with no current route');
}
}
+14 -12
View File
@@ -271,14 +271,14 @@ function htmlParser(html, handler) {
}
}
var index, chars, match, stack = [], last = html, text;
stack.last = function() { return stack[ stack.length - 1 ]; };
stack.last = function() { return stack[stack.length - 1]; };
while (html) {
text = '';
chars = true;
// Make sure we're not in a script or style element
if (!stack.last() || !specialElements[ stack.last() ]) {
if (!stack.last() || !specialElements[stack.last()]) {
// Comment
if (html.indexOf("<!--") === 0) {
@@ -360,20 +360,21 @@ function htmlParser(html, handler) {
function parseStartTag(tag, tagName, rest, unary) {
tagName = angular.lowercase(tagName);
if (blockElements[ tagName ]) {
while (stack.last() && inlineElements[ stack.last() ]) {
if (blockElements[tagName]) {
while (stack.last() && inlineElements[stack.last()]) {
parseEndTag("", stack.last());
}
}
if (optionalEndTagElements[ tagName ] && stack.last() == tagName) {
if (optionalEndTagElements[tagName] && stack.last() == tagName) {
parseEndTag("", tagName);
}
unary = voidElements[ tagName ] || !!unary;
unary = voidElements[tagName] || !!unary;
if (!unary)
if (!unary) {
stack.push(tagName);
}
var attrs = {};
@@ -392,16 +393,17 @@ function htmlParser(html, handler) {
function parseEndTag(tag, tagName) {
var pos = 0, i;
tagName = angular.lowercase(tagName);
if (tagName)
if (tagName) {
// Find the closest opened tag of the same type
for (pos = stack.length - 1; pos >= 0; pos--)
if (stack[ pos ] == tagName)
break;
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] == tagName) break;
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (i = stack.length - 1; i >= pos; i--)
if (handler.end) handler.end(stack[ i ]);
if (handler.end) handler.end(stack[i]);
// Remove the open elements from the stack
stack.length = pos;
+31 -11
View File
@@ -34,8 +34,9 @@ angular.scenario.Application.prototype.getFrame_ = function() {
*/
angular.scenario.Application.prototype.getWindow_ = function() {
var contentWindow = this.getFrame_().prop('contentWindow');
if (!contentWindow)
if (!contentWindow) {
throw 'Frame window is not accessible.';
}
return contentWindow;
};
@@ -68,19 +69,31 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF
try {
var $window = self.getWindow_();
if ($window.angular) {
// Disable animations
$window.angular.resumeBootstrap([['$provide', function($provide) {
return ['$animate', function($animate) {
$animate.enabled(false);
}];
}]]);
if (!$window.angular) {
self.executeAction(loadFn);
return;
}
if (!$window.angular.resumeBootstrap) {
$window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap;
} else {
resumeDeferredBootstrap();
}
self.executeAction(loadFn);
} catch (e) {
errorFn(e);
}
function resumeDeferredBootstrap() {
// Disable animations
var $injector = $window.angular.resumeBootstrap([['$provide', function($provide) {
return ['$animate', function($animate) {
$animate.enabled(false);
}];
}]]);
self.rootElement = $injector.get('$rootElement')[0];
self.executeAction(loadFn);
}
}).attr('src', url);
// for IE compatibility set the name *after* setting the frame url
@@ -105,7 +118,14 @@ angular.scenario.Application.prototype.executeAction = function(action) {
if (!$window.angular) {
return action.call(this, $window, _jQuery($window.document));
}
angularInit($window.document, function(element) {
if (!!this.rootElement) {
executeWithElement(this.rootElement);
} else {
angularInit($window.document, angular.bind(this, executeWithElement));
}
function executeWithElement(element) {
var $injector = $window.angular.element(element).injector();
var $element = _jQuery(element);
@@ -118,5 +138,5 @@ angular.scenario.Application.prototype.executeAction = function(action) {
action.call(self, $window, $element);
});
});
});
}
};
+2 -1
View File
@@ -76,8 +76,9 @@ angular.scenario.ObjectModel = function(runner) {
runner.on('StepEnd', function(spec) {
var it = self.getSpec(spec.id);
var step = it.getLastStep();
if (step.name !== step.name)
if (step.name !== step.name) {
throw 'Events fired in the wrong order. Step names don\'t match.';
}
complete(step);
// forward the event
+4 -2
View File
@@ -36,8 +36,9 @@ angular.scenario.Runner.prototype.emit = function(eventName) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
eventName = eventName.toLowerCase();
if (!this.listeners[eventName])
if (!this.listeners[eventName]) {
return;
}
angular.forEach(this.listeners[eventName], function(listener) {
listener.apply(self, args);
});
@@ -157,8 +158,9 @@ angular.scenario.Runner.prototype.createSpecRunner_ = function(scope) {
// Export all the methods to child scope manually as now we don't mess controllers with scopes
// TODO(vojta): refactor scenario runner so that these objects are not tightly coupled as current
for (var name in Cls.prototype)
for (var name in Cls.prototype) {
child[name] = angular.bind(child, Cls.prototype[name]);
}
Cls.call(child);
return child;
+3 -2
View File
@@ -42,8 +42,9 @@ angular.scenario.dsl = angular.scenario.dsl || function(name, fn) {
/* jshint -W040 *//* The dsl binds `this` for us when calling chained functions */
function executeStatement(statement, args) {
var result = statement.apply(this, args);
if (angular.isFunction(result) || result instanceof angular.scenario.Future)
if (angular.isFunction(result) || result instanceof angular.scenario.Future) {
return result;
}
var self = this;
var chain = angular.extend({}, result);
angular.forEach(chain, function(value, name) {
@@ -304,7 +305,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
var element = windowJquery(this),
bindings;
if (bindings = element.data('$binding')) {
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
binding = bindings[j];
if (binding.expressions) {
+4 -8
View File
@@ -55,8 +55,7 @@
if (window.WebKitTransitionEvent) {
evnt = new WebKitTransitionEvent(eventType, eventData);
evnt.initEvent(eventType, false, true);
}
else {
} else {
try {
evnt = new TransitionEvent(eventType, eventData);
}
@@ -65,13 +64,11 @@
evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
}
else if (/animationend/.test(eventType)) {
} else if (/animationend/.test(eventType)) {
if (window.WebKitAnimationEvent) {
evnt = new WebKitAnimationEvent(eventType, eventData);
evnt.initEvent(eventType, false, true);
}
else {
} else {
try {
evnt = new AnimationEvent(eventType, eventData);
}
@@ -80,8 +77,7 @@
evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
}
else {
} else {
evnt = document.createEvent('MouseEvents');
x = x || 0;
y = y || 0;
+2 -1
View File
@@ -276,8 +276,9 @@ angular.scenario.dsl('repeater', function() {
return this.addFutureAction("repeater '" + this.label + "' row '" + index + "'",
function($window, $document, done) {
var matches = $document.elements().slice(index, index + 1);
if (!matches.length)
if (!matches.length) {
return done('row ' + index + ' out of bounds');
}
done(null, matches.bindings($window.angular.element));
});
};
-1
View File
@@ -26,7 +26,6 @@
"manualUppercase": false,
"isArrayLike": false,
"forEach": false,
"sortedKeys": false,
"reverseParams": false,
"nextUid": false,
"setHashKey": false,
+200 -7
View File
@@ -78,6 +78,186 @@ describe('angular', function() {
expect(copy(objWithRegExp.re) === objWithRegExp.re).toBeFalsy();
});
it("should copy a Uint8Array with no destination", function() {
if (typeof Uint8Array !== 'undefined') {
var src = new Uint8Array(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Uint8Array).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should copy a Uint8ClampedArray with no destination", function() {
if (typeof Uint8ClampedArray !== 'undefined') {
var src = new Uint8ClampedArray(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Uint8ClampedArray).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should copy a Uint16Array with no destination", function() {
if (typeof Uint16Array !== 'undefined') {
var src = new Uint16Array(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Uint16Array).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should copy a Uint32Array with no destination", function() {
if (typeof Uint32Array !== 'undefined') {
var src = new Uint32Array(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Uint32Array).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should copy a Int8Array with no destination", function() {
if (typeof Int8Array !== 'undefined') {
var src = new Int8Array(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Int8Array).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should copy a Int16Array with no destination", function() {
if (typeof Int16Array !== 'undefined') {
var src = new Int16Array(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Int16Array).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should copy a Int32Array with no destination", function() {
if (typeof Int32Array !== 'undefined') {
var src = new Int32Array(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Int32Array).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should copy a Float32Array with no destination", function() {
if (typeof Float32Array !== 'undefined') {
var src = new Float32Array(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Float32Array).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should copy a Float64Array with no destination", function() {
if (typeof Float64Array !== 'undefined') {
var src = new Float64Array(2);
src[1] = 1;
var dst = copy(src);
expect(copy(src) instanceof Float64Array).toBeTruthy();
expect(dst).toEqual(src);
expect(dst).not.toBe(src);
}
});
it("should throw an exception if a Uint8Array is the destination", function() {
if (typeof Uint8Array !== 'undefined') {
var src = new Uint8Array();
var dst = new Uint8Array(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should throw an exception if a Uint8ClampedArray is the destination", function() {
if (typeof Uint8ClampedArray !== 'undefined') {
var src = new Uint8ClampedArray();
var dst = new Uint8ClampedArray(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should throw an exception if a Uint16Array is the destination", function() {
if (typeof Uint16Array !== 'undefined') {
var src = new Uint16Array();
var dst = new Uint16Array(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should throw an exception if a Uint32Array is the destination", function() {
if (typeof Uint32Array !== 'undefined') {
var src = new Uint32Array();
var dst = new Uint32Array(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should throw an exception if a Int8Array is the destination", function() {
if (typeof Int8Array !== 'undefined') {
var src = new Int8Array();
var dst = new Int8Array(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should throw an exception if a Int16Array is the destination", function() {
if (typeof Int16Array !== 'undefined') {
var src = new Int16Array();
var dst = new Int16Array(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should throw an exception if a Int32Array is the destination", function() {
if (typeof Int32Array !== 'undefined') {
var src = new Int32Array();
var dst = new Int32Array(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should throw an exception if a Float32Array is the destination", function() {
if (typeof Float32Array !== 'undefined') {
var src = new Float32Array();
var dst = new Float32Array(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should throw an exception if a Float64Array is the destination", function() {
if (typeof Float64Array !== 'undefined') {
var src = new Float64Array();
var dst = new Float64Array(5);
expect(function() { copy(src, dst); })
.toThrowMinErr("ng", "cpta", "Can't copy! TypedArray destination cannot be mutated.");
}
});
it("should deeply copy an array into an existing array", function() {
var src = [1, {name:"value"}];
var dst = [{key:"v"}];
@@ -699,13 +879,6 @@ describe('angular', function() {
});
describe('sortedKeys', function() {
it('should collect keys from object', function() {
expect(sortedKeys({c:0, b:0, a:0})).toEqual(['a', 'b', 'c']);
});
});
describe('encodeUriSegment', function() {
it('should correctly encode uri segment and not encode chars defined as pchar set in rfc3986',
function() {
@@ -1081,6 +1254,26 @@ describe('angular', function() {
window.name = originalName;
});
it('should provide injector for deferred bootstrap', function() {
var injector;
window.name = 'NG_DEFER_BOOTSTRAP!';
injector = angular.bootstrap(element);
expect(injector).toBeUndefined();
injector = angular.resumeBootstrap();
expect(injector).toBeDefined();
});
it('should resume deferred bootstrap, if defined', function() {
var injector;
window.name = 'NG_DEFER_BOOTSTRAP!';
angular.resumeDeferredBootstrap = noop;
var spy = spyOn(angular, "resumeDeferredBootstrap");
injector = angular.bootstrap(element);
expect(spy).toHaveBeenCalled();
});
it('should wait for extra modules', function() {
window.name = 'NG_DEFER_BOOTSTRAP!';
+7 -3
View File
@@ -238,7 +238,11 @@ describe('injector', function() {
it('should publish annotate API', function() {
expect(injector.annotate).toBe(annotate);
expect(angular.mock.$$annotate).toBe(annotate);
spyOn(angular.mock, '$$annotate').andCallThrough();
function fn() {}
injector.annotate(fn);
expect(angular.mock.$$annotate).toHaveBeenCalledWith(fn);
});
});
@@ -972,7 +976,7 @@ describe('strict-di injector', function() {
});
});
inject(function($injector) {
expect (function() {
expect(function() {
$injector.invoke(function($test2) {});
}).toThrowMinErr('$injector', 'strictdi');
});
@@ -986,7 +990,7 @@ describe('strict-di injector', function() {
});
});
inject(function($injector) {
expect (function() {
expect(function() {
$injector.invoke(['$test', function($test) {}]);
}).toThrowMinErr('$injector', 'strictdi');
});
+6 -14
View File
@@ -74,19 +74,9 @@ afterEach(function() {
// copied from Angular.js
// we need these two methods here so that we can run module tests with wrapped angular.js
function sortedKeys(obj) {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys.sort();
}
// we need this method here so that we can run module tests with wrapped angular.js
function forEachSorted(obj, iterator, context) {
var keys = sortedKeys(obj);
var keys = Object.keys(obj).sort();
for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
@@ -170,8 +160,9 @@ function sortedHtml(element, showNgClass) {
attrs.push(' class="' + className + '"');
}
for (var i = 0; i < attributes.length; i++) {
if (i > 0 && attributes[i] == attributes[i - 1])
if (i > 0 && attributes[i] == attributes[i - 1]) {
continue; //IE9 creates dupes. Ignore them!
}
var attr = attributes[i];
if (attr.name.match(/^ng[\:\-]/) ||
@@ -231,8 +222,9 @@ function sortedHtml(element, showNgClass) {
var tmp = style;
style = [];
forEach(tmp, function(value) {
if (!value.match(/^max[^\-]/))
if (!value.match(/^max[^\-]/)) {
style.push(value);
}
});
if (style.length) {
html += ' style="' + style.join('; ') + ';"';
+2 -2
View File
@@ -395,7 +395,7 @@ describe('browser', function() {
});
it ('should return a value for an existing cookie', function() {
it('should return a value for an existing cookie', function() {
document.cookie = "foo=bar=baz;path=/";
expect(browser.cookies().foo).toEqual('bar=baz');
});
@@ -408,7 +408,7 @@ describe('browser', function() {
expect(browser.cookies()['foo']).toBe('"first"');
});
it ('should decode cookie values that were encoded by puts', function() {
it('should decode cookie values that were encoded by puts', function() {
document.cookie = "cookie2%3Dbar%3Bbaz=val%3Due;path=/";
expect(browser.cookies()['cookie2=bar;baz']).toEqual('val=ue');
});
+376 -20
View File
@@ -423,8 +423,7 @@ describe('$compile', function() {
describe('multiple directives per element', function() {
it('should allow multiple directives per element', inject(function($compile, $rootScope, log) {
element = $compile(
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')
($rootScope);
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')($rootScope);
expect(element.text()).toEqual('Hello angular');
expect(log).toEqual('L; M; H');
}));
@@ -607,8 +606,7 @@ describe('$compile', function() {
describe('priority', function() {
it('should honor priority', inject(function($compile, $rootScope, log) {
element = $compile(
'<span log="L" x-high-log="H" data-medium-log="M"></span>')
($rootScope);
'<span log="L" x-high-log="H" data-medium-log="M"></span>')($rootScope);
expect(log).toEqual('L; M; H');
}));
});
@@ -809,8 +807,7 @@ describe('$compile', function() {
it('should compile template when replacing', inject(function($compile, $rootScope, log) {
element = $compile('<div><div replace medium-log>ignore</div><div>')
($rootScope);
element = $compile('<div><div replace medium-log>ignore</div><div>')($rootScope);
$rootScope.$digest();
expect(element.text()).toEqual('Replace!');
expect(log).toEqual('LOG; HIGH; MEDIUM');
@@ -818,8 +815,7 @@ describe('$compile', function() {
it('should compile template when appending', inject(function($compile, $rootScope, log) {
element = $compile('<div><div append medium-log>ignore</div><div>')
($rootScope);
element = $compile('<div><div append medium-log>ignore</div><div>')($rootScope);
$rootScope.$digest();
expect(element.text()).toEqual('Append!');
expect(log).toEqual('LOG; HIGH; MEDIUM');
@@ -828,8 +824,7 @@ describe('$compile', function() {
it('should merge attributes including style attr', inject(function($compile, $rootScope) {
element = $compile(
'<div><div replace class="medium-log" style="height: 20px" ></div><div>')
($rootScope);
'<div><div replace class="medium-log" style="height: 20px" ></div><div>')($rootScope);
var div = element.find('div');
expect(div.hasClass('medium-log')).toBe(true);
expect(div.hasClass('log')).toBe(true);
@@ -841,8 +836,7 @@ describe('$compile', function() {
it('should not merge attributes if they are the same', inject(function($compile, $rootScope) {
element = $compile(
'<div><div nomerge class="medium-log" id="myid"></div><div>')
($rootScope);
'<div><div nomerge class="medium-log" id="myid"></div><div>')($rootScope);
var div = element.find('div');
expect(div.hasClass('medium-log')).toBe(true);
expect(div.hasClass('log')).toBe(true);
@@ -1115,6 +1109,29 @@ describe('$compile', function() {
expect(element.find('p').text()).toBe('Hello, world!');
});
});
it('should keep prototype properties on directive', function() {
module(function() {
function DirectiveClass() {
this.restrict = 'E';
this.template = "<p>{{value}}</p>";
}
DirectiveClass.prototype.compile = function() {
return function(scope, element, attrs) {
scope.value = "Test Value";
};
};
directive('templateUrlWithPrototype', valueFn(new DirectiveClass()));
});
inject(function($compile, $rootScope) {
element = $compile('<template-url-with-prototype><template-url-with-prototype>')($rootScope);
$rootScope.$digest();
expect(element.find("p")[0].innerHTML).toEqual("Test Value");
});
});
});
@@ -2034,6 +2051,32 @@ describe('$compile', function() {
});
});
it('should keep prototype properties on sync version of async directive', function() {
module(function() {
function DirectiveClass() {
this.restrict = 'E';
this.templateUrl = "test.html";
}
DirectiveClass.prototype.compile = function() {
return function(scope, element, attrs) {
scope.value = "Test Value";
};
};
directive('templateUrlWithPrototype', valueFn(new DirectiveClass()));
});
inject(function($compile, $rootScope, $httpBackend) {
$httpBackend.whenGET('test.html').
respond('<p>{{value}}</p>');
element = $compile('<template-url-with-prototype><template-url-with-prototype>')($rootScope);
$httpBackend.flush();
$rootScope.$digest();
expect(element.find("p")[0].innerHTML).toEqual("Test Value");
});
});
});
@@ -3090,7 +3133,9 @@ describe('$compile', function() {
colref: '=*',
colrefAlias: '=* colref',
expr: '&',
exprAlias: '&expr'
optExpr: '&?',
exprAlias: '&expr',
constructor: '&?'
},
link: function(scope) {
componentScope = scope;
@@ -3230,6 +3275,38 @@ describe('$compile', function() {
});
it('should not initialize scope value if optional expression binding is not passed', inject(function($compile) {
compile('<div my-component></div>');
var isolateScope = element.isolateScope();
expect(isolateScope.optExpr).toBeUndefined();
}));
it('should not initialize scope value if optional expression binding with Object.prototype name is not passed', inject(function($compile) {
compile('<div my-component></div>');
var isolateScope = element.isolateScope();
expect(isolateScope.constructor).toBe($rootScope.constructor);
}));
it('should initialize scope value if optional expression binding is passed', inject(function($compile) {
compile('<div my-component opt-expr="value = \'did!\'"></div>');
var isolateScope = element.isolateScope();
expect(typeof isolateScope.optExpr).toBe('function');
expect(isolateScope.optExpr()).toBe('did!');
expect($rootScope.value).toBe('did!');
}));
it('should initialize scope value if optional expression binding with Object.prototype name is passed', inject(function($compile) {
compile('<div my-component constructor="value = \'did!\'"></div>');
var isolateScope = element.isolateScope();
expect(typeof isolateScope.constructor).toBe('function');
expect(isolateScope.constructor()).toBe('did!');
expect($rootScope.value).toBe('did!');
}));
describe('bind-once', function() {
function countWatches(scope) {
@@ -3809,6 +3886,288 @@ describe('$compile', function() {
expect(controllerCalled).toBe(true);
});
});
it('should throw noctrl when missing controller', function() {
module(function($compileProvider) {
$compileProvider.directive('noCtrl', valueFn({
templateUrl: 'test.html',
scope: {
'data': '=dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
controllerAs: 'test',
bindToController: true
}));
});
inject(function($compile, $rootScope) {
expect(function() {
$compile('<div no-ctrl>')($rootScope);
}).toThrowMinErr('$compile', 'noctrl',
'Cannot bind to controller without directive \'noCtrl\'s controller.');
});
});
it('should throw noident when missing controllerAs directive property', function() {
module(function($compileProvider) {
$compileProvider.directive('noIdent', valueFn({
templateUrl: 'test.html',
scope: {
'data': '=dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
controller: function() {},
bindToController: true
}));
});
inject(function($compile, $rootScope) {
expect(function() {
$compile('<div no-ident>')($rootScope);
}).toThrowMinErr('$compile', 'noident',
'Cannot bind to controller without identifier for directive \'noIdent\'.');
});
});
it('should throw noident when missing controller identifier', function() {
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {});
$compileProvider.directive('noIdent', valueFn({
templateUrl: 'test.html',
scope: {
'data': '=dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
controller: 'myCtrl',
bindToController: true
}));
});
inject(function($compile, $rootScope) {
expect(function() {
$compile('<div no-ident>')($rootScope);
}).toThrowMinErr('$compile', 'noident',
'Cannot bind to controller without identifier for directive \'noIdent\'.');
});
});
it('should bind to controller via object notation (isolate scope)', function() {
var controllerCalled = false;
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {
expect(this.data).toEqualData({
'foo': 'bar',
'baz': 'biz'
});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
controllerCalled = true;
});
$compileProvider.directive('fooDir', valueFn({
templateUrl: 'test.html',
bindToController: {
'data': '=dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
scope: {},
controller: 'myCtrl as myCtrl'
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test.html', '<p>isolate</p>');
$rootScope.fn = valueFn('called!');
$rootScope.whom = 'world';
$rootScope.remoteData = {
'foo': 'bar',
'baz': 'biz'
};
element = $compile('<div foo-dir dir-data="remoteData" ' +
'dir-str="Hello, {{whom}}!" ' +
'dir-fn="fn()"></div>')($rootScope);
$rootScope.$digest();
expect(controllerCalled).toBe(true);
});
});
it('should bind to controller via object notation (new scope)', function() {
var controllerCalled = false;
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {
expect(this.data).toEqualData({
'foo': 'bar',
'baz': 'biz'
});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
controllerCalled = true;
});
$compileProvider.directive('fooDir', valueFn({
templateUrl: 'test.html',
bindToController: {
'data': '=dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
scope: true,
controller: 'myCtrl as myCtrl'
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test.html', '<p>isolate</p>');
$rootScope.fn = valueFn('called!');
$rootScope.whom = 'world';
$rootScope.remoteData = {
'foo': 'bar',
'baz': 'biz'
};
element = $compile('<div foo-dir dir-data="remoteData" ' +
'dir-str="Hello, {{whom}}!" ' +
'dir-fn="fn()"></div>')($rootScope);
$rootScope.$digest();
expect(controllerCalled).toBe(true);
});
});
it('should put controller in scope when controller identifier present but not using controllerAs', function() {
var controllerCalled = false;
var myCtrl;
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {
controllerCalled = true;
myCtrl = this;
});
$compileProvider.directive('fooDir', valueFn({
templateUrl: 'test.html',
bindToController: {},
scope: true,
controller: 'myCtrl as theCtrl'
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test.html', '<p>isolate</p>');
element = $compile('<div foo-dir>')($rootScope);
$rootScope.$digest();
expect(controllerCalled).toBe(true);
var childScope = element.children().scope();
expect(childScope).not.toBe($rootScope);
expect(childScope.theCtrl).toBe(myCtrl);
});
});
it('should re-install controllerAs and bindings for returned value from controller (new scope)', function() {
var controllerCalled = false;
var myCtrl;
function MyCtrl() {
}
MyCtrl.prototype.test = function() {
expect(this.data).toEqualData({
'foo': 'bar',
'baz': 'biz'
});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
};
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {
controllerCalled = true;
myCtrl = this;
return new MyCtrl();
});
$compileProvider.directive('fooDir', valueFn({
templateUrl: 'test.html',
bindToController: {
'data': '=dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
scope: true,
controller: 'myCtrl as theCtrl'
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test.html', '<p>isolate</p>');
$rootScope.fn = valueFn('called!');
$rootScope.whom = 'world';
$rootScope.remoteData = {
'foo': 'bar',
'baz': 'biz'
};
element = $compile('<div foo-dir dir-data="remoteData" ' +
'dir-str="Hello, {{whom}}!" ' +
'dir-fn="fn()"></div>')($rootScope);
$rootScope.$digest();
expect(controllerCalled).toBe(true);
var childScope = element.children().scope();
expect(childScope).not.toBe($rootScope);
expect(childScope.theCtrl).not.toBe(myCtrl);
expect(childScope.theCtrl.constructor).toBe(MyCtrl);
childScope.theCtrl.test();
});
});
it('should re-install controllerAs and bindings for returned value from controller (isolate scope)', function() {
var controllerCalled = false;
var myCtrl;
function MyCtrl() {
}
MyCtrl.prototype.test = function() {
expect(this.data).toEqualData({
'foo': 'bar',
'baz': 'biz'
});
expect(this.str).toBe('Hello, world!');
expect(this.fn()).toBe('called!');
};
module(function($compileProvider, $controllerProvider) {
$controllerProvider.register('myCtrl', function() {
controllerCalled = true;
myCtrl = this;
return new MyCtrl();
});
$compileProvider.directive('fooDir', valueFn({
templateUrl: 'test.html',
bindToController: true,
scope: {
'data': '=dirData',
'str': '@dirStr',
'fn': '&dirFn'
},
controller: 'myCtrl as theCtrl'
}));
});
inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('test.html', '<p>isolate</p>');
$rootScope.fn = valueFn('called!');
$rootScope.whom = 'world';
$rootScope.remoteData = {
'foo': 'bar',
'baz': 'biz'
};
element = $compile('<div foo-dir dir-data="remoteData" ' +
'dir-str="Hello, {{whom}}!" ' +
'dir-fn="fn()"></div>')($rootScope);
$rootScope.$digest();
expect(controllerCalled).toBe(true);
var childScope = element.children().scope();
expect(childScope).not.toBe($rootScope);
expect(childScope.theCtrl).not.toBe(myCtrl);
expect(childScope.theCtrl.constructor).toBe(MyCtrl);
childScope.theCtrl.test();
});
});
});
@@ -4610,8 +4969,7 @@ describe('$compile', function() {
});
});
inject(function(log, $rootScope, $compile) {
element = $compile('<div><div trans>T:{{x}}-{{$parent.$id}}-{{$id}}<span>;</span></div></div>')
($rootScope);
element = $compile('<div><div trans>T:{{x}}-{{$parent.$id}}-{{$id}}<span>;</span></div></div>')($rootScope);
$rootScope.x = 'root';
$rootScope.$apply();
expect(element.text()).toEqual('W:iso-1-2;T:root-2-3;');
@@ -4896,8 +5254,7 @@ describe('$compile', function() {
});
});
inject(function(log, $rootScope, $compile) {
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')
($rootScope);
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');
@@ -5728,8 +6085,7 @@ describe('$compile', function() {
});
});
inject(function(log, $rootScope, $compile) {
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')
($rootScope);
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')($rootScope);
$rootScope.$apply();
expect(log).toEqual('compile: <!-- trans: text -->; link; LOG; LOG; HIGH');
expect(element.text()).toEqual('1-2;1-3;');
@@ -6065,7 +6421,7 @@ describe('$compile', function() {
link: function(scope, element, attrs, ctrl, transclude) {
// We use timeout here to simulate how ng-if works
$timeout(function() {
$timeout(function() {
transclude(function(child) { element.append(child); });
});
}
+42 -1
View File
@@ -90,7 +90,16 @@ describe('$controller', function() {
var foo = $controller('a.Foo', {$scope: scope});
expect(foo).toBeDefined();
expect(foo instanceof Foo).toBe(true);
}));
}));
it('should throw ctrlfmt if name contains spaces', function() {
expect(function() {
$controller('ctrl doom');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl doom'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
});
@@ -168,5 +177,37 @@ describe('$controller', function() {
}).toThrowMinErr("$controller", "noscp", "Cannot export controller 'a.b.FooCtrl' as 'foo'! No $scope object provided via `locals`.");
});
it('should throw ctrlfmt if identifier contains non-ident characters', function() {
expect(function() {
$controller('ctrl as foo<bar');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl as foo<bar'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
it('should throw ctrlfmt if identifier contains spaces', function() {
expect(function() {
$controller('ctrl as foo bar');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl as foo bar'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
it('should throw ctrlfmt if identifier missing after " as "', function() {
expect(function() {
$controller('ctrl as ');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl as '. " +
"Must match `__name__ as __id__` or `__name__`.");
expect(function() {
$controller('ctrl as');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl as'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
});
});
+47 -20
View File
@@ -3,6 +3,25 @@
describe('a', function() {
var element, $compile, $rootScope;
beforeEach(module(function($compileProvider) {
$compileProvider.
directive('linkTo', valueFn({
restrict: 'A',
template: '<div class="my-link"><a href="{{destination}}">{{destination}}</a></div>',
replace: true,
scope: {
destination: '@linkTo'
}
})).
directive('linkNot', valueFn({
restrict: 'A',
template: '<div class="my-link"><a href>{{destination}}</a></div>',
replace: true,
scope: {
destination: '@linkNot'
}
}));
}));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
@@ -63,16 +82,37 @@ describe('a', function() {
});
it('should not link and hookup an event if name is present at compile', function() {
var jq = jQuery || jqLite;
element = jq('<a name="bobby">hello@you</a>');
var linker = $compile(element);
it('should not preventDefault if anchor element is replaced with href-containing element', function() {
spyOn(jqLite.prototype, 'on').andCallThrough();
element = $compile('<a link-to="https://www.google.com">')($rootScope);
$rootScope.$digest();
spyOn(jq.prototype, 'on');
var child = element.children('a');
var preventDefault = jasmine.createSpy('preventDefault');
linker($rootScope);
child.triggerHandler({
type: 'click',
preventDefault: preventDefault
});
expect(jq.prototype.on).not.toHaveBeenCalled();
expect(preventDefault).not.toHaveBeenCalled();
});
it('should preventDefault if anchor element is replaced with element without href attribute', function() {
spyOn(jqLite.prototype, 'on').andCallThrough();
element = $compile('<a link-not="https://www.google.com">')($rootScope);
$rootScope.$digest();
var child = element.children('a');
var preventDefault = jasmine.createSpy('preventDefault');
child.triggerHandler({
type: 'click',
preventDefault: preventDefault
});
expect(preventDefault).toHaveBeenCalled();
});
@@ -115,19 +155,6 @@ describe('a', function() {
expect(jq.prototype.on).not.toHaveBeenCalled();
});
it('should not link and hookup an event if name is present at compile', function() {
var jq = jQuery || jqLite;
element = jq('<svg><a name="bobby">hello@you</a></svg>');
var linker = $compile(element);
spyOn(jq.prototype, 'on');
linker($rootScope);
expect(jq.prototype.on).not.toHaveBeenCalled();
});
});
}
});
+2 -2
View File
@@ -1641,9 +1641,9 @@ describe('input', function() {
});
});
describe('max', function() {
describe('max', function() {
it('should invalidate', function() {
it('should invalidate', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="value" name="alias" max="2019-01-01" />');
helper.changeInputValueTo('2019-12-31');
expect(inputElm).toBeInvalid();
+91 -39
View File
@@ -83,52 +83,64 @@ describe('ngPluralize', function() {
}));
it('should show single/plural strings with mal-formed inputs', inject(function($rootScope) {
$rootScope.email = '';
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
it('should show single/plural strings with mal-formed inputs', inject(
function($log, $rootScope) {
$rootScope.email = '';
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
expect($log.debug.logs.shift()).toEqual([
"ngPluralize: no rule defined for 'NaN' in {" +
"'-1': 'You have negative email. Whohoo!'," +
"'0': 'You have no new email'," +
"'one': 'You have one new email'," +
"'other': 'You have {} new emails'}"
]);
expect($log.debug.logs.shift()).toEqual([
"ngPluralize: no rule defined for 'NaN' in undefined"
]);
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = undefined;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = undefined;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = 'a3';
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = 'a3';
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = '011';
$rootScope.$digest();
expect(element.text()).toBe('You have 11 new emails');
expect(elementAlt.text()).toBe('You have 11 new emails');
$rootScope.email = '011';
$rootScope.$digest();
expect(element.text()).toBe('You have 11 new emails');
expect(elementAlt.text()).toBe('You have 11 new emails');
$rootScope.email = '-011';
$rootScope.$digest();
expect(element.text()).toBe('You have -11 new emails');
expect(elementAlt.text()).toBe('You have -11 new emails');
$rootScope.email = '-011';
$rootScope.$digest();
expect(element.text()).toBe('You have -11 new emails');
expect(elementAlt.text()).toBe('You have -11 new emails');
$rootScope.email = '1fff';
$rootScope.$digest();
expect(element.text()).toBe('You have one new email');
expect(elementAlt.text()).toBe('You have one new email');
$rootScope.email = '1fff';
$rootScope.$digest();
expect(element.text()).toBe('You have one new email');
expect(elementAlt.text()).toBe('You have one new email');
$rootScope.email = '0aa22';
$rootScope.$digest();
expect(element.text()).toBe('You have no new email');
expect(elementAlt.text()).toBe('You have no new email');
$rootScope.email = '0aa22';
$rootScope.$digest();
expect(element.text()).toBe('You have no new email');
expect(elementAlt.text()).toBe('You have no new email');
$rootScope.email = '000001';
$rootScope.$digest();
expect(element.text()).toBe('You have one new email');
expect(elementAlt.text()).toBe('You have one new email');
}));
$rootScope.email = '000001';
$rootScope.$digest();
expect(element.text()).toBe('You have one new email');
expect(elementAlt.text()).toBe('You have one new email');
}
));
});
@@ -144,6 +156,33 @@ describe('ngPluralize', function() {
$rootScope.$digest();
expect(element.text()).toBe('');
}));
it('should be able to specify a message for null/undefined values', inject(
function($compile, $rootScope) {
element = $compile(
'<ng:pluralize count="email"' +
"when=\"{'NaN': 'Unspecified email count'," +
"'0': ''," +
"'one': 'Some text'," +
"'other': 'Some text'}\">" +
'</ng:pluralize>')($rootScope);
$rootScope.email = '0';
$rootScope.$digest();
expect(element.text()).toBe('');
$rootScope.email = undefined;
$rootScope.$digest();
expect(element.text()).toBe('Unspecified email count');
$rootScope.email = '1';
$rootScope.$digest();
expect(element.text()).toBe('Some text');
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('Unspecified email count');
}));
});
describe('undefined rule cases', function() {
@@ -195,6 +234,10 @@ describe('ngPluralize', function() {
$rootScope.email = '1';
$rootScope.$digest();
expect(element.text()).toBe('Some text');
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('');
}));
});
@@ -243,6 +286,11 @@ describe('ngPluralize', function() {
$rootScope.$digest();
expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.');
expect(elementAlt.text()).toBe('Igor, Misko and 2 other people are viewing.');
$rootScope.viewCount = null;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
}));
});
@@ -296,7 +344,6 @@ describe('ngPluralize', function() {
describe('bind-once', function() {
it('should support for `count` to be a one-time expression',
inject(function($compile, $rootScope) {
element = $compile(
@@ -320,6 +367,11 @@ describe('ngPluralize', function() {
expect(element.text()).toBe('You have 3 new emails');
expect(elementAlt.text()).toBe('You have 3 new emails');
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('You have 3 new emails');
expect(elementAlt.text()).toBe('You have 3 new emails');
$rootScope.email = 2;
$rootScope.$digest();
expect(element.text()).toBe('You have 3 new emails');
+42
View File
@@ -254,6 +254,20 @@ describe('validators', function() {
expect($rootScope.value).toBe(12345);
expect($rootScope.form.input.$error.minlength).toBeUndefined();
});
it('should validate emptiness against the viewValue', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
var ctrl = inputElm.controller('ngModel');
spyOn(ctrl, '$isEmpty').andCallThrough();
ctrl.$parsers.push(function(value) {
return value + '678';
});
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
});
@@ -410,6 +424,20 @@ describe('validators', function() {
expect($rootScope.value).toBe(12345);
expect($rootScope.form.input.$error.maxlength).toBeUndefined();
});
it('should validate emptiness against the viewValue', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
var ctrl = inputElm.controller('ngModel');
spyOn(ctrl, '$isEmpty').andCallThrough();
ctrl.$parsers.push(function(value) {
return value + '678';
});
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
});
@@ -503,5 +531,19 @@ describe('validators', function() {
$rootScope.$apply("answer = false");
expect(inputElm).toBeValid();
});
it('should validate emptiness against the viewValue', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" required />');
var ctrl = inputElm.controller('ngModel');
spyOn(ctrl, '$isEmpty').andCallThrough();
ctrl.$parsers.push(function(value) {
return value + '678';
});
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
});
});
+30 -1
View File
@@ -271,7 +271,7 @@ describe('Filter: filter', function() {
expect(filter(items, expr, true).length).toBe(1);
expect(filter(items, expr, true)[0]).toBe(items[0]);
// Inherited function proprties
// Inherited function properties
function Item(text) {
this.text = text;
}
@@ -399,6 +399,35 @@ describe('Filter: filter', function() {
});
it('should throw an error when is not used with an array', function() {
var item = {'not': 'array'};
expect(function() { filter(item, {}); }).
toThrowMinErr('filter', 'notarray', 'Expected array but received: {"not":"array"}');
item = Object.create(null);
expect(function() { filter(item, {}); }).
toThrowMinErr('filter', 'notarray', 'Expected array but received: {}');
item = {
toString: null,
valueOf: null
};
expect(function() { filter(item, {}); }).
toThrowMinErr('filter', 'notarray', 'Expected array but received: {"toString":null,"valueOf":null}');
});
it('should return undefined when the array is undefined', function() {
expect(filter(undefined, {})).toBeUndefined();
});
it('should return null when the value of the array is null', function() {
var item = null;
expect(filter(item, {})).toBe(null);
});
describe('should support comparator', function() {
it('not consider `object === "[object Object]"` in non-strict comparison', function() {
+10
View File
@@ -456,6 +456,16 @@ describe('filters', function() {
it('should use UTC if the timezone is set to "UTC"', function() {
expect(date(new Date(2003, 8, 10, 3, 2, 4), 'yyyy-MM-dd HH-mm-ss')).toEqual('2003-09-10 03-02-04');
expect(date(new Date(Date.UTC(2003, 8, 10, 3, 2, 4)), 'yyyy-MM-dd HH-mm-ss', 'UTC')).toEqual('2003-09-10 03-02-04');
expect(date(new Date(Date.UTC(2003, 8, 10, 3, 2, 4)), 'yyyy-MM-dd HH-mm-ssZ', 'UTC')).toEqual('2003-09-10 03-02-04+0000');
});
it('should support conversion to any timezone', function() {
expect(date(new Date(Date.UTC(2003, 8, 10, 3, 2, 4)), 'yyyy-MM-dd HH-mm-ssZ', 'GMT+0500')).toEqual('2003-09-10 08-02-04+0500');
});
it('should fallback to default timezone in case an unknown timezone was passed', function() {
var value = new angular.mock.TzDate(-2, '2003-09-10T01:02:04.000Z');
expect(date(value, 'yyyy-MM-dd HH-mm-ssZ', 'WTF')).toEqual('2003-09-10 03-02-04+0200');
});
});
});
+20 -7
View File
@@ -1272,7 +1272,7 @@ describe('$location', function() {
it('should not rewrite links with target="_blank"', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="_blank"'});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="_blank"'});
inject(
initBrowser(),
initLocation(),
@@ -1285,7 +1285,7 @@ describe('$location', function() {
it('should not rewrite links with target specified', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="some-frame"'});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="some-frame"'});
inject(
initBrowser(),
initLocation(),
@@ -1323,7 +1323,7 @@ describe('$location', function() {
});
it ('should not rewrite links when rewriting links is disabled', function() {
it('should not rewrite links when rewriting links is disabled', function() {
configureService({linkHref: 'link?a#b', html5Mode: {enabled: true, rewriteLinks:false}, supportHist: true});
inject(
initBrowser(),
@@ -1480,7 +1480,7 @@ describe('$location', function() {
});
it('should not rewrite when clicked with ctrl pressed', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
inject(
initBrowser(),
initLocation(),
@@ -1493,7 +1493,7 @@ describe('$location', function() {
it('should not rewrite when clicked with meta pressed', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
inject(
initBrowser(),
initLocation(),
@@ -1505,7 +1505,7 @@ describe('$location', function() {
});
it('should not rewrite when right click pressed', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
inject(
initBrowser(),
initLocation(),
@@ -1539,6 +1539,19 @@ describe('$location', function() {
});
it('should not rewrite when clicked with shift pressed', function() {
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
inject(
initBrowser(),
initLocation(),
function($browser) {
browserTrigger(link, 'click', { keys: ['shift'] });
expectNoRewrite($browser);
}
);
});
it('should not mess up hash urls when clicking on links in hashbang mode', function() {
var base;
module(function() {
@@ -1847,7 +1860,7 @@ describe('$location', function() {
})
);
it ('should fire $locationChangeSuccess event when change from browser location bar',
it('should fire $locationChangeSuccess event when change from browser location bar',
inject(function($log, $location, $browser, $rootScope) {
$rootScope.$apply(); // clear initial $locationChangeStart
+1645 -7
View File
File diff suppressed because it is too large Load Diff
+59 -1
View File
@@ -14,7 +14,7 @@ describe('Scope', function() {
it('should expose the constructor', inject(function($rootScope) {
/* jshint -W103 */
if (msie) return;
if (msie < 11) return;
expect($rootScope.__proto__).toBe($rootScope.constructor.prototype);
}));
@@ -135,9 +135,67 @@ describe('Scope', function() {
it('should not keep constant expressions on watch queue', inject(function($rootScope) {
$rootScope.$watch('1 + 1', function() {});
expect($rootScope.$$watchers.length).toEqual(1);
expect($rootScope.$$watchersCount).toEqual(1);
$rootScope.$digest();
expect($rootScope.$$watchers.length).toEqual(0);
expect($rootScope.$$watchersCount).toEqual(0);
}));
it('should decrement the watcherCount when destroying a child scope', inject(function($rootScope) {
var child1 = $rootScope.$new(),
child2 = $rootScope.$new(),
grandChild1 = child1.$new(),
grandChild2 = child2.$new();
child1.$watch('a', function() {});
child2.$watch('a', function() {});
grandChild1.$watch('a', function() {});
grandChild2.$watch('a', function() {});
expect($rootScope.$$watchersCount).toBe(4);
expect(child1.$$watchersCount).toBe(2);
expect(child2.$$watchersCount).toBe(2);
expect(grandChild1.$$watchersCount).toBe(1);
expect(grandChild2.$$watchersCount).toBe(1);
grandChild2.$destroy();
expect(child2.$$watchersCount).toBe(1);
expect($rootScope.$$watchersCount).toBe(3);
child1.$destroy();
expect($rootScope.$$watchersCount).toBe(1);
}));
it('should decrement the watcherCount when calling the remove function', inject(function($rootScope) {
var child1 = $rootScope.$new(),
child2 = $rootScope.$new(),
grandChild1 = child1.$new(),
grandChild2 = child2.$new(),
remove1,
remove2;
remove1 = child1.$watch('a', function() {});
child2.$watch('a', function() {});
grandChild1.$watch('a', function() {});
remove2 = grandChild2.$watch('a', function() {});
remove2();
expect(grandChild2.$$watchersCount).toBe(0);
expect(child2.$$watchersCount).toBe(1);
expect($rootScope.$$watchersCount).toBe(3);
remove1();
expect(grandChild1.$$watchersCount).toBe(1);
expect(child1.$$watchersCount).toBe(1);
expect($rootScope.$$watchersCount).toBe(2);
// Execute everything a second time to be sure that calling the remove funciton
// several times, it only decrements the counter once
remove2();
expect(child2.$$watchersCount).toBe(1);
expect($rootScope.$$watchersCount).toBe(2);
remove1();
expect(child1.$$watchersCount).toBe(1);
expect($rootScope.$$watchersCount).toBe(2);
}));
it('should not keep constant literals on the watch queue', inject(function($rootScope) {
+2 -4
View File
@@ -87,11 +87,9 @@ describe('$sniffer', function() {
var ua = $window.navigator.userAgent.toLowerCase();
if (/chrome/i.test(ua) || /safari/i.test(ua) || /webkit/i.test(ua)) {
expectedPrefix = 'Webkit';
}
else if (/firefox/i.test(ua)) {
} else if (/firefox/i.test(ua)) {
expectedPrefix = 'Moz';
}
else if (/ie/i.test(ua) || /trident/i.test(ua)) {
} else if (/ie/i.test(ua) || /trident/i.test(ua)) {
expectedPrefix = 'Ms';
}
expect($sniffer.vendorPrefix).toBe(expectedPrefix);
+2 -1
View File
@@ -17,7 +17,7 @@ describe('$templateRequest', function() {
}));
it('should cache the request to prevent extra downloads',
inject(function($rootScope, $templateRequest, $httpBackend) {
inject(function($rootScope, $templateRequest, $templateCache, $httpBackend) {
$httpBackend.expectGET('tpl.html').respond('matias');
@@ -34,6 +34,7 @@ describe('$templateRequest', function() {
expect(content[0]).toBe('matias');
expect(content[1]).toBe('matias');
expect($templateCache.get('tpl.html')).toBe('matias');
}));
it('should throw an error when the template is not found',
+37 -4
View File
@@ -787,6 +787,39 @@ describe('ngMock', function() {
expect(testFn.$$hashKey).toBeUndefined();
});
});
describe('$inject cleanup', function() {
function testFn() {
}
it('should add $inject when invoking test function', inject(function($injector) {
$injector.invoke(testFn);
expect(testFn.$inject).toBeDefined();
}));
it('should cleanup $inject after previous test', function() {
expect(testFn.$inject).toBeUndefined();
});
it('should add $inject when annotating test function', inject(function($injector) {
$injector.annotate(testFn);
expect(testFn.$inject).toBeDefined();
}));
it('should cleanup $inject after previous test', function() {
expect(testFn.$inject).toBeUndefined();
});
it('should invoke an already annotated function', inject(function($injector) {
testFn.$inject = [];
$injector.invoke(testFn);
}));
it('should not cleanup $inject after previous test', function() {
expect(testFn.$inject).toBeDefined();
});
});
});
describe('in DSL', function() {
@@ -1193,7 +1226,7 @@ describe('ngMock', function() {
});
it ('should throw exception when only headers differs from expectation', function() {
it('should throw exception when only headers differs from expectation', function() {
hb.when('GET').respond(200, '', {});
hb.expect('GET', '/match', undefined, {'Content-Type': 'application/json'});
@@ -1204,7 +1237,7 @@ describe('ngMock', function() {
});
it ('should throw exception when only data differs from expectation', function() {
it('should throw exception when only data differs from expectation', function() {
hb.when('GET').respond(200, '', {});
hb.expect('GET', '/match', 'some-data');
@@ -1215,7 +1248,7 @@ describe('ngMock', function() {
});
it ('should not throw an exception when parsed body is equal to expected body object', function() {
it('should not throw an exception when parsed body is equal to expected body object', function() {
hb.when('GET').respond(200, '', {});
hb.expect('GET', '/match', {a: 1, b: 2});
@@ -1230,7 +1263,7 @@ describe('ngMock', function() {
});
it ('should throw exception when only parsed body differs from expected body object', function() {
it('should throw exception when only parsed body differs from expected body object', function() {
hb.when('GET').respond(200, '', {});
hb.expect('GET', '/match', {a: 1, b: 2});
+24
View File
@@ -1341,6 +1341,30 @@ describe('$route', function() {
});
});
it('should not update query params when an optional property was previously not in path', function() {
var routeChangeSpy = jasmine.createSpy('route change');
module(function($routeProvider) {
$routeProvider.when('/bar/:barId/:fooId/:spamId/:eggId?', {controller: angular.noop});
});
inject(function($route, $routeParams, $location, $rootScope) {
$rootScope.$on('$routeChangeSuccess', routeChangeSpy);
$location.path('/bar/1/2/3');
$location.search({initial: 'true'});
$rootScope.$digest();
routeChangeSpy.reset();
$route.updateParams({barId: '5', fooId: '6', eggId: '4'});
$rootScope.$digest();
expect($routeParams).toEqual({barId: '5', fooId: '6', spamId: '3', eggId: '4', initial: 'true'});
expect(routeChangeSpy).toHaveBeenCalledOnce();
expect($location.path()).toEqual('/bar/5/6/3/4');
expect($location.search()).toEqual({initial: 'true'});
});
});
it('should complain if called without an existing route', inject(function($route) {
expect($route.updateParams).toThrowMinErr('ngRoute', 'norout');
+97
View File
@@ -118,6 +118,75 @@ describe('angular.scenario.Application', function() {
expect(called).toBeTruthy();
});
it('should set rootElement when navigateTo instigates bootstrap', inject(function($injector, $browser) {
var called;
var testWindow = {
document: jqLite('<div class="test-foo"></div>')[0],
angular: {
element: jqLite,
service: {},
resumeBootstrap: noop
}
};
jqLite(testWindow.document).data('$injector', $injector);
var resumeBootstrapSpy = spyOn(testWindow.angular, 'resumeBootstrap').andReturn($injector);
var injectorGet = $injector.get;
spyOn($injector, 'get').andCallFake(function(name) {
switch (name) {
case "$rootElement": return jqLite(testWindow.document);
default: return injectorGet(name);
}
});
app.getWindow_ = function() {
return testWindow;
};
app.navigateTo('http://localhost/', noop);
callLoadHandlers(app);
expect(app.rootElement).toBe(testWindow.document);
expect(resumeBootstrapSpy).toHaveBeenCalled();
dealoc(testWindow.document);
}));
it('should set setup resumeDeferredBootstrap if resumeBootstrap is not yet defined', inject(function($injector, $browser) {
var called;
var testWindow = {
document: jqLite('<div class="test-foo"></div>')[0],
angular: {
element: jqLite,
service: {},
resumeBootstrap: null
}
};
jqLite(testWindow.document).data('$injector', $injector);
var injectorGet = $injector.get;
var injectorSpy = spyOn($injector, 'get').andCallFake(function(name) {
switch (name) {
case "$rootElement": return jqLite(testWindow.document);
default: return injectorGet(name);
}
});
app.getWindow_ = function() {
return testWindow;
};
app.navigateTo('http://localhost/', noop);
expect(testWindow.angular.resumeDeferredBootstrap).toBeUndefined();
callLoadHandlers(app);
expect(testWindow.angular.resumeDeferredBootstrap).toBeDefined();
expect(app.rootElement).toBeUndefined;
expect(injectorSpy).not.toHaveBeenCalled();
var resumeBootstrapSpy = spyOn(testWindow.angular, 'resumeBootstrap').andReturn($injector);
testWindow.angular.resumeDeferredBootstrap();
expect(app.rootElement).toBe(testWindow.document);
expect(resumeBootstrapSpy).toHaveBeenCalled();
expect(injectorSpy).toHaveBeenCalledWith("$rootElement");
dealoc(testWindow.document);
}));
it('should wait for pending requests in executeAction', inject(function($injector, $browser) {
var called, polled;
var handlers = [];
@@ -144,4 +213,32 @@ describe('angular.scenario.Application', function() {
handlers[0]();
dealoc(testWindow.document);
}));
it('should allow explicit rootElement', inject(function($injector, $browser) {
var called, polled;
var handlers = [];
var testWindow = {
document: jqLite('<div class="test-foo"></div>')[0],
angular: {
element: jqLite,
service: {}
}
};
$browser.notifyWhenNoOutstandingRequests = function(fn) {
handlers.push(fn);
};
app.rootElement = testWindow.document;
jqLite(testWindow.document).data('$injector', $injector);
app.getWindow_ = function() {
return testWindow;
};
app.executeAction(function($window, $document) {
expect($window).toEqual(testWindow);
expect($document).toBeDefined();
expect($document[0].className).toEqual('test-foo');
});
expect(handlers.length).toEqual(1);
handlers[0]();
dealoc(testWindow.document);
}));
});
+2 -1
View File
@@ -43,8 +43,9 @@ describe('angular.scenario.SpecRunner', function() {
runner = $root.$new();
var Cls = angular.scenario.SpecRunner;
for (var name in Cls.prototype)
for (var name in Cls.prototype) {
runner[name] = angular.bind(runner, Cls.prototype[name]);
}
Cls.call(runner);
}));
+1 -1
View File
@@ -624,7 +624,7 @@ describe("angular.scenario.dsl", function() {
});
it('should match bindings by substring match', function() {
compile('<pre ng-bind="foo.bar | filter"></pre>', 'binding value');
compile('<pre ng-bind="foo.bar | lowercase"></pre>', 'binding value');
$root.dsl.binding('foo . bar');
expect($root.futureResult).toEqual('binding value');
});