Compare commits

..

85 Commits

Author SHA1 Message Date
Martin Staffa 30ab202fa7 fixup! fix(ngAria.ngClick): restrict preventDefault on space / enter to non-interactive elements 2018-09-05 19:33:33 +02:00
Martin Staffa 190bc1d52b fixup! fix(ngAria.ngClick): restrict preventDefault on space / enter to non-interactive elements 2018-09-03 09:32:08 +02:00
Martin Staffa 4be80aefa2 fix(ngAria.ngClick): restrict preventDefault on space / enter to non-interactive elements
Fixes #16664
2018-09-01 11:14:20 +02:00
Martin Staffa 837e519acc fix(ngHref): allow numbers and other objects in interpolation
Interpolated content in ngHref must be stringified before being passed to $$sanitizeUri by $sce. Before 1.7.x, the sanitization had happened on the already interpolated value inside $compile.

Closes #16652
Fixes #16626
2018-08-20 20:04:28 +02:00
John Mantas ad7ea95386 docs(ngRepeat): redundant "and" on line 77
Closes #16657
2018-08-07 16:20:40 +03:00
George Kalpakas fd4a284c85 docs(guide/migration): fix typos, format inline code 2018-08-06 15:51:03 +03:00
Martin Staffa 79ca0f1e91 fix(select): allow to select first option with value undefined
Previously, the value observer incorrectly assumed a value had changed even if
it was the first time it was set, which caused it to remove an option with
the value `undefined` from the internal option map.

Fixes #16653
Closes #16656
2018-08-06 12:47:43 +02:00
Martin Staffa 848c9b51f1 docs(select): remove solved known issue
The issue in question has been resolved some time in 2017.
The bug report is still open, but the behavior has changed:
https://bugzilla.mozilla.org/show_bug.cgi?id=126379

Let's hope they have tests for this!
2018-08-03 18:16:19 +02:00
Martin Staffa 16b0692885 docs(CHANGELOG.md): add changes for 1.7.3 2018-08-03 13:36:59 +02:00
Martin Staffa af3b71ef46 docs(\$compile): add docs for ngProp and ngOn bindings
The docs are written as if ngProp and ngOn were regular directives,
which makes them easier to find.

Closes #16627
2018-08-03 12:17:03 +02:00
Jason Bedard dedb10c0b8 feat($compile): add support for arbitrary DOM property and event bindings
Properties:

Previously only arbitrary DOM attribute bindings were supported via interpolation such as
`my-attribute="{{expression}}"` or `ng-attr-my-attribute="{{expression}}"`, and only a set of
distinct properties could be bound. `ng-prop-*` adds support for binding expressions to any DOM
properties. For example `ng-prop-foo="x"` will assign the value of the expression `x` to the
`foo` property, and re-assign whenever the expression `x` changes.

Events:

Previously only a distinct set of DOM events could be bound using directives such as `ng-click`,
`ng-blur` etc. `ng-on-*` adds support for binding expressions to any DOM event. For example
`ng-on-bar="barOccured($event)"` will add a listener to the "bar" event and invoke the
`barOccured($event)` expression.

Since HTML attributes are case-insensitive, property and event names are specified in snake_case
for `ng-prop-*` and `ng-on-*`. For example, to bind property `fooBar` use `ng-prop-foo_bar`, to
listen to event `fooBar` use `ng-on-foo_bar`.

Fixes #16428
Fixes #16235
Closes #16614
2018-08-01 17:50:25 -07:00
Jason Bedard 88a12f8623 refactor($compile): move img[srcset] sanitizing to helper method 2018-08-01 17:50:25 -07:00
Georgii Dolzhykov ebeb1c9491 fix(ngMock): pass failed HTTP expectations to $exceptionHandler
This was only partially fixed in f18dd2957.

Closes #16644
2018-07-31 14:35:39 +03:00
George Kalpakas 864c7f00c4 fix($location): avoid unnecessary $locationChange* events due to empty hash
Fixes #16632

Closes #16636
2018-07-30 23:36:31 +03:00
George Kalpakas 2907798ca5 test($location): add assertion 2018-07-30 23:36:12 +03:00
George Kalpakas 8562a62197 refactor($browser): correctly export helper used in specs
The helper is used in `fakeWindow.location.hash`. ATM, no test is using
the `hash` getter, so there were no errors.
2018-07-30 23:36:12 +03:00
George Kalpakas fdfba097da refactor($location): remove unnecessary capturing group in RegExp 2018-07-30 23:36:12 +03:00
George Kalpakas a07191727d refactor($location): minor changes (unused deps, exported globals, unused deps, etc) 2018-07-30 23:36:12 +03:00
George Kalpakas c133ef8360 fix($animate): avoid memory leak with $animate.enabled(element, enabled)
When disabling/enabling animations on a specific element (via
`$animate.enabled(element, enabled)`), the element is added in a map to
track its state. Previously, the element was never removed from the map,
causing AngularJS to hold on to the element even after it is removed
from the DOM, thus preventing it from being garbage collected.

This commit fixes it by removing the element from the map on `$destroy`.

Fixes #16637.

Closes #16649
2018-07-27 20:46:25 +03:00
Martin Staffa aa7d45e804 fix($compile): use correct parent element when requiring on html element
Fixes #16535
Closes #16647
2018-07-27 17:10:25 +02:00
Martin Staffa 6b915ad9db fix(Angular): add workaround for Safari / Webdriver problem
Closes #16645
2018-07-26 10:43:14 +02:00
Martin Staffa a42f8a0d5b fix(ngEventDirs): pass error in handler to $exceptionHandler when event was triggered in a digest
This ensures that the error handling is the same for events triggered inside and outside a digest.
2018-07-25 12:43:14 +02:00
Mark Gardner 6b0193e4d9 fix(ngEventDirs): don't wrap the event handler in $apply if already in $digest
Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

Closes #14673
Closes #14674
2018-07-25 12:43:14 +02:00
George Kalpakas e500fb6ddb fix(angular.element): do not break on cleanData() if _data() returns undefined
This shouldn't happen in supported jQuery versions (2+), but if someone
uses the unsupported 1.x version the app will break. The change that
causes this new behavior was introduced in b7d396b8b.

Even though jQuery 1.x is not supported, it is worth avoiding the
unnecessary breakage (given how simple).

Fixes #16641

Closes #16642
2018-07-23 14:20:31 +03:00
George Kalpakas 204f9ffebe docs(browserTrigger): document eventData.data property 2018-07-22 19:09:19 +03:00
George Kalpakas 7eee0c2c13 docs($route): fix typo (inluding --> including) 2018-07-22 18:44:59 +03:00
George Kalpakas 2dc83b2f04 docs(ngMock/$interval.flush): fix param type (not optional)
Closes #16640
2018-07-22 17:14:55 +03:00
George Kalpakas 3154111bb4 chore(saucelabs): upgrade sauce-connect to latest version
Closes #16639
2018-07-21 13:36:24 +03:00
George Kalpakas 2f6f1a7d70 chore(karma): upgrade karma and related dependencies to latest versions 2018-07-21 13:35:50 +03:00
Jason Bedard ff60b3f47a refactor($location): move repeated path normalization code into helper method (#16618)
Closes #16618
2018-07-21 02:39:38 -07:00
George Kalpakas c2d0783ca9 docs(ngMocks): fix type for $flushPendingTasks/$verifyPendingsTasks
Closes #16638
2018-07-21 10:44:57 +03:00
George Kalpakas 6706353a71 docs(ngMock/$timeout): deprecate flush() and verifyNoPendingTasks()
Closes #16603
2018-07-13 13:31:50 +03:00
George Kalpakas 24eeab0ebd docs(ngMock/$timeout): recommend $verifyNoPendingTasks() when appropriate
For historical reasons, `$timeout.verifyNoPendingTasks()` throws if
there is any type of task pending (even if it is not a timeout). When
calling `$timeout.verifyNoPendingTasks()`, the user is most likely
interested in verifying pending timeouts only, which is now possible
with `$verifyNoPendingTasks('$timeout')`.

To raise awareness of `$verifyNoPendingTasks()`, it is mentioned in the
error message thrown by `$timeoutverifyNoPendingTasks()` if none of the
pending tasks is a timeout.
2018-07-13 13:31:48 +03:00
George Kalpakas b14f67f4ae feat(ngMock): add $flushPendingTasks() and $verifyNoPendingTasks()
`$flushPendingTasks([delay])` allows flushing all pending tasks (or up
to a specific delay). This includes `$timeout`s, `$q` promises and tasks
scheduled via `$rootScope.$applyAsync()` and `$rootScope.$evalAsync()`.
(ATM, it only flushes tasks scheduled via `$browser.defer()`, which does
not include `$http` requests and `$route` transitions.)

`$verifyNoPendingTasks([taskType])` allows verifying that there are no
pending tasks (in general or of a specific type). This includes tasks
flushed by `$flushPendingTasks()` as well as pending `$http` requests
and in-progress `$route` transitions.

Background:
`ngMock/$timeout` has `flush()` and `verifyNoPendingTasks()` methods,
but they take all kinds of tasks into account which is confusing. For
example, `$timeout.verifyNoPendingTasks()` can fail (even if there are
no pending timeouts) because of an unrelated pending `$http` request.

This behavior is retained for backwards compatibility, but the new
methods are more generic (and thus less confusing) and also allow
more fine-grained control (when appropriate).

Closes #14336
2018-07-13 13:31:48 +03:00
George Kalpakas 411e35472b refactor($browser): share task-tracking code between $browser and ngMock/$browser
This avoids code/logic duplication and helps the implementations stay
in-sync.
2018-07-13 13:31:46 +03:00
George Kalpakas af59a0a00b refactor($interval): share code between $interval and ngMock/$interval
This avoids code/logic duplication and helps the implementations stay
in-sync.
2018-07-13 13:31:44 +03:00
George Kalpakas 1c380b7671 refactor(ngMock/$interval): more closely follow actual $interval's internal implementation 2018-07-13 13:31:44 +03:00
George Kalpakas 84fb041a89 feat(*): implement more granular pending task tracking
Previously, all pending async tasks (tracked via `$browser`) are treated
the same. I.e. things like `$$testability.whenStable()` and
`ngMock#$timeout.verifyNoPendingTasks()` take all tasks into account.

Yet, in some cases we might be interested in specific tasks only. For
example, if one wants to verify there are no pending `$timeout`s, they
don't care if there are other pending tasks, such as `$http` requests.
Similarly, one might want to get notified when all `$http` requests have
completed and does not care about pending promises.

This commit adds support for more granular task tracking, by enabling
callers to specify the type of task that is being added/removed from the
queue and enabling listeners to be triggered when specific types of
tasks are completed (even if there are more pending tasks of different
types).

The change is backwards compatible. I.e. calling the affected methods
with no explicit task-type, behaves the same as before.

Related to #14336.
2018-07-13 13:31:43 +03:00
Jason Bedard aee7d53a6b fix($location): fix infinite recursion/digest on URLs with special characters
Some characters are treated differently by `$location` compared to `$browser` and
the native browser. When comparing URLs across these two services this must be
taken into account.

Fixes #16592
Closes #16611
2018-07-09 19:39:25 -07:00
George Kalpakas bfdc9170f2 fix($compile): work around Firefox DocumentFragment bug
DOM nodes passed to `compilationGenerator()` will eventually be wrapped
in `jqLite`, when the compilation actually happens. In Firefox 60+,
there seems to be a `DocumentFragment`-related bug that sometimes causes
the `childNodes` to be empty at the time the compilation happens.

This commit works around this bug by eagerly wrapping `childNodes` in
`jqLite`.

NOTE:
The wrapped nodes have references to their `DocumentFragment` container.
This is "by design", since we want to be able to traverse the nodes via
`nextSibling` (in order to correctly handle multi-element directives).

Once the nodes are compiled, they will be either moved to a new
container element or the `jqLite` wrapper is release making them
eligible for garbage collection. In both cases, the original
`DocumentFragment` container should be eligible for garbage collection
too.

Fixes #16607

Closes #16615
2018-07-09 21:21:24 +03:00
Martin Staffa 494277e3d3 docs($animate): clarify possible options and fired events 2018-07-09 17:10:03 +02:00
Martin Staffa e4339bc512 feat($animate): add option data to event callbacks
Closes #12697
Closes #13059
2018-07-09 17:10:03 +02:00
Matias Niemelä 0e26197623 perf(ngAnimate): avoid repeated calls to addClass/removeClass when animation has no duration
Background:
ngAnimate writes helper classes to DOM elements to see if animations are defined on them. If many
elements have the same definition, and the same parent, we can cache the definition and skip the
application of the helper classes altogether. This helps particularly with large ngRepeat
collections.

Closes #14165
Closes #14166
Closes #16613
2018-07-05 19:44:34 +02:00
Martin Staffa 74726b4f79 docs(changelog, guide/Migration): add info about $sce BC in 1.7
Closes #16593
Closes #16622
2018-07-04 14:49:40 +02:00
Martin Staffa 3ad07eadef docs(guide/Using Location): change / remove obsolete information 2018-07-04 14:46:26 +02:00
Martin Staffa 7920f7140f docs(*): fix headlines 2018-07-04 14:46:26 +02:00
Atef Ben Ali 66fdf2a8a7 docs(guide/component): add missing :
Add `:` to `Components have a well-defined lifecycle` title.

Closes #16620
2018-06-29 11:10:01 +03:00
Martin Staffa 26a5779cdd chore(benchpress): add ngRepeat animation benchmark
Closes #13976
2018-06-25 16:54:33 +02:00
Martin Staffa a90d0cbbba chore: fix eslint error 2018-06-23 17:56:45 +02:00
Pete Bacon Darwin 9307ccf6ee chore(docs-app): ensure ToC links contain the path
Without the path the link is always pointing to the
root page, rather than the current page, which means
that copying the link address or opening the page in
a new tab is broken.

Closes #16608
2018-06-22 21:54:51 +01:00
Ilia Choly 2c7aa68ca8 fix(grunt-utils): correctly detect java 32bit support
Closes #16605
2018-06-20 21:06:25 +03:00
Georgii Dolzhykov 840b5f0a76 fix(ngMock/$httpBackend): correctly ignore query params in {expect,when}Route
Previously, a route definition such as
`$httpBackend.whenRoute('GET', '/route/:id')` matched against a URL with
query params, for example `/route/1?q=foo`, would incorrectly include
the query params in `id`: `{id: '1?q=foo', q: 'foo'}`.

This commit fixes it, so that the extracted `params` will now be:
`{id: '1', q: 'foo'}`.

Fixes #14173

Closes #16589
2018-06-18 19:42:50 +03:00
George Kalpakas 6c224a2a60 fix(ngAria): do not scroll when pressing spacebar on custom buttons
By default, pressing spacebar causes the browser to scroll down.
However, when a native button is focused, the button is clicked instead.

`ngAria`'s `ngClick` directive, sets elements up to behave like buttons.
For example, it adds `role="button"` and forwards `ENTER` and `SPACEBAR`
keypresses to the `click` handler (to emulate the behavior of native
buttons).

Yet, pressing spacebar on such an element, still invokes the default
browser behavior of scrolling down.

This commit fixes this, by calling `preventDefault()` on the keyboard
event, thus preventing the default scrolling behavior and making custom
buttons behave closer to native ones.

Closes #14665

Closes #16604
2018-06-18 18:44:46 +03:00
George Kalpakas af1e6a38b7 refactor(ngAria): clean up accessible actions tests 2018-06-18 18:44:27 +03:00
Martin Staffa 5b11145473 feat(form.FormController): add $getControls()
Closes #16601
Fixes #14749
Closes #14517
Closes #13202
2018-06-18 16:02:19 +02:00
Martin Staffa d7d64cc363 docs(downloading.ngdoc): remove link to Google CDN overview page
AngularJS is no longer listed on the CDN page, because the available versions
were almost always out of date due to the need to manually update the list
2018-06-14 18:06:00 +02:00
Pete Bacon Darwin 998d8b4b55 chore(code.angularjs.org): fix directory list paging 2018-06-14 10:48:59 +01:00
Pete Bacon Darwin 2472d621d7 chore(code.angularjs.org): update firebase libraries 2018-06-14 08:37:22 +01:00
Martin Staffa 83f7980e2f feat(ngModelOptions): add timeStripZeroSeconds and timeSecondsFormat
Closes #10721
Closes #16510
Closes #16584
2018-06-12 22:45:17 +01:00
George Kalpakas c9a92fcad5 docs(CHANGELOG): add release notes for 1.7.2 2018-06-12 16:33:59 +03:00
George Kalpakas f7b2fffa51 chore(doc-gen): error on unmatched links
Closes #16597
2018-06-12 14:40:32 +03:00
George Kalpakas 9a065c0fa9 docs(*): fix dangling or ambiguous links 2018-06-12 14:40:21 +03:00
George Kalpakas 42e622b751 revert: refactor($compile): remove preAssignBindingsEnabled leftovers
This reverts commit 8e104ee508.

This internal clean-up turned out to break popular UI libraries (e.g.
`ngMaterial`, `ui-bootstrap`) and cause pain to developers.

Fixes #16594

Closes #16595
2018-06-11 16:58:54 +03:00
George Kalpakas ad0ba99d8a docs(CHANGELOG): fix links to issues/PRs 2018-06-08 17:09:30 +03:00
George Kalpakas 05170bf371 docs(CHANGELOG): add release notes for 1.7.1 2018-06-08 16:25:08 +03:00
Georgios Kalpakas db584f7835 feat(ngAria): add support for ignoring a specific element
Fixes #14602
Fixes #14672

Closes #14833
2018-06-08 11:11:55 +03:00
Georgios Kalpakas 83d1229c87 refactor(ngAria): move test helpers inside of closure 2018-06-08 11:02:49 +03:00
Georgios Kalpakas 60069e67ae feat($route): add support for the reloadOnUrl configuration option
Enables users to specify that a particular route should not be reloaded after a
URL change (including a change in `$location.path()`), if the new URL maps to
the same route.
The default behavior is still to always load the matched route when any part of
the URL changes.

Related to #1699, #5860, #14999 (potentially closing the first two).

Fixes #7925

Closes #15002
2018-06-08 10:58:53 +03:00
Martin Staffa 84d80be2b4 feat(ngMessages): add support for default message
add support for showing default message when a truthy value is not matched by an ng-message directive.

Closes #12008
Closes #12213
Closes #16587
2018-06-06 17:40:55 +02:00
Martin Staffa 03a4782a35 feat(errorHandlingConfig): add option to exclude error params from url
Specific errors, such as those during nested module loading, can create very long
error urls because the error message includes the error stack. These urls create visual
clutter in the browser console, are often not clickable, and may be rejected
by the docs page because they are simply too long.

We've already made improvements to the error display in #16283, which excludes
the error url from error parameters, which results in cleaner error messages.

Further, modern browsers restrict console message length intelligently.

This option can still be useful for older browsers like Internet Explorer, or
in general to reduce visual clutter in the console.

Closes #14744
Closes #15707
Closes #16283
Closes #16299 
Closes #16591
2018-06-06 15:03:09 +02:00
Jakub Freisler 0ed36430da docs(ngAnimate): add "animating between value changes" section
Add a section which covers use case when users need to animate upon
a variable's value changes (not between two states).

Refers #16561

Closes #16582
2018-06-05 23:22:25 +03:00
Martin Staffa aa6adc77ae feat(ngRef): add directive to publish controller, or element into scope
Thanks to @drpicox for the original implementation: PR #14080 

Closes #16511
2018-06-05 15:00:12 +02:00
Georgii Dolzhykov a7de87d3e8 docs($httpBackend): headers param of expect* can be function
Closes #16588
2018-06-04 14:31:06 +03:00
Christian Oliff 1bb9aa0523 docs(.editorconfig): change link to use https 2018-06-04 10:13:43 +02:00
Jakub Freisler 586b6e8b73 docs(ngAnimate): refactor of ngAnimate docs
- Synced "animation aware" directives tables in API docs and "Animation"
  guide.
- Sorted tables alphabetically.
- Added info about `ngAnimateSwap` directive.

References #16561

Closes #16581
2018-06-02 11:48:20 +03:00
George Kalpakas 459997b482 fix(ngModel): do not throw if view value changes on destroyed scope
This could for example happen if updating the value is debounced (either
by asynchronously calling `$setViewValue()` or via `ngModelOptions`).

Fixes #16583

Closes #16585
2018-05-31 10:51:53 +03:00
Georgii Dolzhykov 8e104ee508 refactor($compile): remove preAssignBindingsEnabled leftovers
Now that we don't need to support `preAssignBindingsEnabled` (removed in #15782),
complexity introduced in `$controller` by #7645 can be removed.

One difference with the previous implementation is that all non-ES2015-class controller instances
were available on the element before calling their constructors. Now it depends on the relative
order of controllers. Controller constructors shouldn't be used to access other controllers
(e.g. via `$element.controller(directiveName)`). The recommended way is to use the `require`
property of the directive definition object and the life cycle hooks `$onChanges` or `$onInit`.

See
https://docs.angularjs.org/api/ng/service/$compile#-require-
https://docs.angularjs.org/api/ng/service/$compile#life-cycle-hooks

Closes #16580
2018-05-30 23:01:52 +03:00
George Kalpakas 78b9f61366 fix($compile): support transcluding multi-element directives
Previously, transcluding multi-element directives (e.g. `foo-start`/`foo-end`)
was not supported on elements with multi-slot transclusion (a `uterdir` error
was thrown).
This commit fixes it by putting the transcluded nodes into a DocumentFragment,
where they can be traversed via `.nextSibling`.

Fixes #15554
Closes #15555
2018-05-26 16:11:09 +02:00
Martin Staffa b9f19ad0d4 docs(ngMockE2E): add docs for $httpBackend.matchLatestDefinitionEnabled()
Closes #16577
2018-05-26 16:05:51 +02:00
Martin Staffa 0b1adbb2e7 chore: try ios 11.2 2018-05-25 21:17:55 +02:00
Martin Staffa 6a1d7dd7dc chore(saucelabs): remove platform for ios11 2018-05-25 20:18:30 +02:00
George Kalpakas 5262039096 refactor(ngModelOptions): fix ng-closure-runner warning
Without this fix `grunt minall` emits the following warning:
> WARNING - Parse error. Non-JSDoc comment has annotations.
> Did you mean to start it with '/**'?

Closes #16575
2018-05-24 12:59:30 +03:00
Martin Staffa 96c4cb6694 chore: set ios 11 in saucelabs to 11.3 2018-05-23 23:36:48 +02:00
Martin Staffa 9fe596422f chore: add platform to ios saucelabs 2018-05-23 22:42:14 +02:00
Pete Bacon Darwin e22c5d3dba chore(package.json): update dist-tag for master branch 2018-05-23 19:20:29 +01:00
99 changed files with 1626 additions and 3926 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ https://plnkr.co or similar (you can use this template as a starting point: http
<!-- Check whether this is still an issue in the most recent stable or in the snapshot AngularJS
version (https://code.angularjs.org/snapshot/) -->
**Browser:** [all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView | Opera XX ]
**Browser:** [all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
<!-- All browsers where this could be reproduced (and Operating System if relevant) -->
**Anything else:**
+1 -2
View File
@@ -15,7 +15,6 @@ env:
- JOB=ci-checks
- JOB=unit-core BROWSER_PROVIDER=saucelabs
- JOB=unit-jquery BROWSER_PROVIDER=saucelabs
- JOB=unit-modules BROWSER_PROVIDER=saucelabs
- JOB=docs-app BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
@@ -27,7 +26,7 @@ env:
- secure: oTBjhnOKhs0qDSKTf7fE4f6DYiNDPycvB7qfSF5QRIbJK/LK/J4UtFwetXuXj79HhUZG9qnoT+5e7lPaiaMlpsIKn9ann7ffqFWN1E8TMtpJF+AGigx3djYElwfgf5nEnFUFhwjFzvbfpZNnxVGgX5YbIZpe/WUbHkP4ffU0Wks=
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
- export PATH="$HOME/.yarn/bin:$PATH"
before_script:
+2 -116
View File
@@ -1,117 +1,3 @@
<a name="1.7.8"></a>
# 1.7.8 enthusiastic-oblation (2019-03-11)
## Bug Fixes
- **required:** correctly validate required on non-input element surrounded by ngIf
([a4c7bd](https://github.com/angular/angular.js/commit/a4c7bdccd76c39c30e33f6215da9a00cc8acde2c),
[#16830](https://github.com/angular/angular.js/issues/16830),
[#16836](https://github.com/angular/angular.js/issues/16836))
<a name="1.7.7"></a>
# 1.7.7 kingly-exiting (2019-02-04)
## Bug Fixes
- **ngRequired:** set error correctly when inside ngRepeat and false by default
([5ad4f5](https://github.com/angular/angular.js/commit/5ad4f5562c37b1cb575e3e5fddd96e9dd10408e2),
[#16814](https://github.com/angular/angular.js/issues/16814),
[#16820](https://github.com/angular/angular.js/issues/16820))
<a name="1.7.6"></a>
# 1.7.6 gravity-manipulation (2019-01-17)
## Bug Fixes
- **$compile:** fix ng-prop-* with undefined values
([772440](https://github.com/angular/angular.js/commit/772440cdaf9a9bfa40de1675e20a5f0e356089ed),
[#16797](https://github.com/angular/angular.js/issues/16797),
[#16798](https://github.com/angular/angular.js/issues/16798))
- **compile:** properly handle false value for boolean attrs with jQuery
([27486b](https://github.com/angular/angular.js/commit/27486bd15e70946ece2ba713e4e8654b7f9bddad),
[#16778](https://github.com/angular/angular.js/issues/16778),
[#16779](https://github.com/angular/angular.js/issues/16779))
- **ngRepeat:**
- fix reference to last collection value remaining across linkages
([cf919a](https://github.com/angular/angular.js/commit/cf919a6fb7fc655f3fa37a74899a797ea5b8073e))
- fix trackBy function being invoked with incorrect scope
([d4d103](https://github.com/angular/angular.js/commit/d4d1031bcd9b30ae6a58bd60a79bcc9d20f0f2b7),
[#16776](https://github.com/angular/angular.js/issues/16776),
[#16777](https://github.com/angular/angular.js/issues/16777))
- **aria/ngClick:** check if element is `contenteditable` before blocking spacebar
([289374](https://github.com/angular/angular.js/commit/289374a43c1b2fd715ddf7455db225b17afebbaf),
[#16762](https://github.com/angular/angular.js/issues/16762))
- **input:** prevent browsers from autofilling hidden inputs
([7cbb10](https://github.com/angular/angular.js/commit/7cbb1044fcb3576cdad791bd22ebea3dfd533ff8))
- **Angular:** add workaround for Safari / Webdriver problem
([eb49f6](https://github.com/angular/angular.js/commit/eb49f6b7555cfd7ab03fd35581adb6b4bd49044e))
- **$browser:** normalize inputted URLs
([2f72a6](https://github.com/angular/angular.js/commit/2f72a69ded53a122afad3ec28d91f9bd2f41eb4f),
[#16606](https://github.com/angular/angular.js/issues/16606))
- **interpolate:** do not create directives for constant media URL attributes
([90a41d](https://github.com/angular/angular.js/commit/90a41d415c83abdbf28317f49df0fd0a7e07db86),
[#16734](https://github.com/angular/angular.js/issues/16734))
- **$q:** allow third-party promise libraries
([eefaa7](https://github.com/angular/angular.js/commit/eefaa76a90dbef08fdc7d734a205cc2de50d9f91),
[#16164](https://github.com/angular/angular.js/issues/16164),
[#16471](https://github.com/angular/angular.js/issues/16471))
- **urlUtils:** make IPv6 URL's hostname wrapped in square brackets in IE/Edge
([0e1bd7](https://github.com/angular/angular.js/commit/0e1bd7822e61822a48b8fd7ba5913a8702e6dabf),
[#16692](https://github.com/angular/angular.js/issues/16692),
[#16715](https://github.com/angular/angular.js/issues/16715))
- **ngAnimateSwap:** make it compatible with `ngIf` on the same element
([b27080](https://github.com/angular/angular.js/commit/b27080d52546409fb4e483f212f03616e2ca8037),
[#16616](https://github.com/angular/angular.js/issues/16616),
[#16729](https://github.com/angular/angular.js/issues/16729))
- **ngMock:** make matchLatestDefinitionEnabled work
([3cdffc](https://github.com/angular/angular.js/commit/3cdffcecbae71189b4db69b57fadda6608a23b61),
[#16702](https://github.com/angular/angular.js/issues/16702))
- **ngStyle:** skip setting empty value when new style has the property
([d6098e](https://github.com/angular/angular.js/commit/d6098eeb1c9510d599e9bd3cfdba7dd21e7a55a5),
[#16709](https://github.com/angular/angular.js/issues/16709))
## Performance Improvements
- **input:** prevent multiple validations on initialization
([692622](https://github.com/angular/angular.js/commit/69262239632027b373258e75c670b89132ad9edb),
[#14691](https://github.com/angular/angular.js/issues/14691),
[#16760](https://github.com/angular/angular.js/issues/16760))
<a name="1.7.5"></a>
# 1.7.5 anti-prettification (2018-10-04)
## Bug Fixes
- **ngClass:** do not break on invalid values
([f3a565](https://github.com/angular/angular.js/commit/f3a565872d802c94bb213944791b11b483d52f73),
[#16697](https://github.com/angular/angular.js/issues/16697),
[#16699](https://github.com/angular/angular.js/issues/16699))
<a name="1.7.4"></a>
# 1.7.4 interstellar-exploration (2018-09-07)
## Bug Fixes
- **ngAria.ngClick:** prevent default event on space/enter only for non-interactive elements
([61b335](https://github.com/angular/angular.js/commit/61b33543ff8e7f32464dec98a46bf0a35e9b03a4),
[#16664](https://github.com/angular/angular.js/issues/16664),
[#16680](https://github.com/angular/angular.js/issues/16680))
- **ngAnimate:** remove the "prepare" classes with multiple structural animations
([3105b2](https://github.com/angular/angular.js/commit/3105b2c26a71594c4e7904efc18f4b2e9da25b1b),
[#16681](https://github.com/angular/angular.js/issues/16681),
[#16677](https://github.com/angular/angular.js/issues/16677))
- **$route:** correctly extract path params if the path contains a question mark or a hash
([2ceeb7](https://github.com/angular/angular.js/commit/2ceeb739f35e01fcebcabac4beeeb7684ae9f86d))
- **ngHref:** allow numbers and other objects in interpolation
([30084c](https://github.com/angular/angular.js/commit/30084c13699c814ff6703d7aa2d3947a9b2f7067),
[#16652](https://github.com/angular/angular.js/issues/16652),
[#16626](https://github.com/angular/angular.js/issues/16626))
- **select:** allow to select first option with value `undefined`
([668a33](https://github.com/angular/angular.js/commit/668a33da3439f17e61dfa8f6d9b114ebde8c9d87),
[#16653](https://github.com/angular/angular.js/issues/16653),
[#16656](https://github.com/angular/angular.js/issues/16656))
<a name="1.7.3"></a>
# 1.7.3 eventful-proposal (2018-08-03)
@@ -826,7 +712,7 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
link: function(scope, element, attrs, ctrl) {
var maxValidator = ctrl.$validators.max;
ctrl.$validators.max = function(modelValue, viewValue) {
ctrk.$validators.max = function(modelValue, viewValue) {
return maxValidator(modelValue, modelValue);
};
}
@@ -1659,7 +1545,7 @@ If you rely on the $modelValue validation, you can overwrite the `min`/`max` val
link: function(scope, element, attrs, ctrl) {
var maxValidator = ctrl.$validators.max;
ctrl.$validators.max = function(modelValue, viewValue) {
ctrk.$validators.max = function(modelValue, viewValue) {
return maxValidator(modelValue, modelValue);
};
}
+3 -56
View File
@@ -4,7 +4,6 @@ var serveFavicon = require('serve-favicon');
var serveStatic = require('serve-static');
var serveIndex = require('serve-index');
var files = require('./angularFiles').files;
var mergeFilesFor = require('./angularFiles').mergeFilesFor;
var util = require('./lib/grunt/utils.js');
var versionInfo = require('./lib/versions/version-info');
var path = require('path');
@@ -31,7 +30,7 @@ if (!semver.satisfies(currentYarnVersion, expectedYarnVersion)) {
}
// Grunt CLI version checks
var expectedGruntVersion = pkg.engines['grunt-cli'];
var expectedGruntVersion = pkg.engines.grunt;
var currentGruntVersions = exec('grunt --version', {silent: true}).stdout;
var match = /^grunt-cli v(.+)$/m.exec(currentGruntVersions);
if (!match) {
@@ -142,9 +141,7 @@ module.exports = function(grunt) {
'jquery-2.2': 'karma-jquery-2.2.conf.js',
'jquery-2.1': 'karma-jquery-2.1.conf.js',
docs: 'karma-docs.conf.js',
modules: 'karma-modules.conf.js',
'modules-ngAnimate': 'karma-modules-ngAnimate.conf.js',
'modules-ngMock': 'karma-modules-ngMock.conf.js'
modules: 'karma-modules.conf.js'
},
@@ -214,12 +211,6 @@ module.exports = function(grunt) {
dest: 'build/angular-touch.js',
src: util.wrap(files['angularModules']['ngTouch'], 'module')
},
touchModuleTestBundle: {
dest: 'build/test-bundles/angular-touch.js',
prefix: 'src/module.prefix',
src: mergeFilesFor('karmaModules-ngTouch'),
suffix: 'src/module.suffix'
},
mocks: {
dest: 'build/angular-mocks.js',
src: util.wrap(files['angularModules']['ngMock'], 'module'),
@@ -229,42 +220,18 @@ module.exports = function(grunt) {
dest: 'build/angular-sanitize.js',
src: util.wrap(files['angularModules']['ngSanitize'], 'module')
},
sanitizeModuleTestBundle: {
dest: 'build/test-bundles/angular-sanitize.js',
prefix: 'src/module.prefix',
src: mergeFilesFor('karmaModules-ngSanitize'),
suffix: 'src/module.suffix'
},
resource: {
dest: 'build/angular-resource.js',
src: util.wrap(files['angularModules']['ngResource'], 'module')
},
resourceModuleTestBundle: {
dest: 'build/test-bundles/angular-resource.js',
prefix: 'src/module.prefix',
src: mergeFilesFor('karmaModules-ngResource'),
suffix: 'src/module.suffix'
},
messageformat: {
dest: 'build/angular-message-format.js',
src: util.wrap(files['angularModules']['ngMessageFormat'], 'module')
},
messageformatModuleTestBundle: {
dest: 'build/test-bundles/angular-message-format.js',
prefix: 'src/module.prefix',
src: mergeFilesFor('karmaModules-ngMessageFormat'),
suffix: 'src/module.suffix'
},
messages: {
dest: 'build/angular-messages.js',
src: util.wrap(files['angularModules']['ngMessages'], 'module')
},
messagesModuleTestBundle: {
dest: 'build/test-bundles/angular-messages.js',
prefix: 'src/module.prefix',
src: mergeFilesFor('karmaModules-ngMessages'),
suffix: 'src/module.suffix'
},
animate: {
dest: 'build/angular-animate.js',
src: util.wrap(files['angularModules']['ngAnimate'], 'module')
@@ -273,32 +240,14 @@ module.exports = function(grunt) {
dest: 'build/angular-route.js',
src: util.wrap(files['angularModules']['ngRoute'], 'module')
},
routeModuleTestBundle: {
dest: 'build/test-bundles/angular-route.js',
prefix: 'src/module.prefix',
src: mergeFilesFor('karmaModules-ngRoute'),
suffix: 'src/module.suffix'
},
cookies: {
dest: 'build/angular-cookies.js',
src: util.wrap(files['angularModules']['ngCookies'], 'module')
},
cookiesModuleTestBundle: {
dest: 'build/test-bundles/angular-cookies.js',
prefix: 'src/module.prefix',
src: mergeFilesFor('karmaModules-ngCookies'),
suffix: 'src/module.suffix'
},
aria: {
dest: 'build/angular-aria.js',
src: util.wrap(files['angularModules']['ngAria'], 'module')
},
ariaModuleTestBundle: {
dest: 'build/test-bundles/angular-aria.js',
prefix: 'src/module.prefix',
src: mergeFilesFor('karmaModules-ngAria'),
suffix: 'src/module.suffix'
},
parseext: {
dest: 'build/angular-parse-ext.js',
src: util.wrap(files['angularModules']['ngParseExt'], 'module')
@@ -481,9 +430,7 @@ module.exports = function(grunt) {
grunt.registerTask('test:jquery-2.1', 'Run the jQuery 2.1 unit tests with Karma', ['tests:jquery-2.1']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', [
'build',
'tests:modules',
'tests:modules-ngAnimate',
'tests:modules-ngMock'
'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', [
+2 -2
View File
@@ -14,9 +14,9 @@ piece of cake. Best of all? It makes development fun!
--------------------
**On July 1, 2018 AngularJS entered a 3 year Long Term Support period:** [Find out more](https://docs.angularjs.org/misc/version-support-status)
##### AngularJS will be moving to Long Term Support (LTS) mode on July 1st 2018: [Find out more](https://docs.angularjs.org/misc/version-support-status)
**Looking for the new Angular? Go here:** https://github.com/angular/angular
##### Looking for the new Angular? Go here: https://github.com/angular/angular
--------------------
-98
View File
@@ -1,98 +0,0 @@
# AngularJS Release instructions
## Compare the list of commits between stable and unstable
There is a script - compare-master-to-stable.js - that helps with this.
We just want to make sure that good commits (low risk fixes + docs fixes) got cherry-picked into stable branch and nothing interesting got merged only into stable branch.
## Pick a release name (for this version)
A super-heroic power (adverb-verb phrase).
## Generate release notes
Example Commit: https://github.com/angular/angular.js/commit/7ab5098c14ee4f195dbfe2681e402fe2dfeacd78
1) Run
```bash
node_modules/.bin/changez -o changes.md -v <new version> <base branch>
```
2) Review the generated file and manually fix typos, group and reorder stuff if needed.
3) Move the content into CHANGELOG.md add release code-names to headers.
4) Push the changes to your private github repo and review.
5) cherry-pick the release notes commit to the appropriate branches.
## Pick a commit to release (for this version)
Usually this will be the commit containing the release notes, but it may also be in the past.
## Run "release" script
```bash
scripts/jenkins/release.sh --git-push-dryrun=false --commit-sha=8822a4f --version-number=1.7.6 --version-name=gravity-manipulation
```
1) The SHA is of the commit to release (could be in the past).
2) The version number and code-name that should be released, not the next version number (e.g. to release 1.2.12 you enter 1.2.12 as release version and the code-name that was picked for 1.2.12, cauliflower-eradication).
3) You will need to have write access to all the AngularJS github dist repositories and publish rights for the AngularJS packages on npm.
## Update GitHub milestones
1) Create the next milestone if it doesn't exist yet-giving ita due date.
2) Move all open issues and PRs for the current milestone to the next milestone<br>
You can do this by filtering the current milestone, selecting via checklist, and moving to the next milestone within the GH issues page.
3) Close the current milestone click the milestones tab and close from there.
4) Create a new holding milestone for the release after next-but don't give it a due date otherwise that will mess up the dashboard.
## Push build artifacts to CDN
Google CDNs are fed with data from google3 every day at 11:15am PT it takes only few minutes for the import to propagate).
If we want to make our files available, we need submit our CLs before this time on the day of the release.
## Don't update the package.json (branchVersion) until the CDN has updated
This is the version used to compute what version to link to in the CDN. If you update this too early then the CDN lookup fails and you end up with 'null, for the version, which breaks the docs.
## Verify angularjs.org download modal has latest version (updates via Travis job)
The versions in the modal are updated (based on the versions available on CDN) as part of the Travis deploy stage: https://github.com/angular/angularjs.org/blob/a4d25c5abcd39e8ce19d31cb1c78073d13c4c974/.travis.yml#L26
(You may need to explicitly trigger the Travis job. e.g. re-running the last job.)
## Announce the release (via official Google accounts)
Double check that angularjs.org is up to date with the new release version before sharing.
1) Collect a list of contributors
use: `git log --format='%aN' v1.2.12..v1.2.13 | sort -u`
2) Write a blog post (for minor releases, not patch releases) and publish it with the "release" tag
3) Post on twitter as yourself (tweet from your heart; there is no template for this), retweet as @AngularJS
## Party!
## Major Release Tasks
1) Update angularjs.org to use the latest branch.
2) Write up a migration document.
3) Create a new git branch for the version that has been released (e.g. 1.8.x).
4) Check that the build and release scripts still work.
5) Update the dist-tag of the old branch, see https://github.com/angular/angular.js/pull/12722.
6) Write a blog post.
+13 -74
View File
@@ -189,72 +189,21 @@ var angularFiles = {
'src/angular.bind.js'
],
'karmaModules-ngAnimate': [
'karmaModules': [
'build/angular.js',
'build/angular-mocks.js',
'@angularSrcModules',
'test/modules/no_bootstrap.js',
'test/helpers/matchers.js',
'test/helpers/privateMocks.js',
'test/helpers/support.js',
'test/helpers/testabilityPatch.js',
'@angularSrcModuleNgAnimate',
'test/ngAnimate/**/*.js'
],
'karmaModules-ngAria': [
'@angularSrcModuleNgAria',
'test/ngAria/**/*.js'
],
'karmaModules-ngCookies': [
'@angularSrcModuleNgCookies',
'test/ngCookies/**/*.js'
],
'karmaModules-ngMessageFormat': [
'@angularSrcModuleNgMessageFormat',
'test/ngMessageFormat/**/*.js'
],
'karmaModules-ngMessages': [
'build/angular-animate.js',
'@angularSrcModuleNgMessages',
'test/ngMessages/**/*.js'
],
// ngMock doesn't include the base because it must use the ngMock src files
'karmaModules-ngMock': [
'build/angular.js',
'src/ngMock/*.js',
'test/modules/no_bootstrap.js',
'test/helpers/matchers.js',
'test/helpers/privateMocks.js',
'test/helpers/support.js',
'test/helpers/testabilityPatch.js',
'src/routeToRegExp.js',
'build/angular-animate.js',
'test/ngMock/**/*.js'
],
'karmaModules-ngResource': [
'@angularSrcModuleNgResource',
'test/ngResource/**/*.js'
],
'karmaModules-ngRoute': [
'build/angular-animate.js',
'@angularSrcModuleNgRoute',
'test/ngRoute/**/*.js'
],
'karmaModules-ngSanitize': [
'@angularSrcModuleNgSanitize',
'test/ngSanitize/**/*.js'
],
'karmaModules-ngTouch': [
'@angularSrcModuleNgTouch',
'test/ngTouch/**/*.js'
'test/helpers/*.js',
'test/ngAnimate/*.js',
'test/ngMessageFormat/*.js',
'test/ngMessages/*.js',
'test/ngMock/*.js',
'test/ngCookies/*.js',
'test/ngRoute/**/*.js',
'test/ngResource/*.js',
'test/ngSanitize/**/*.js',
'test/ngTouch/**/*.js',
'test/ngAria/*.js'
],
'karmaJquery': [
@@ -283,16 +232,6 @@ var angularFiles = {
});
});
angularFiles['angularSrcModuleNgAnimate'] = angularFiles['angularModules']['ngAnimate'];
angularFiles['angularSrcModuleNgAria'] = angularFiles['angularModules']['ngAria'];
angularFiles['angularSrcModuleNgCookies'] = angularFiles['angularModules']['ngCookies'];
angularFiles['angularSrcModuleNgMessageFormat'] = angularFiles['angularModules']['ngMessageFormat'];
angularFiles['angularSrcModuleNgMessages'] = angularFiles['angularModules']['ngMessages'];
angularFiles['angularSrcModuleNgResource'] = angularFiles['angularModules']['ngResource'];
angularFiles['angularSrcModuleNgRoute'] = angularFiles['angularModules']['ngRoute'];
angularFiles['angularSrcModuleNgSanitize'] = angularFiles['angularModules']['ngSanitize'];
angularFiles['angularSrcModuleNgTouch'] = angularFiles['angularModules']['ngTouch'];
angularFiles['angularSrcModules'] = [].concat(
angularFiles['angularModules']['ngAnimate'],
angularFiles['angularModules']['ngMessageFormat'],
@@ -15,7 +15,7 @@ var cdnUrl = googleCdnUrl + versionInfo.cdnVersion;
// docs.angularjs.org and code.angularjs.org need them.
var versionPath = versionInfo.currentVersion.isSnapshot ?
'snapshot' :
versionInfo.currentVersion.version;
(versionInfo.currentVersion.version || versionInfo.currentVersion.version);
var examplesDependencyPath = angularCodeUrl + versionPath + '/';
module.exports = function productionDeployment(getVersion) {
@@ -17,7 +17,7 @@
{% endif %}
{% if method.this %}
<h4>Method's `this`</h4>
<h4>Method's {% code %}this{% endcode %}</h4>
{$ method.this | marked $}
{% endif %}
@@ -1,4 +1,4 @@
{% if doc.this %}
<h3>Method's `this`</h3>
<h3>Method's {% code %}this{% endcode %}</h3>
{$ doc.this | marked $}
{% endif %}
{% endif %}
+1 -1
View File
@@ -5,7 +5,7 @@
# AngularJS API Docs
<div class="alert alert-warning">
**On July 1, 2018 AngularJS entered a 3 year Long Term Support period:** [Find out more](misc/version-support-status).
**AngularJS will be moving to Long Term Support (LTS) mode on July 1st 2018.**: [Find out more](misc/version-support-status).
</div>
## Welcome to the AngularJS API docs page.
+3 -3
View File
@@ -186,7 +186,7 @@ Right now, the `InvoiceController` contains all logic of our example. When the a
is a good practice to move view-independent logic from the controller into a
<a name="service">{@link services service}</a>, so it can be reused by other parts
of the application as well. Later on, we could also change that service to load the exchange rates
from the web, e.g. by calling the [exchangeratesapi.io](https://exchangeratesapi.io) exchange rate API, without changing the controller.
from the web, e.g. by calling the [Fixer.io](http://fixer.io) exchange rate API, without changing the controller.
Let's refactor our example and move the currency conversion into a service in another file:
@@ -300,7 +300,7 @@ to something shorter like `a`.
## Accessing the backend
Let's finish our example by fetching the exchange rates from the [exchangeratesapi.io](https://exchangeratesapi.io) exchange rate API.
Let's finish our example by fetching the exchange rates from the [Fixer.io](http://fixer.io) exchange rate API.
The following example shows how this is done with AngularJS:
<example name="guide-concepts-3" ng-app-included="true">
@@ -331,7 +331,7 @@ The following example shows how this is done with AngularJS:
};
var refresh = function() {
var url = 'https://api.exchangeratesapi.io/latest?base=USD&symbols=' + currencies.join(",");
var url = 'https://api.fixer.io/latest?base=USD&symbols=' + currencies.join(",");
return $http.get(url).then(function(response) {
usdToForeignRates = response.data.rates;
usdToForeignRates['USD'] = 1;
+3 -8
View File
@@ -279,20 +279,15 @@ construction and lookup of dependencies.
Here is an example of using the injector service:
First create an AngularJS module that will hold the service definition. (The empty array passed as
the second parameter means that this module does not depend on any other modules.)
```js
// Create a module to hold the service definition
// Provide the wiring information in a module
var myModule = angular.module('myModule', []);
```
Teach the injector how to build a `greeter` service, which is just an object that contains a `greet`
method. Notice that `greeter` is dependent on the `$window` service, which will be provided
(injected into `greeter`) by the injector.
Teach the injector how to build a `greeter` service. Notice that `greeter` is dependent on the
`$window` service. The `greeter` service is an object that contains a `greet` method.
```js
// Define the `greeter` service
myModule.factory('greeter', function($window) {
return {
greet: function(text) {
+1 -1
View File
@@ -43,7 +43,7 @@ In AngularJS applications, you move the job of filling page templates with data
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
* **Internationalization and Localization:** {@link guide/i18n AngularJS Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](https://blog.novanet.no/creating-multilingual-support-using-angularjs/)
* **Internationalization and Localization:** {@link guide/i18n AngularJS Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
* **Touch events:** {@link ngTouch Touch events}
* **Accessibility:** {@link guide/accessibility ngAria}
+1 -1
View File
@@ -148,7 +148,7 @@ custom directive, as seen in the following example directive definition object:
link: function(scope, element, attrs, ctrl) {
var maxValidator = ctrl.$validators.max;
ctrl.$validators.max = function(modelValue, viewValue) {
ctrk.$validators.max = function(modelValue, viewValue) {
return maxValidator(modelValue, modelValue);
};
}
+1 -1
View File
@@ -102,7 +102,7 @@ For more information please visit {@link $http#json-vulnerability-protection JSO
Bear in mind that calling `$http.jsonp` gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
instant remote code execution in your application: the result of these requests is handed off
to the browser as a regular `<script>` tag.
to the browser as regular `<script>` tag.
## Strict Contextual Escaping
+21 -6
View File
@@ -7,9 +7,26 @@
This page describes the support status of the significant versions of AngularJS.
<div class="alert alert-info">
On July 1, 2018 AngularJS entered a 3 year Long Term Support period.
AngularJS is planning one more significant release, version 1.7, and on July 1, 2018 it will enter a 3 year Long Term Support period.
</div>
### Until July 1st 2018
Any version branch not shown in the following table (e.g. 1.5.x) is no longer being developed.
<table class="dev-status table table-bordered">
<thead>
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
</thead>
<tbody>
<tr class="security"><td><span>1.2.x</span></td><td>Security patches only</td><td>Last version to provide IE 8 support</td></tr>
<tr class="stable"><td><span>1.6.x</span></td><td>Patch Releases</td><td>Minor features, bug fixes, security patches - no breaking changes</td></tr>
<tr class="current"><td><span>1.7.x</span></td><td>Active Development</td><td>1.7.0 (not yet released) will be the last release of AngularJS to contain breaking changes</td></tr>
</tbody>
</table>
### After July 1st 2018
Any version branch not shown in the following table (e.g. 1.6.x) is no longer being developed.
<table class="dev-status table table-bordered">
@@ -19,7 +36,7 @@ Any version branch not shown in the following table (e.g. 1.6.x) is no longer be
<tbody>
<tr class="security">
<td><span>1.2.x</span></td>
<td>Security patches only</td>
<td>Long Term Support</td>
<td>Last version to provide IE 8 support</td>
</tr>
<tr class="stable">
@@ -32,16 +49,14 @@ Any version branch not shown in the following table (e.g. 1.6.x) is no longer be
### Long Term Support
On July 1st 2018, AngularJS entered a Long Term Support period for AngularJS.
On July 1st 2018, we will enter a Long Term Support period for AngularJS.
We now focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
At this time we will focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
* A security flaw is detected in the 1.7.x branch of the framework
* One of the major browsers releases a version that will cause current production applications using AngularJS 1.7.x to stop working
* The jQuery library releases a version that will cause current production applications using AngularJS 1.7.x to stop working.
AngularJS 1.2.x will get a new version if and only if a new severe security issue is discovered.
### Blog Post
You can read more about these plans in our [blog post announcement](https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c).
+23 -67
View File
@@ -64,7 +64,7 @@ a few git commands.
### Install Git
You can download and install Git from https://git-scm.com/download. Once installed, you should have
You can download and install Git from http://git-scm.com/download. Once installed, you should have
access to the `git` command line tool. The main commands that you will need to use are:
* `git clone ...`: Clone a remote repository onto your local machine.
@@ -99,8 +99,8 @@ The tutorial instructions, from now on, assume you are running all commands from
### Install Node.js
In order to install dependencies (such as the test tools and AngularJS itself) and run the
preconfigured local web server, you will also need [Node.js v6+][node].
If you want to run the preconfigured local web server and the test tools then you will also need
[Node.js v4+][node].
You can download a Node.js installer for your operating system from https://nodejs.org/en/download/.
@@ -125,25 +125,22 @@ npm --version
[Node Version Manager (nvm)][nvm] or [Node Version Manager (nvm) for Windows][nvm-windows].
</div>
By installing Node.js, you also get [npm][npm], which is a command line executable for downloading
and managing Node.js packages. We use it to download the AngularJS framework as well as development
and testing tools.
Once you have Node.js installed on your machine, you can download these dependencies by running:
Once you have Node.js installed on your machine, you can download the tool dependencies by running:
```
npm install
```
This command reads angular-phonecat's `package.json` file and downloads the following dependencies
into the `node_modules` directory:
This command reads angular-phonecat's `package.json` file and downloads the following tools into the
`node_modules` directory:
* [Bower][bower] - client-side code package manager
* [Http-Server][http-server] - simple local static web server
* [Karma][karma] - unit test runner
* [Protractor][protractor] - end-to-end (E2E) test runner
Running `npm install` will also automatically copy the AngularJS framework and other dependencies
necessary for our app to work into the `app/lib/` directory.
Running `npm install` will also automatically use bower to download the AngularJS framework into the
`app/bower_components` directory.
<div class="alert alert-info">
Note the angular-phonecat project is setup to install and run these utilities via npm scripts.
@@ -163,23 +160,23 @@ tasks that you will need while developing:
### Install Helper Tools (optional)
The Http-Server, Karma and Protractor modules are also executables, which can be installed globally
and run directly from a terminal/command prompt. You don't need to do this to follow the tutorial,
but if you decide you do want to run them directly, you can install these modules globally using,
`sudo npm install --global ...`.
The Bower, Http-Server, Karma and Protractor modules are also executables, which can be installed
globally and run directly from a terminal/command prompt. You don't need to do this to follow the
tutorial, but if you decide you do want to run them directly, you can install these modules globally
using, `sudo npm install -g ...`.
For instance, to install the `http-server` command line executable you would do:
For instance, to install the Bower command line executable you would do:
```
sudo npm install --global http-server
sudo npm install -g bower
```
_(Omit the sudo if running on Windows.)_
_(Omit the sudo if running on Windows)_
Then you can run the `http-server` tool directly, such as:
Then you can run the bower tool directly, such as:
```
http-server ./app
bower install
```
@@ -281,45 +278,6 @@ It is good to run the E2E tests whenever you make changes to the HTML views or w
the application as a whole is executing correctly. It is very common to run E2E tests before pushing
a new commit of changes to a remote repository.
<div class="alert alert-warning">
<p>
Each version of Protractor is compatible with specific browser versions. If you are reading this
some time in the future, it is possible that the specified Protractor version is no longer
compatible with the latest version of Chrome that you are using.
</p>
<p>
If that is the case, you can try upgrading Protractor to newer version. For instructions on how
to upgrade dependencies see [Updating dependencies](tutorial/#updating-dependencies).
</p>
</div>
### Updating dependencies
In order to avoid surprises, all dependencies listed in `package.json` are pinned to specific
versions (this is what the [package-lock.json][package-lock] file is about). This ensures that the
same version of a dependency is installed every time.
Since all dependencies are acquired via npm, you can use the same tool to easily update them as
well (although you probably don't need to for the purpose of this tutorial). Simply run the
preconfigured script:
```
npm run update-deps
```
This will update all packages to the latest version that satisfy their version ranges (as specified
in `package.json`) and also copy the necessary files into `app/lib/`. For example, if `package.json`
contains `"some-package": "1.2.x"`, it will be updated to the latest 1.2.x version (e.g. 1.2.99),
but not to 1.3.x (e.g. 1.3.0).
If you want to update a dependency to a version newer than what the specificed range would permit,
you can change the version range in `package.json` and then run `npm run update-deps` as usual.
<div class="alert alert-info">
See [here][semver-ranges] for more info on the various version range formats.
</div>
### Common Issues
@@ -366,16 +324,14 @@ Now that you have set up your local machine, let's get started with the tutorial
[angular-phonecat]: https://github.com/angular/angular-phonecat
[git]: https://git-scm.com/
[bower]: http://bower.io/
[git]: http://git-scm.com/
[http-server]: https://github.com/nodeapps/http-server
[jdk]: https://en.wikipedia.org/wiki/Java_Development_Kit
[jdk-download]: https://www.oracle.com/technetwork/java/javase/downloads/index.html
[jdk-download]: http://www.oracle.com/technetwork/java/javase/downloads/index.html
[karma]: https://karma-runner.github.io/
[node]: https://nodejs.org/
[npm]: https://www.npmjs.com/
[node]: http://nodejs.org/
[nvm]: https://github.com/creationix/nvm
[nvm-windows]: https://github.com/coreybutler/nvm-windows
[package-lock]: https://docs.npmjs.com/files/package-lock.json
[protractor]: https://github.com/angular/protractor
[selenium]: https://docs.seleniumhq.org/
[semver-ranges]: https://docs.npmjs.com/misc/semver#ranges
[selenium]: http://docs.seleniumhq.org/
+6 -7
View File
@@ -51,8 +51,8 @@ The code contains some key AngularJS elements that we will need as we progress.
<head>
<meta charset="utf-8">
<title>My HTML File</title>
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css" />
<script src="lib/angular/angular.js"></script>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<script src="bower_components/angular/angular.js"></script>
</head>
<body>
@@ -84,7 +84,7 @@ For more info on `ngApp`, check out the {@link ngApp API Reference}.
**`angular.js` script tag:**
```html
<script src="lib/angular/angular.js"></script>
<script src="bower_components/angular/angular.js"></script>
```
This code downloads the `angular.js` script which registers a callback that will be executed by the
@@ -154,8 +154,8 @@ and one static binding, and our model is empty. That will soon change!
Most of the files in your working directory come from the [angular-seed project][angular-seed],
which is typically used to bootstrap new AngularJS projects. The seed project is pre-configured to
install the AngularJS framework (via `npm` into the `app/lib/` directory) and tools for developing
and testing a typical web application (via `npm`).
install the AngularJS framework (via `bower` into the `app/bower_components/` directory) and tools
for developing and testing a typical web application (via `npm`).
For the purposes of this tutorial, we modified the angular-seed with the following changes:
@@ -163,7 +163,7 @@ For the purposes of this tutorial, we modified the angular-seed with the followi
* Removed unused dependencies.
* Added phone images to `app/img/phones/`.
* Added phone data files (JSON) to `app/phones/`.
* Added a dependency on [Bootstrap][bootstrap-3.3] in the `package.json` file.
* Added a dependency on [Bootstrap](http://getbootstrap.com) in the `bower.json` file.
## Experiments
@@ -186,4 +186,3 @@ Now let's go to {@link step_01 step 1} and add some content to the web app.
[angular-seed]: https://github.com/angular/angular-seed
[bootstrap-3.3]: https://getbootstrap.com/docs/3.3
+4 -4
View File
@@ -33,7 +33,7 @@ The view is constructed by AngularJS from this template.
<html ng-app="phonecatApp">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="PhoneListController">
@@ -317,7 +317,7 @@ by utilizing components.
<ul doc-tutorial-nav="2"></ul>
[jasmine-docs]: https://jasmine.github.io/api/3.3/global
[jasmine-home]: https://jasmine.github.io/
[jasmine-docs]: http://jasmine.github.io/2.4/introduction.html
[jasmine-home]: http://jasmine.github.io/
[karma]: https://karma-runner.github.io/
[mvc-pattern]: https://en.wikipedia.org/wiki/ModelViewController
[mvc-pattern]: http://en.wikipedia.org/wiki/ModelViewController
+6 -13
View File
@@ -88,13 +88,6 @@ Let's see an example:
});
```
```html
<body>
<!-- The following line is how to use the `greetUser` component above in your html doc. -->
<greet-user></greet-user>
</body>
```
Now, every time we include `<greet-user></greet-user>` in our view, AngularJS will expand it into a
DOM sub-tree constructed using the provided `template` and managed by an instance of the specified
controller.
@@ -127,14 +120,14 @@ acquired skill.
<html ng-app="phonecatApp">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="app.js"></script>
<script src="phone-list.component.js"></script>
</head>
<body>
<!-- Use a custom component to render a list of phones -->
<phone-list></phone-list> <!-- This tells AngularJS to instantiate a `phoneList` component here. -->
<phone-list></phone-list>
</body>
</html>
@@ -155,7 +148,7 @@ angular.module('phonecatApp', []);
// Register `phoneList` component, along with its associated controller and template
angular.
module('phonecatApp').
component('phoneList', { // This name is what AngularJS uses to match to the `<phone-list>` element.
component('phoneList', {
template:
'<ul>' +
'<li ng-repeat="phone in $ctrl.phones">' +
@@ -284,7 +277,7 @@ files, so it remains easy to locate as our application grows.
[case-styles]: https://en.wikipedia.org/wiki/Letter_case#Special_case_styles
[jasmine-docs]: https://jasmine.github.io/api/3.3/global
[jasmine-home]: https://jasmine.github.io/
[jasmine-docs]: http://jasmine.github.io/2.4/introduction.html
[jasmine-home]: http://jasmine.github.io/
[karma]: https://karma-runner.github.io/
[mvc-pattern]: https://en.wikipedia.org/wiki/ModelViewController
[mvc-pattern]: http://en.wikipedia.org/wiki/ModelViewController
-2
View File
@@ -230,8 +230,6 @@ You can now rerun `npm run protractor` to see the tests run.
* Reverse the sort order by adding a `-` symbol before the sorting value:
`<option value="-age">Oldest</option>`
After making this change, you'll notice that the drop-down list has a blank option selected and does not default to age anymore.
Fix this by updating the `orderProp` value in `phone-list.component.js` to match the new value on the `<option>` element.
## Summary
+2 -2
View File
@@ -8,8 +8,8 @@
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
from our server using one of AngularJS's built-in {@link guide/services services} called
{@link ng.$http $http}. We will use AngularJS's {@link guide/di dependency injection (DI)} to
provide the service to the `phoneList` component's controller.
{@link ng.$http $http}. We will use AngularJS's {@link guide/di dependency injection (DI)} to provide
the service to the `phoneList` component's controller.
* There is now a list of 20 phones, loaded from the server.
+5 -6
View File
@@ -47,10 +47,10 @@ URLs point to the `app/img/phones/` directory.
...
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp" class="thumbnail">
<a href="#!/phones/{{phone.id}}" class="thumb">
<a href="#/phones/{{phone.id}}" class="thumb">
<img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}" />
</a>
<a href="#!/phones/{{phone.id}}">{{phone.name}}</a>
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
@@ -83,7 +83,7 @@ HTTP request to an invalid location.
query.sendKeys('nexus');
element.all(by.css('.phones li a')).first().click();
expect(browser.getCurrentUrl()).toContain('index.html#!/phones/nexus-s');
expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s');
});
...
@@ -110,9 +110,8 @@ You can now rerun `npm run protractor` to see the tests run.
## Summary
Now that you have added phone images and links, go to {@link step_09 step 9} to learn about
AngularJS layout templates and how AngularJS makes it easy to create applications that have
multiple views.
Now that you have added phone images and links, go to {@link step_09 step 9} to learn about AngularJS
layout templates and how AngularJS makes it easy to create applications that have multiple views.
<ul doc-tutorial-nav="8"></ul>
+50 -39
View File
@@ -23,33 +23,49 @@ has multiple views by adding routing, using an AngularJS module called {@link ng
The routing functionality added in this step is provided by AngularJS in the `ngRoute` module, which
is distributed separately from the core AngularJS framework.
Since we are using [npm][npm] to install client-side dependencies, this step updates the
`package.json` configuration file to include the new dependency:
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
`bower.json` configuration file to include the new dependency:
<br />
**`package.json`:**
**`bower.json`:**
```json
{
"name": "angular-phonecat",
...
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.7.x",
"angular-route": "1.7.x",
"angular": "1.5.x",
"angular-mocks": "1.5.x",
"angular-route": "1.5.x",
"bootstrap": "3.3.x"
},
...
}
}
```
The new dependency `"angular-route": "1.7.x"` tells npm to install a version of the angular-route
module that is compatible with version 1.7.x of AngularJS. We must tell npm to download and install
The new dependency `"angular-route": "1.5.x"` tells bower to install a version of the angular-route
module that is compatible with version 1.5.x of AngularJS. We must tell bower to download and install
this dependency.
```
npm install
```
<div class="alert alert-info">
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
we have preconfigured `npm install` to run bower for us.
</div>
<div class="alert alert-warning">
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
you may have a problem with the `bower install` due to a conflict between the versions of
angular.js that need to be installed. If you run into this issue, simply delete your
`app/bower_components` directory and then run `npm install`.
</div>
## Multiple Views, Routing and Layout Templates
@@ -111,8 +127,8 @@ service, the `$routeProvider` exposes APIs that allow you to define routes for y
</div>
AngularJS modules solve the problem of removing global variables from the application and provide a
way of configuring the injector. As opposed to AMD or require.js modules, AngularJS modules don't
try to solve the problem of script load ordering or lazy script fetching. These goals are totally
way of configuring the injector. As opposed to AMD or require.js modules, AngularJS modules don't try
to solve the problem of script load ordering or lazy script fetching. These goals are totally
independent and both module systems can live side-by-side and fulfill their goals.
To deepen your understanding on AngularJS's DI, see [Understanding Dependency Injection][wiki-di].
@@ -130,8 +146,8 @@ into the layout template. This makes it a perfect fit for our `index.html` templ
```html
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="lib/angular-route/angular-route.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="app.module.js"></script>
<script src="app.config.js"></script>
...
@@ -187,8 +203,10 @@ code, we put it into a separate file and used the `.config` suffix.
```js
angular.
module('phonecatApp').
config(['$routeProvider',
function config($routeProvider) {
config(['$locationProvider', '$routeProvider',
function config($locationProvider, $routeProvider) {
$locationProvider.hashPrefix('!');
$routeProvider.
when('/phones', {
template: '<phone-list></phone-list>'
@@ -208,6 +226,18 @@ the corresponding services. Here, we use the
{@link ngRoute.$routeProvider#otherwise $routeProvider.otherwise()} methods to define our
application routes.
<div class="alert alert-success">
<p>
We also used {@link $locationProvider#hashPrefix $locationProvider.hashPrefix()} to set the
hash-prefix to `!`. This prefix will appear in the links to our client-side routes, right after
the hash (`#`) symbol and before the actual path (e.g. `index.html#!/some/path`).
</p>
<p>
Setting a prefix is not necessary, but it is considered a good practice (for reasons that are
outside the scope of this tutorial). `!` is the most commonly used prefix.
</p>
</div>
Our routes are defined as follows:
* `when('/phones')`: Determines the view that will be shown, when the URL hash fragment is
@@ -231,25 +261,6 @@ the route declaration — `'/phones/:phoneId'` — as a template that is matched
URL. All variables defined with the `:` prefix are extracted into the (injectable)
{@link ngRoute.$routeParams $routeParams} object.
<div class="alert alert-info">
<p>
You may have noticed, that &mdash; while the configured route paths start with `/` (e.g.
`/phones`) &mdash; the URLs used in templates start with `#!/` (e.g. `#!/phones`).
</p>
<p>
Without getting into much detail, AngularJS (by default) uses the hash part of the URL (i.e.
what comes after the hash (`#`) symbol) to determine the current route. In addition to that, you
can also specify a {@link $locationProvider#hashPrefix hash-prefix} (`!` by default) that needs
to appear after the hash symbol in order for AngularJS to consider the value an "AngularJS path"
and process it (for example, try to match it to a route).
</p>
<p>
You can find out more about how all this works in the [Using $location](guide/$location) section
of the Developer Guide. But all you need to know for now, is that the URLs to our various routes
should be prefixed with `#!`.
</p>
</div>
## The `phoneDetail` Component
@@ -334,8 +345,8 @@ any modification.
```js
files: [
'lib/angular/angular.js',
'lib/angular-route/angular-route.js',
'bower_components/angular/angular.js',
'bower_components/angular-route/angular-route.js',
...
],
```
@@ -352,7 +363,7 @@ various URLs and verifying that the correct view was rendered.
it('should redirect `index.html` to `index.html#!/phones', function() {
browser.get('index.html');
expect(browser.getCurrentUrl()).toContain('index.html#!/phones');
expect(browser.getLocationAbsUrl()).toBe('/phones');
});
...
@@ -413,6 +424,6 @@ With the routing set up and the phone list view implemented, we are ready to go
<ul doc-tutorial-nav="9"></ul>
[bower]: http://bower.io
[deep-linking]: https://en.wikipedia.org/wiki/Deep_linking
[npm]: https://www.npmjs.com/
[wiki-di]: https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
+35 -20
View File
@@ -21,34 +21,50 @@ In this step, we will change the way our application fetches data.
The RESTful functionality is provided by AngularJS in the {@link ngResource ngResource} module, which
is distributed separately from the core AngularJS framework.
Since we are using [npm][npm] to install client-side dependencies, this step updates the
`package.json` configuration file to include the new dependency:
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
`bower.json` configuration file to include the new dependency:
<br />
**`package.json`:**
**`bower.json`:**
```json
```
{
"name": "angular-phonecat",
...
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.7.x",
"angular-resource": "1.7.x",
"angular-route": "1.7.x",
"angular": "1.5.x",
"angular-mocks": "1.5.x",
"angular-resource": "1.5.x",
"angular-route": "1.5.x",
"bootstrap": "3.3.x"
},
...
}
}
```
The new dependency `"angular-resource": "1.7.x"` tells npm to install a version of the
angular-resource module that is compatible with version 1.7.x of AngularJS. We must tell npm to
The new dependency `"angular-resource": "1.5.x"` tells bower to install a version of the
angular-resource module that is compatible with version 1.5.x of AngularJS. We must tell bower to
download and install this dependency.
```
npm install
```
<div class="alert alert-info">
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
we have preconfigured `npm install` to run bower for us.
</div>
<div class="alert alert-warning">
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
you may have a problem with the `bower install` due to a conflict between the versions of
angular.js that need to be installed. If you run into this issue, simply delete your
`app/bower_components` directory and then run `npm install`.
</div>
## Service
@@ -113,7 +129,7 @@ need to load the `angular-resource.js` file, which contains the `ngResource` mod
```html
<head>
...
<script src="lib/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
...
<script src="core/phone/phone.module.js"></script>
<script src="core/phone/phone.service.js"></script>
@@ -125,10 +141,9 @@ need to load the `angular-resource.js` file, which contains the `ngResource` mod
## Component Controllers
We can now simplify our component controllers (`PhoneListController` and `PhoneDetailController`) by
factoring out the lower-level `$http` service, replacing it with the new `Phone` service.
AngularJS's `$resource` service is easier to use than `$http` for interacting with data sources
exposed as RESTful resources. It is also easier now to understand what the code in our controllers
is doing.
factoring out the lower-level `$http` service, replacing it with the new `Phone` service. AngularJS's
`$resource` service is easier to use than `$http` for interacting with data sources exposed as
RESTful resources. It is also easier now to understand what the code in our controllers is doing.
<br />
**`app/phone-list/phone-list.module.js`:**
@@ -225,8 +240,8 @@ Karma configuration file with angular-resource.
```js
files: [
'lib/angular/angular.js',
'lib/angular-resource/angular-resource.js',
'bower_components/angular/angular.js',
'bower_components/angular-resource/angular-resource.js',
...
],
```
@@ -304,6 +319,6 @@ Now that we have seen how to build a custom service as a RESTful client, we are
<ul doc-tutorial-nav="13"></ul>
[bower]: http://bower.io/
[jasmine-equality]: https://jasmine.github.io/2.4/custom_equality.html
[npm]: https://www.npmjs.com/
[restful]: https://en.wikipedia.org/wiki/Representational_State_Transfer
+43 -27
View File
@@ -22,43 +22,59 @@ the template code we created earlier.
## Dependencies
The animation functionality is provided by AngularJS in the `ngAnimate` module, which is distributed
separately from the core AngularJS framework. In addition we will use [jQuery][jquery] in this
project to do extra JavaScript animations.
separately from the core AngularJS framework. In addition we will use [jQuery][jquery] in this project
to do extra JavaScript animations.
Since we are using [npm][npm] to install client-side dependencies, this step updates the
`package.json` configuration file to include the new dependencies:
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
`bower.json` configuration file to include the new dependencies:
<br />
**`package.json`:**
**`bower.json`:**
```json
```
{
"name": "angular-phonecat",
...
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.7.x",
"angular-animate": "1.7.x",
"angular-resource": "1.7.x",
"angular-route": "1.7.x",
"angular": "1.5.x",
"angular-animate": "1.5.x",
"angular-mocks": "1.5.x",
"angular-resource": "1.5.x",
"angular-route": "1.5.x",
"bootstrap": "3.3.x",
"jquery": "3.3.x"
},
...
"jquery": "3.2.x"
}
}
```
* `"angular-animate": "1.7.x"` tells npm to install a version of the angular-animate module that
is compatible with version 1.7.x of AngularJS.
* `"jquery": "3.3.x"` tells npm to install the latest patch release of the 3.3 version of jQuery.
Note that this is not an AngularJS library; it is the standard jQuery library. We can use npm to
* `"angular-animate": "1.5.x"` tells bower to install a version of the angular-animate module that
is compatible with version 1.5.x of AngularJS.
* `"jquery": "3.2.x"` tells bower to install the latest patch release of the 3.2 version of jQuery.
Note that this is not an AngularJS library; it is the standard jQuery library. We can use bower to
install a wide range of 3rd party libraries.
Now, we must tell npm to download and install these dependencies.
Now, we must tell bower to download and install these dependencies.
```
npm install
```
<div class="alert alert-info">
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
we have preconfigured `npm install` to run bower for us.
</div>
<div class="alert alert-warning">
**Warning:** If a new version of AngularJS has been released since you last ran `npm install`, then
you may have a problem with the `bower install` due to a conflict between the versions of
angular.js that need to be installed. If you run into this issue, simply delete your
`app/bower_components` directory and then run `npm install`.
</div>
## How Animations work with `ngAnimate`
@@ -85,12 +101,12 @@ code necessary to make your application "animation aware".
...
<!-- Used for JavaScript animations (include this before angular.js) -->
<script src="lib/jquery/dist/jquery.js"></script>
<script src="bower_components/jquery/dist/jquery.js"></script>
...
<!-- Adds animation support in AngularJS -->
<script src="lib/angular-animate/angular-animate.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<!-- Defines JavaScript animations -->
<script src="app.animations.js"></script>
@@ -99,8 +115,8 @@ code necessary to make your application "animation aware".
```
<div class="alert alert-error">
**Important:** Be sure to use jQuery version 2.1 or newer, when using AngularJS 1.5 or newer;
jQuery 1.x is not officially supported.
**Important:** Be sure to use jQuery version 2.1 or newer, when using AngularJS 1.5 or newer; jQuery 1.x is
not officially supported.
In order for AngularJS to detect jQuery and take advantage of it, make sure to include `jquery.js`
before `angular.js`.
</div>
@@ -540,9 +556,9 @@ There you have it! We have created a web application in a relatively short amoun
<ul doc-tutorial-nav="14"></ul>
[caniuse-css-animation]: https://caniuse.com/#feat=css-animation
[caniuse-css-transitions]: https://caniuse.com/#feat=css-transitions
[bower]: http://bower.io/
[caniuse-css-animation]: http://caniuse.com/#feat=css-animation
[caniuse-css-transitions]: http://caniuse.com/#feat=css-transitions
[jquery]: https://jquery.com/
[jquery-animate]: https://api.jquery.com/animate
[jquery-animate]: https://api.jquery.com/animate/
[mdn-animations]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations
[npm]: https://www.npmjs.com/
+1 -1
View File
@@ -22,5 +22,5 @@ If you have questions or feedback or just want to say "hi", please post a messag
[angular-seed]: https://github.com/angular/angular-seed
[gitter]: https://gitter.im/angular/angular.js
[irc]: https://webchat.freenode.net/?channels=angularjs&uio=d4
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
[mailing-list]: https://groups.google.com/forum/#!forum/angular
+1 -1
View File
@@ -20,7 +20,7 @@ function main() {
} catch (e) {
fs.mkdirSync(__dirname + '/../../../src/ngParseExt');
}
fs.writeFileSync(__dirname + '/../../../src/ngParseExt/ucd.js', code);
fs.writeFile(__dirname + '/../../../src/ngParseExt/ucd.js', code);
}
}
-12
View File
@@ -1,12 +0,0 @@
'use strict';
var angularFiles = require('./angularFiles');
var sharedConfig = require('./karma-shared.conf');
module.exports = function(config) {
sharedConfig(config, {testName: 'AngularJS: isolated module tests (ngAnimate)', logFile: 'karma-ngAnimate-isolated.log'});
config.set({
files: angularFiles.mergeFilesFor('karmaModules-ngAnimate')
});
};
-12
View File
@@ -1,12 +0,0 @@
'use strict';
var angularFiles = require('./angularFiles');
var sharedConfig = require('./karma-shared.conf');
module.exports = function(config) {
sharedConfig(config, {testName: 'AngularJS: isolated module tests (ngMock)', logFile: 'karma-ngMock-isolated.log'});
config.set({
files: angularFiles.mergeFilesFor('karmaModules-ngMock')
});
};
+8 -11
View File
@@ -1,20 +1,17 @@
'use strict';
var angularFiles = require('./angularFiles');
var sharedConfig = require('./karma-shared.conf');
module.exports = function(config) {
sharedConfig(config, {testName: 'AngularJS: isolated module tests', logFile: 'karma-modules-isolated.log'});
sharedConfig(config, {testName: 'AngularJS: modules', logFile: 'karma-modules.log'});
config.set({
files: [
'build/angular.js',
'build/angular-mocks.js',
'test/modules/no_bootstrap.js',
'test/helpers/matchers.js',
'test/helpers/privateMocks.js',
'test/helpers/support.js',
'test/helpers/testabilityPatch.js',
'build/test-bundles/angular-*.js'
]
files: angularFiles.mergeFilesFor('karmaModules'),
junitReporter: {
outputFile: 'test_out/modules.xml',
suite: 'modules'
}
});
};
+14 -8
View File
@@ -23,7 +23,12 @@ module.exports = function(config, specificOptions) {
// SauceLabs config for local development.
sauceLabs: {
testName: specificOptions.testName || 'AngularJS',
startConnect: true
startConnect: true,
options: {
// We need selenium version +2.46 for Firefox 39 and the last selenium version for OS X is 2.45.
// TODO: Uncomment when there is a selenium 2.46 available for OS X.
// 'selenium-version': '2.46.0'
}
},
// BrowserStack config for local development.
@@ -60,11 +65,13 @@ module.exports = function(config, specificOptions) {
'SL_Safari-1': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.12',
version: 'latest-1'
},
'SL_Safari': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.12',
version: 'latest'
},
'SL_IE_9': {
@@ -97,15 +104,17 @@ module.exports = function(config, specificOptions) {
platform: 'Windows 10',
version: 'latest-1'
},
'SL_iOS': {
'SL_iOS_10': {
base: 'SauceLabs',
browserName: 'iphone',
version: 'latest'
platform: 'OS X 10.12',
version: '10.3'
},
'SL_iOS-1': {
'SL_iOS_11': {
base: 'SauceLabs',
browserName: 'iphone',
version: 'latest-1'
platform: 'OS X 10.12',
version: '11.2'
},
'BS_Chrome': {
@@ -184,9 +193,6 @@ module.exports = function(config, specificOptions) {
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
config.sauceLabs.recordScreenshots = true;
// Try 'websocket' for a faster transmission first. Fallback to 'polling' if necessary.
config.transports = ['websocket', 'polling'];
// Debug logging into a file, that we print out at the end of the build.
config.loggers.push({
type: 'file',
+4 -12
View File
@@ -75,8 +75,10 @@ module.exports = {
},
wrap(src, name) {
return [`src/${name}.prefix`, ...src, `src/${name}.suffix`];
wrap: function(src, name) {
src.unshift('src/' + name + '.prefix');
src.push('src/' + name + '.suffix');
return src;
},
@@ -133,16 +135,6 @@ module.exports = {
build: function(config, fn) {
var files = grunt.file.expand(config.src);
// grunt.file.expand might reorder the list of files
// when it is expanding globs, so we use prefix and suffix
// fields to ensure that files are at the start of end of
// the list (primarily for wrapping in an IIFE).
if (config.prefix) {
files = grunt.file.expand(config.prefix).concat(files);
}
if (config.suffix) {
files = files.concat(grunt.file.expand(config.suffix));
}
var styles = config.styles;
var processedStyles;
//concat
+9 -6
View File
@@ -11,14 +11,14 @@ set -e
# Curl and run this script as part of your .travis.yml before_script section:
# before_script:
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
SC_VERSION="4.5.2"
SC_VERSION="4.4.12"
CONNECT_URL="https://saucelabs.com/downloads/sc-$SC_VERSION-linux.tar.gz"
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
CONNECT_DOWNLOAD="sc-$SC_VERSION-linux.tar.gz"
# We don't want to create a log file because sauceconnect always logs in verbose mode. This seems
# to be overwhelming Travis and causing flakes when we are cat-ing the log in "print_logs.sh"
CONNECT_LOG="/dev/null"
CONNECT_LOG="$LOGS_DIR/sauce-connect"
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
CONNECT_STDERR="$LOGS_DIR/sauce-connect.stderr"
# Get Connect and start it
mkdir -p $CONNECT_DIR
@@ -42,6 +42,9 @@ if [ ! -z "$BROWSER_PROVIDER_READY_FILE" ]; then
fi
echo "Starting Sauce Connect in the background"
echo "Starting Sauce Connect in the background, logging into:"
echo " $CONNECT_LOG"
echo " $CONNECT_STDOUT"
echo " $CONNECT_STDERR"
sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS \
--logfile $CONNECT_LOG &
--logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT &
+12 -16
View File
@@ -1,22 +1,23 @@
{
"name": "angular",
"name": "angularjs",
"license": "MIT",
"branchVersion": "^1.7.0",
"branchPattern": "1.7.*",
"distTag": "latest",
"distTag": "next",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
},
"engines": {
"node": ">=8.12.0",
"yarn": ">=1.10.1",
"grunt-cli": "^1.2.0"
"node": "^8.9.1",
"yarn": ">=1.3.2",
"grunt": "^1.2.0"
},
"scripts": {
"commit": "git-cz",
"test-i18n": "jasmine-node i18n/spec",
"test-i18n-ucd": "jasmine-node i18n/ucd/spec"
"test-i18n-ucd": "jasmine-node i18n/ucd/spec",
"grunt": "grunt"
},
"devDependencies": {
"angular-benchpress": "0.x.x",
@@ -32,7 +33,7 @@
"cross-spawn": "^4.0.0",
"cz-conventional-changelog": "1.1.4",
"dgeni": "^0.4.9",
"dgeni-packages": "^0.26.5",
"dgeni-packages": "^0.26.2",
"eslint-plugin-promise": "^3.6.0",
"event-stream": "~3.1.0",
"glob": "^6.0.1",
@@ -62,7 +63,7 @@
"jquery": "3.2.1",
"jquery-2.1": "npm:jquery@2.1.4",
"jquery-2.2": "npm:jquery@2.2.4",
"karma": "^3.1.4",
"karma": "^2.0.4",
"karma-browserstack-launcher": "^1.3.0",
"karma-chrome-launcher": "^2.2.0",
"karma-edge-launcher": "^0.4.2",
@@ -71,7 +72,7 @@
"karma-jasmine": "^1.1.2",
"karma-junit-reporter": "^1.2.0",
"karma-safari-launcher": "^1.0.0",
"karma-sauce-launcher": "^2.0.2",
"karma-sauce-launcher": "^1.2.0",
"karma-script-launcher": "^1.0.0",
"karma-spec-reporter": "^0.0.32",
"load-grunt-tasks": "^3.5.0",
@@ -83,13 +84,13 @@
"npm-run": "^4.1.0",
"open-sans-fontface": "^1.4.0",
"promises-aplus-tests": "~2.1.0",
"protractor": "^5.4.1",
"protractor": "^5.1.2",
"q": "~1.0.0",
"q-io": "^1.10.9",
"qq": "^0.3.5",
"rewire": "~2.1.0",
"sax": "^1.1.1",
"selenium-webdriver": "^4.0.0-alpha.1",
"selenium-webdriver": "^2.53.1",
"semver": "^5.4.1",
"serve-favicon": "^2.3.0",
"serve-index": "^1.8.0",
@@ -99,11 +100,6 @@
"stringmap": "^0.2.2"
},
"dependencies": {},
"resolutions": {
"//1": "`natives@1.1.0` does not work with Node.js 10.x on Windows 10",
"//2": "(E.g. see https://github.com/gulpjs/gulp/issues/2162.)",
"natives": "1.1.3"
},
"commitplease": {
"style": "angular",
"nohook": true
+1 -3
View File
@@ -79,8 +79,6 @@ function capabilitiesForSauceLabs(capabilities) {
'browserName': capabilities.browserName,
'platform': capabilities.platform,
'version': capabilities.version,
'elementScrollBehavior': 1,
// Allow e2e test sessions to run for a maximum of 35 minutes, instead of the default 30 minutes.
'maxDuration': 2100
'elementScrollBehavior': 1
};
}
+1 -1
View File
@@ -27,7 +27,7 @@ function prepare {
for repo in "${REPOS[@]}"
do
echo "-- Cloning bower-$repo"
git clone https://github.com/angular/bower-$repo.git $TMP_DIR/bower-$repo --depth=1
git clone git@github.com:angular/bower-$repo.git $TMP_DIR/bower-$repo --depth=1
done
+1 -1
View File
@@ -25,7 +25,7 @@ function init {
function prepare {
echo "-- Cloning code.angularjs.org"
git clone https://github.com/angular/code.angularjs.org $REPO_DIR --depth=1
git clone git@github.com:angular/code.angularjs.org.git $REPO_DIR --depth=1
echo "-- Updating code.angularjs.org"
+5 -5
View File
@@ -21,21 +21,21 @@ rm -f angular.js.size
# BUILD #
yarn grunt ci-checks package --no-color
yarn run grunt -- ci-checks package --no-color
mkdir -p test_out
# UNIT TESTS #
yarn grunt test:unit --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
yarn run grunt -- test:unit --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
# END TO END TESTS #
yarn grunt test:ci-protractor
yarn run grunt -- test:ci-protractor
# DOCS APP TESTS #
yarn grunt test:docs --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
yarn run grunt -- test:docs --browsers="$BROWSERS" --reporters=dots,junit --no-colors --no-color
# Promises/A+ TESTS #
yarn grunt test:promises-aplus --no-color
yarn run grunt -- test:promises-aplus --no-color
# CHECK SIZE #
+2 -2
View File
@@ -8,11 +8,11 @@ nvm install
# clean out and install yarn
rm -rf ~/.yarn
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.10.1
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2
export PATH="$HOME/.yarn/bin:$PATH"
# Ensure that we have the local dependencies installed
yarn install
echo testing grunt version
yarn grunt --version
yarn run grunt -- --version
+1 -1
View File
@@ -37,7 +37,7 @@ function init {
function build {
cd ../..
source scripts/jenkins/init-node.sh
yarn grunt ci-checks package --no-color
yarn run grunt -- ci-checks package --no-color
cd $SCRIPT_DIR
}
+5 -3
View File
@@ -2,6 +2,8 @@
set -e
yarn global add grunt-cli@1.2.0
mkdir -p "$LOGS_DIR"
if [ "$JOB" != "ci-checks" ]; then
@@ -11,13 +13,13 @@ fi
# ci-checks and unit tests do not run against the packaged code
if [[ "$JOB" != "ci-checks" ]] && [[ "$JOB" != unit-* ]]; then
yarn grunt package
grunt package
fi
# unit runs the docs tests too which need a built version of the code
if [[ "$JOB" = unit-* ]]; then
yarn grunt validate-angular-files
yarn grunt build
grunt validate-angular-files
grunt build
fi
# check this after the package, because at this point the browser_provider
+7 -10
View File
@@ -11,14 +11,14 @@ export SAUCE_ACCESS_KEY
BROWSER_STACK_ACCESS_KEY=$(echo "$BROWSER_STACK_ACCESS_KEY" | rev)
SAUCE_ACCESS_KEY=$(echo "$SAUCE_ACCESS_KEY" | rev)
# The currently latest version of Safari on Saucelabs (v12) is unstable and disconnects frequently.
# TODO: Add `SL_Safari` back, once/if it becomes more stable again.
# TODO: restore "SL_EDGE-1" once Sauce Labs adds Edge 17 and "SL_EDGE-1" refers
# to version 16. Edge 15 disconnects from Karma frequently causing extreme build instability.
BROWSERS="SL_Chrome,SL_Chrome-1,\
SL_Firefox,SL_Firefox-1,\
SL_Safari-1,\
SL_iOS,SL_iOS-1,\
SL_Safari,SL_Safari-1,\
SL_iOS_10,SL_iOS_11,\
SL_IE_9,SL_IE_10,SL_IE_11,\
SL_EDGE,SL_EDGE-1"
SL_EDGE"
case "$JOB" in
"ci-checks")
@@ -29,21 +29,19 @@ case "$JOB" in
# convert commit range to 2 dots, as commitplease uses `git log`.
# See https://github.com/travis-ci/travis-ci/issues/4596 for more info
echo "Validate commit messages in PR:"
yarn run commitplease "${TRAVIS_COMMIT_RANGE/.../..}"
yarn run commitplease -- "${TRAVIS_COMMIT_RANGE/.../..}"
fi
;;
"unit-core")
grunt test:promises-aplus
grunt test:jqlite --browsers="$BROWSERS" --reporters=spec
grunt test:modules --browsers="$BROWSERS" --reporters=spec
;;
"unit-jquery")
grunt test:jquery --browsers="$BROWSERS" --reporters=spec
grunt test:jquery-2.2 --browsers="$BROWSERS" --reporters=spec
grunt test:jquery-2.1 --browsers="$BROWSERS" --reporters=spec
;;
"unit-modules")
grunt test:modules --browsers="$BROWSERS" --reporters=spec
;;
"docs-app")
grunt tests:docs --browsers="$BROWSERS" --reporters=spec
grunt test:travis-protractor --specs="docs/app/e2e/**/*.scenario.js"
@@ -104,7 +102,6 @@ case "$JOB" in
'ci-checks',\
'unit-core',\
'unit-jquery',\
'unit-modules',\
'docs-app',\
'e2e',\
or\
+1 -1
View File
@@ -7,5 +7,5 @@ for FILE in $LOG_FILES; do
echo "================================================================================"
echo " $FILE"
echo "================================================================================"
cat $FILE || true
cat $FILE
done
+13 -32
View File
@@ -396,8 +396,8 @@ function extend(dst) {
* sinceVersion="1.6.5"
* This function is deprecated, but will not be removed in the 1.x lifecycle.
* There are edge cases (see {@link angular.merge#known-issues known issues}) that are not
* supported by this function. We suggest using another, similar library for all-purpose merging,
* such as [lodash's merge()](https://lodash.com/docs/4.17.4#merge).
* supported by this function. We suggest
* using [lodash's merge()](https://lodash.com/docs/4.17.4#merge) instead.
*
* @knownIssue
* This is a list of (known) object types that are not handled correctly by this function:
@@ -406,8 +406,6 @@ function extend(dst) {
* - [`CanvasGradient`](https://developer.mozilla.org/docs/Web/API/CanvasGradient)
* - AngularJS {@link $rootScope.Scope scopes};
*
* `angular.merge` also does not support merging objects with circular references.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @returns {Object} Reference to `dst`.
@@ -785,9 +783,7 @@ function arrayRemove(array, value) {
* @kind function
*
* @description
* Creates a deep copy of `source`, which should be an object or an array. This functions is used
* internally, mostly in the change-detection code. It is not intended as an all-purpose copy
* function, and has several limitations (see below).
* Creates a deep copy of `source`, which should be an object or an array.
*
* * If no destination is supplied, a copy of the object or array is created.
* * If a destination is provided, all of its elements (for arrays) or properties (for objects)
@@ -796,35 +792,15 @@ function arrayRemove(array, value) {
* * If `source` is identical to `destination` an exception will be thrown.
*
* <br />
*
* <div class="alert alert-warning">
* Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
* and on `destination`) will be ignored.
* </div>
*
* <div class="alert alert-warning">
* `angular.copy` does not check if destination and source are of the same type. It's the
* developer's responsibility to make sure they are compatible.
* </div>
*
* @knownIssue
* This is a non-exhaustive list of object types / features that are not handled correctly by
* `angular.copy`. Note that since this functions is used by the change detection code, this
* means binding or watching objects of these types (or that include these types) might not work
* correctly.
* - [`File`](https://developer.mozilla.org/docs/Web/API/File)
* - [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)
* - [`ImageData`](https://developer.mozilla.org/docs/Web/API/ImageData)
* - [`MediaStream`](https://developer.mozilla.org/docs/Web/API/MediaStream)
* - [`Set`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)
* - [`WeakMap`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
* - ['getter'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/
* [`setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set)`
*
* @param {*} source The source that will be used to make a copy. Can be any type, including
* primitives, `null`, and `undefined`.
* @param {(Object|Array)=} destination Destination into which the source is copied. If provided,
* must be of the same type as `source`.
* @param {*} source The source that will be used to make a copy.
* Can be any type, including primitives, `null`, and `undefined`.
* @param {(Object|Array)=} destination Destination into which the source is copied. If
* provided, must be of the same type as `source`.
* @returns {*} The copy or updated `destination`, if `destination` was specified.
*
* @example
@@ -1719,8 +1695,13 @@ function angularInit(element, bootstrap) {
});
if (appElement) {
if (!isAutoBootstrapAllowed) {
window.console.error('AngularJS: disabling automatic bootstrap. <script> protocol indicates ' +
try {
window.console.error('AngularJS: disabling automatic bootstrap. <script> protocol indicates ' +
'an extension, document.location.href does not match.');
} catch (e) {
// Support: Safari 11 w/ Webdriver
// The console.error will throw and make the test fail
}
return;
}
config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
+2 -3
View File
@@ -7,7 +7,7 @@
htmlAnchorDirective,
inputDirective,
hiddenInputBrowserCacheDirective,
inputDirective,
formDirective,
scriptDirective,
selectDirective,
@@ -221,8 +221,7 @@ function publishExternalAPI(angular) {
ngModelOptions: ngModelOptionsDirective
}).
directive({
ngInclude: ngIncludeFillContentDirective,
input: hiddenInputBrowserCacheDirective
ngInclude: ngIncludeFillContentDirective
}).
directive(ngAttributeAliasDirectives).
directive(ngEventDirectives);
-3
View File
@@ -108,9 +108,6 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
if (url) {
var sameState = lastHistoryState === state;
// Normalize the inputted URL
url = urlResolve(url).href;
// Don't change anything if previous and current URLs and states match. This also prevents
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
// See https://github.com/angular/angular.js/commit/ffb2701
+2 -11
View File
@@ -2231,16 +2231,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
this.$$element.removeAttr(attrName);
} else {
if (SIMPLE_ATTR_NAME.test(attrName)) {
// jQuery skips special boolean attrs treatment in XML nodes for
// historical reasons and hence AngularJS cannot freely call
// `.attr(attrName, false) with such attributes. To avoid issues
// in XHTML, call `removeAttr` in such cases instead.
// See https://github.com/jquery/jquery/issues/4249
if (booleanKey && value === false) {
this.$$element.removeAttr(attrName);
} else {
this.$$element.attr(attrName, value);
}
this.$$element.attr(attrName, value);
} else {
setSpecialAttr(this.$$element[0], attrName, value);
}
@@ -3837,7 +3828,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
pre: function ngPropPreLinkFn(scope, $element) {
function applyPropValue() {
var propValue = ngPropGetter(scope);
$element[0][propName] = sanitizer(propValue);
$element.prop(propName, sanitizer(propValue));
}
applyPropValue();
+3 -6
View File
@@ -181,6 +181,7 @@
</file>
</example>
*
* @element INPUT
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
* then the `disabled` attribute will be set on the element
*/
@@ -407,7 +408,7 @@ forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
// ng-src, ng-srcset, ng-href are interpolated
forEach(['src', 'srcset', 'href'], function(attrName) {
var normalized = directiveNormalize('ng-' + attrName);
ngAttributeAliasDirectives[normalized] = ['$sce', function($sce) {
ngAttributeAliasDirectives[normalized] = function() {
return {
priority: 99, // it needs to run after the attributes are interpolated
link: function(scope, element, attr) {
@@ -421,10 +422,6 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
propName = null;
}
// We need to sanitize the url at least once, in case it is a constant
// non-interpolated attribute.
attr.$set(normalized, $sce.getTrustedMediaUrl(attr[normalized]));
attr.$observe(normalized, function(value) {
if (!value) {
if (attrName === 'href') {
@@ -444,5 +441,5 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
});
}
};
}];
};
});
+31 -115
View File
@@ -909,10 +909,8 @@ var inputType = {
*
* <div class="alert alert-warning">
* **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex
* used in Chromium, which may not fulfill your app's requirements.
* If you need stricter (e.g. requiring a top-level domain), or more relaxed validation
* (e.g. allowing IPv6 address literals) you can use `ng-pattern` or
* modify the built-in validators (see the {@link guide/forms Forms guide}).
* used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can
* use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide})
* </div>
*
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
@@ -1499,7 +1497,7 @@ function createDateParser(regexp, mapping) {
}
function createDateInputType(type, regexp, parseDate, format) {
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
badInputChecker(scope, element, attr, ctrl, type);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
@@ -1542,34 +1540,24 @@ function createDateInputType(type, regexp, parseDate, format) {
});
if (isDefined(attr.min) || attr.ngMin) {
var minVal = attr.min || $parse(attr.ngMin)(scope);
var parsedMinVal = parseObservedDateValue(minVal);
var minVal;
ctrl.$validators.min = function(value) {
return !isValidDate(value) || isUndefined(parsedMinVal) || parseDate(value) >= parsedMinVal;
return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
};
attr.$observe('min', function(val) {
if (val !== minVal) {
parsedMinVal = parseObservedDateValue(val);
minVal = val;
ctrl.$validate();
}
minVal = parseObservedDateValue(val);
ctrl.$validate();
});
}
if (isDefined(attr.max) || attr.ngMax) {
var maxVal = attr.max || $parse(attr.ngMax)(scope);
var parsedMaxVal = parseObservedDateValue(maxVal);
var maxVal;
ctrl.$validators.max = function(value) {
return !isValidDate(value) || isUndefined(parsedMaxVal) || parseDate(value) <= parsedMaxVal;
return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
};
attr.$observe('max', function(val) {
if (val !== maxVal) {
parsedMaxVal = parseObservedDateValue(val);
maxVal = val;
ctrl.$validate();
}
maxVal = parseObservedDateValue(val);
ctrl.$validate();
});
}
@@ -1721,68 +1709,50 @@ function isValidForStep(viewValue, stepBase, step) {
return (value - stepBase) % step === 0;
}
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
badInputChecker(scope, element, attr, ctrl, 'number');
numberFormatterParser(ctrl);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
var parsedMinVal;
var minVal;
var maxVal;
if (isDefined(attr.min) || attr.ngMin) {
var minVal = attr.min || $parse(attr.ngMin)(scope);
parsedMinVal = parseNumberAttrVal(minVal);
ctrl.$validators.min = function(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || isUndefined(parsedMinVal) || viewValue >= parsedMinVal;
return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal;
};
attr.$observe('min', function(val) {
if (val !== minVal) {
parsedMinVal = parseNumberAttrVal(val);
minVal = val;
// TODO(matsko): implement validateLater to reduce number of validations
ctrl.$validate();
}
minVal = parseNumberAttrVal(val);
// TODO(matsko): implement validateLater to reduce number of validations
ctrl.$validate();
});
}
if (isDefined(attr.max) || attr.ngMax) {
var maxVal = attr.max || $parse(attr.ngMax)(scope);
var parsedMaxVal = parseNumberAttrVal(maxVal);
ctrl.$validators.max = function(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || isUndefined(parsedMaxVal) || viewValue <= parsedMaxVal;
return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal;
};
attr.$observe('max', function(val) {
if (val !== maxVal) {
parsedMaxVal = parseNumberAttrVal(val);
maxVal = val;
// TODO(matsko): implement validateLater to reduce number of validations
ctrl.$validate();
}
maxVal = parseNumberAttrVal(val);
// TODO(matsko): implement validateLater to reduce number of validations
ctrl.$validate();
});
}
if (isDefined(attr.step) || attr.ngStep) {
var stepVal = attr.step || $parse(attr.ngStep)(scope);
var parsedStepVal = parseNumberAttrVal(stepVal);
var stepVal;
ctrl.$validators.step = function(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || isUndefined(parsedStepVal) ||
isValidForStep(viewValue, parsedMinVal || 0, parsedStepVal);
return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) ||
isValidForStep(viewValue, minVal || 0, stepVal);
};
attr.$observe('step', function(val) {
stepVal = parseNumberAttrVal(val);
// TODO(matsko): implement validateLater to reduce number of validations
if (val !== stepVal) {
parsedStepVal = parseNumberAttrVal(val);
stepVal = val;
ctrl.$validate();
}
ctrl.$validate();
});
}
}
@@ -1812,8 +1782,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
originalRender;
if (hasMinAttr) {
minVal = parseNumberAttrVal(attr.min);
ctrl.$validators.min = supportsRange ?
// Since all browsers set the input to a valid value, we don't need to check validity
function noopMinValidator() { return true; } :
@@ -1826,8 +1794,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
if (hasMaxAttr) {
maxVal = parseNumberAttrVal(attr.max);
ctrl.$validators.max = supportsRange ?
// Since all browsers set the input to a valid value, we don't need to check validity
function noopMaxValidator() { return true; } :
@@ -1840,8 +1806,6 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
if (hasStepAttr) {
stepVal = parseNumberAttrVal(attr.step);
ctrl.$validators.step = supportsRange ?
function nativeStepValidator() {
// Currently, only FF implements the spec on step change correctly (i.e. adjusting the
@@ -1863,13 +1827,7 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
// attribute value when the input is first rendered, so that the browser can adjust the
// input value based on the min/max value
element.attr(htmlAttrName, attr[htmlAttrName]);
var oldVal = attr[htmlAttrName];
attr.$observe(htmlAttrName, function wrappedObserver(val) {
if (val !== oldVal) {
oldVal = val;
changeFn(val);
}
});
attr.$observe(htmlAttrName, changeFn);
}
function minChange(val) {
@@ -1923,11 +1881,11 @@ function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
// Some browsers don't adjust the input value correctly, but set the stepMismatch error
if (!supportsRange) {
if (supportsRange && ctrl.$viewValue !== element.val()) {
ctrl.$setViewValue(element.val());
} else {
// TODO(matsko): implement validateLater to reduce number of validations
ctrl.$validate();
} else if (ctrl.$viewValue !== element.val()) {
ctrl.$setViewValue(element.val());
}
}
}
@@ -2235,48 +2193,6 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
}];
var hiddenInputBrowserCacheDirective = function() {
var valueProperty = {
configurable: true,
enumerable: false,
get: function() {
return this.getAttribute('value') || '';
},
set: function(val) {
this.setAttribute('value', val);
}
};
return {
restrict: 'E',
priority: 200,
compile: function(_, attr) {
if (lowercase(attr.type) !== 'hidden') {
return;
}
return {
pre: function(scope, element, attr, ctrls) {
var node = element[0];
// Support: Edge
// Moving the DOM around prevents autofillling
if (node.parentNode) {
node.parentNode.insertBefore(node, node.nextSibling);
}
// Support: FF, IE
// Avoiding direct assignment to .value prevents autofillling
if (Object.defineProperty) {
Object.defineProperty(node, 'value', valueProperty);
}
}
};
}
};
};
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
/**
-4
View File
@@ -125,8 +125,6 @@ function classDirective(name, selector) {
}
function toClassString(classValue) {
if (!classValue) return classValue;
var classString = classValue;
if (isArray(classValue)) {
@@ -135,8 +133,6 @@ function classDirective(name, selector) {
classString = Object.keys(classValue).
filter(function(key) { return classValue[key]; }).
join(' ');
} else if (!isString(classValue)) {
classString = classValue + '';
}
return classString;
-1
View File
@@ -562,7 +562,6 @@ NgModelController.prototype = {
* `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
*/
$validate: function() {
// ignore $validate before model is initialized
if (isNumberNaN(this.$modelValue)) {
return;
+20 -24
View File
@@ -454,13 +454,6 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
return block.clone[block.clone.length - 1];
};
var trackByIdArrayFn = function($scope, key, value) {
return hashKey(value);
};
var trackByIdObjFn = function($scope, key) {
return key;
};
return {
restrict: 'A',
@@ -500,23 +493,32 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
aliasAs);
}
var trackByIdExpFn;
var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
var hashFnLocals = {$id: hashKey};
if (trackByExp) {
var hashFnLocals = {$id: hashKey};
var trackByExpGetter = $parse(trackByExp);
trackByIdExpFn = function($scope, key, value, index) {
// assign key, value, and $index to the locals so that they can be used in hash functions
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
hashFnLocals[valueIdentifier] = value;
hashFnLocals.$index = index;
return trackByExpGetter($scope, hashFnLocals);
trackByExpGetter = $parse(trackByExp);
} else {
trackByIdArrayFn = function(key, value) {
return hashKey(value);
};
trackByIdObjFn = function(key) {
return key;
};
}
return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
if (trackByExpGetter) {
trackByIdExpFn = function(key, value, index) {
// assign key, value, and $index to the locals so that they can be used in hash functions
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
hashFnLocals[valueIdentifier] = value;
hashFnLocals.$index = index;
return trackByExpGetter($scope, hashFnLocals);
};
}
// Store a list of elements from previous run. This is a hash where key is the item from the
// iterator, and the value is objects with following properties.
// - scope: bound scope
@@ -570,7 +572,7 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
for (index = 0; index < collectionLength; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
trackById = trackByIdFn($scope, key, value, index);
trackById = trackByIdFn(key, value, index);
if (lastBlockMap[trackById]) {
// found previously seen block
block = lastBlockMap[trackById];
@@ -592,12 +594,6 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
}
}
// Clear the value property from the hashFnLocals object to prevent a reference to the last value
// being leaked into the ngRepeatCompile function scope
if (hashFnLocals) {
hashFnLocals[valueIdentifier] = undefined;
}
// remove leftover items
for (var blockKey in lastBlockMap) {
block = lastBlockMap[blockKey];
+1 -8
View File
@@ -54,14 +54,7 @@
var ngStyleDirective = ngDirective(function(scope, element, attr) {
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
if (oldStyles && (newStyles !== oldStyles)) {
if (!newStyles) {
newStyles = {};
}
forEach(oldStyles, function(val, style) {
if (newStyles[style] == null) {
newStyles[style] = '';
}
});
forEach(oldStyles, function(val, style) { element.css(style, '');});
}
if (newStyles) element.css(newStyles);
});
+38 -104
View File
@@ -62,35 +62,24 @@
* </file>
* </example>
*/
var requiredDirective = ['$parse', function($parse) {
var requiredDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
// For boolean attributes like required, presence means true
var value = attr.hasOwnProperty('required') || $parse(attr.ngRequired)(scope);
if (!attr.ngRequired) {
// force truthy in case we are on non input element
// (input elements do this automatically for boolean attributes like required)
attr.required = true;
}
attr.required = true; // force truthy in case we are on non input element
ctrl.$validators.required = function(modelValue, viewValue) {
return !value || !ctrl.$isEmpty(viewValue);
return !attr.required || !ctrl.$isEmpty(viewValue);
};
attr.$observe('required', function(newVal) {
if (value !== newVal) {
value = newVal;
ctrl.$validate();
}
attr.$observe('required', function() {
ctrl.$validate();
});
}
};
}];
};
/**
* @ngdoc directive
@@ -173,59 +162,36 @@ var requiredDirective = ['$parse', function($parse) {
* </file>
* </example>
*/
var patternDirective = ['$parse', function($parse) {
var patternDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
compile: function(tElm, tAttr) {
var patternExp;
var parseFn;
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
if (tAttr.ngPattern) {
patternExp = tAttr.ngPattern;
// ngPattern might be a scope expression, or an inlined regex, which is not parsable.
// We get value of the attribute here, so we can compare the old and the new value
// in the observer to avoid unnecessary validations
if (tAttr.ngPattern.charAt(0) === '/' && REGEX_STRING_REGEXP.test(tAttr.ngPattern)) {
parseFn = function() { return tAttr.ngPattern; };
} else {
parseFn = $parse(tAttr.ngPattern);
}
}
return function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var attrVal = attr.pattern;
if (attr.ngPattern) {
attrVal = parseFn(scope);
} else {
patternExp = attr.pattern;
var regexp, patternExp = attr.ngPattern || attr.pattern;
attr.$observe('pattern', function(regex) {
if (isString(regex) && regex.length > 0) {
regex = new RegExp('^' + regex + '$');
}
var regexp = parsePatternAttr(attrVal, patternExp, elm);
if (regex && !regex.test) {
throw minErr('ngPattern')('noregexp',
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
regex, startingTag(elm));
}
attr.$observe('pattern', function(newVal) {
var oldRegexp = regexp;
regexp = regex || undefined;
ctrl.$validate();
});
regexp = parsePatternAttr(newVal, patternExp, elm);
if ((oldRegexp && oldRegexp.toString()) !== (regexp && regexp.toString())) {
ctrl.$validate();
}
});
ctrl.$validators.pattern = function(modelValue, viewValue) {
// HTML5 pattern constraint validates the input value, so we validate the viewValue
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
};
ctrl.$validators.pattern = function(modelValue, viewValue) {
// HTML5 pattern constraint validates the input value, so we validate the viewValue
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
};
}
};
}];
};
/**
* @ngdoc directive
@@ -298,29 +264,25 @@ var patternDirective = ['$parse', function($parse) {
* </file>
* </example>
*/
var maxlengthDirective = ['$parse', function($parse) {
var maxlengthDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var maxlength = attr.maxlength || $parse(attr.ngMaxlength)(scope);
var maxlengthParsed = parseLength(maxlength);
var maxlength = -1;
attr.$observe('maxlength', function(value) {
if (maxlength !== value) {
maxlengthParsed = parseLength(value);
maxlength = value;
ctrl.$validate();
}
var intVal = toInt(value);
maxlength = isNumberNaN(intVal) ? -1 : intVal;
ctrl.$validate();
});
ctrl.$validators.maxlength = function(modelValue, viewValue) {
return (maxlengthParsed < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlengthParsed);
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
};
}
};
}];
};
/**
* @ngdoc directive
@@ -391,49 +353,21 @@ var maxlengthDirective = ['$parse', function($parse) {
* </file>
* </example>
*/
var minlengthDirective = ['$parse', function($parse) {
var minlengthDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var minlength = attr.minlength || $parse(attr.ngMinlength)(scope);
var minlengthParsed = parseLength(minlength) || -1;
var minlength = 0;
attr.$observe('minlength', function(value) {
if (minlength !== value) {
minlengthParsed = parseLength(value) || -1;
minlength = value;
ctrl.$validate();
}
minlength = toInt(value) || 0;
ctrl.$validate();
});
ctrl.$validators.minlength = function(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlengthParsed;
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
};
}
};
}];
function parsePatternAttr(regex, patternExp, elm) {
if (!regex) return undefined;
if (isString(regex)) {
regex = new RegExp('^' + regex + '$');
}
if (!regex.test) {
throw minErr('ngPattern')('noregexp',
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
regex, startingTag(elm));
}
return regex;
}
function parseLength(val) {
var intVal = toInt(val);
return isNumberNaN(intVal) ? -1 : intVal;
}
};
+1 -1
View File
@@ -242,7 +242,7 @@ function $InterpolateProvider() {
// Provide a quick exit and simplified result function for text with no interpolation
if (!text.length || text.indexOf(startSymbol) === -1) {
if (mustHaveExpression) return;
if (mustHaveExpression && !contextAllowsConcatenation) return;
var unescapedText = unescapeText(text);
if (contextAllowsConcatenation) {
+1 -7
View File
@@ -683,11 +683,5 @@ function markQStateExceptionHandled(state) {
state.pur = true;
}
function markQExceptionHandled(q) {
// Built-in `$q` promises will always have a `$$state` property. This check is to allow
// overwriting `$q` with a different promise library (e.g. Bluebird + angular-bluebird-promises).
// (Currently, this is the only method that might be called with a promise, even if it is not
// created by the built-in `$q`.)
if (q.$$state) {
markQStateExceptionHandled(q.$$state);
}
markQStateExceptionHandled(q.$$state);
}
+1 -13
View File
@@ -10,12 +10,6 @@ var urlParsingNode = window.document.createElement('a');
var originUrl = urlResolve(window.location.href);
var baseUrlParsingNode;
urlParsingNode.href = 'http://[::1]';
// Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview)
// IE/Edge don't wrap IPv6 addresses' hostnames in square brackets
// when parsed out of an anchor element.
var ipv6InBrackets = urlParsingNode.hostname === '[::1]';
/**
*
@@ -78,19 +72,13 @@ function urlResolve(url) {
urlParsingNode.setAttribute('href', href);
var hostname = urlParsingNode.hostname;
if (!ipv6InBrackets && hostname.indexOf(':') > -1) {
hostname = '[' + hostname + ']';
}
return {
href: urlParsingNode.href,
protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
host: urlParsingNode.host,
search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
hostname: hostname,
hostname: urlParsingNode.hostname,
port: urlParsingNode.port,
pathname: (urlParsingNode.pathname.charAt(0) === '/')
? urlParsingNode.pathname
+1 -1
View File
@@ -57,11 +57,11 @@
"applyInlineStyle": false,
"assertArg": false,
"blockKeyframeAnimations": false,
"blockTransitions": false,
"clearGeneratedClasses": false,
"concatWithSpace": false,
"extractElementNode": false,
"getDomNode": false,
"helpers": false,
"mergeAnimationDetails": false,
"mergeClasses": false,
"packageStyles": false,
+4 -4
View File
@@ -560,7 +560,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
// that if there is no transition defined then nothing will happen and this will also allow
// other transitions to be stacked on top of each other without any chopping them out.
if (isFirst && !options.skipBlocking) {
helpers.blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
}
var timings = computeTimings(node, fullClassName, cacheKey, !isStructural);
@@ -646,7 +646,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
if (flags.blockTransition || flags.blockKeyframeAnimation) {
applyBlocking(maxDuration);
} else if (!options.skipBlocking) {
helpers.blockTransitions(node, false);
blockTransitions(node, false);
}
// TODO(matsko): for 1.5 change this code to have an animator object for better debugging
@@ -699,7 +699,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
}
blockKeyframeAnimations(node, false);
helpers.blockTransitions(node, false);
blockTransitions(node, false);
forEach(temporaryStyles, function(entry) {
// There is only one way to remove inline style properties entirely from elements.
@@ -750,7 +750,7 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro
function applyBlocking(duration) {
if (flags.blockTransition) {
helpers.blockTransitions(node, duration);
blockTransitions(node, duration);
}
if (flags.blockKeyframeAnimation) {
+3 -2
View File
@@ -113,6 +113,8 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
// TODO(matsko): document the signature in a better way
return function(element, event, options) {
var node = getDomNode(element);
options = prepareAnimationOptions(options);
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
@@ -184,9 +186,8 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
forEach(groupedAnimations, function(animationEntry) {
var element = animationEntry.from ? animationEntry.from.element : animationEntry.element;
var extraClasses = options.addClass;
extraClasses = (extraClasses ? (extraClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
var cacheKey = $$animateCache.cacheKey(element[0], animationEntry.event, extraClasses, options.removeClass);
var cacheKey = $$animateCache.cacheKey(node, event, extraClasses, options.removeClass);
toBeSortedAnimations.push({
element: element,
+1 -2
View File
@@ -92,8 +92,7 @@ var ngAnimateSwapDirective = ['$animate', function($animate) {
restrict: 'A',
transclude: 'element',
terminal: true,
priority: 550, // We use 550 here to ensure that the directive is caught before others,
// but after `ngIf` (at priority 600).
priority: 600, // we use 600 here to ensure that the directive is caught before others
link: function(scope, $element, attrs, ctrl, $transclude) {
var previousElement, previousScope;
scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
+9 -11
View File
@@ -329,6 +329,15 @@ function clearGeneratedClasses(element, options) {
}
}
function blockTransitions(node, duration) {
// we use a negative delay value since it performs blocking
// yet it doesn't kill any existing transitions running on the
// same element which makes this safe for class-based animations
var value = duration ? '-' + duration + 's' : '';
applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
return [TRANSITION_DELAY_PROP, value];
}
function blockKeyframeAnimations(node, applyBlock) {
var value = applyBlock ? 'paused' : '';
var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
@@ -347,14 +356,3 @@ function concatWithSpace(a,b) {
if (!b) return a;
return a + ' ' + b;
}
var helpers = {
blockTransitions: function(node, duration) {
// we use a negative delay value since it performs blocking
// yet it doesn't kill any existing transitions running on the
// same element which makes this safe for class-based animations
var value = duration ? '-' + duration + 's' : '';
applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
return [TRANSITION_DELAY_PROP, value];
}
};
+1 -1
View File
@@ -390,7 +390,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
if (keyCode === 13 || keyCode === 32) {
// If the event is triggered on a non-interactive element ...
if (nodeBlackList.indexOf(event.target.nodeName) === -1 && !event.target.isContentEditable) {
if (nodeBlackList.indexOf(event.target.nodeName) === -1) {
// ... prevent the default browser behavior (e.g. scrolling when pressing spacebar)
// See https://github.com/angular/angular.js/issues/16664
event.preventDefault();
+4 -4
View File
@@ -215,10 +215,10 @@ var noop;
var toJson;
var $$stringify;
var ngModule = window['angular']['module']('ngMessageFormat', ['ng']);
ngModule['info']({ 'angularVersion': '"NG_VERSION_FULL"' });
ngModule['factory']('$$messageFormat', $$MessageFormatFactory);
ngModule['config'](['$provide', function($provide) {
var module = window['angular']['module']('ngMessageFormat', ['ng']);
module['info']({ 'angularVersion': '"NG_VERSION_FULL"' });
module['factory']('$$messageFormat', $$MessageFormatFactory);
module['config'](['$provide', function($provide) {
$interpolateMinErr = window['angular']['$interpolateMinErr'];
isFunction = window['angular']['isFunction'];
noop = window['angular']['noop'];
+94 -114
View File
@@ -1619,12 +1619,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
/**
* @ngdoc method
* @name $httpBackend#matchLatestDefinitionEnabled
* @name $httpBackend#matchLatestDefinition
* @description
* This method can be used to change which mocked responses `$httpBackend` returns, when defining
* them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
* By default, `$httpBackend` returns the first definition that matches. When setting
* `$http.matchLatestDefinitionEnabled(true)`, it will use the last response that matches, i.e. the
* `$http.matchLatestDefinition(true)`, it will use the last response that matches, i.e. the
* one that was added last.
*
* ```js
@@ -1632,7 +1632,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* hb.when('GET', '/url1').respond(201, 'another', {});
* hb('GET', '/url1'); // receives "content"
*
* $http.matchLatestDefinitionEnabled(true)
* $http.matchLatestDefinition(true)
* hb('GET', '/url1'); // receives "another"
*
* hb.when('GET', '/url1').respond(201, 'onemore', {});
@@ -1641,7 +1641,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
*
* This is useful if a you have a default response that is overriden inside specific tests.
*
* Note that different from config methods on providers, `matchLatestDefinitionEnabled()` can be changed
* Note that different from config methods on providers, `matchLatestDefinition()` can be changed
* even when the application is already running.
*
* @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
@@ -1650,7 +1650,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* as a getter
*/
$httpBackend.matchLatestDefinitionEnabled = function(value) {
if (angular.isDefined(value)) {
if (isDefined(value)) {
matchLatestDefinition = value;
return this;
} else {
@@ -1771,8 +1771,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* See {@link ngMock.$httpBackend#when `when`} for more info.
*/
$httpBackend.whenRoute = function(method, url) {
var parsed = parseRouteUrl(url);
return $httpBackend.when(method, parsed.regexp, undefined, undefined, parsed.keys);
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true});
return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys);
};
/**
@@ -1955,8 +1955,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* See {@link ngMock.$httpBackend#expect `expect`} for more info.
*/
$httpBackend.expectRoute = function(method, url) {
var parsed = parseRouteUrl(url);
return $httpBackend.expect(method, parsed.regexp, undefined, undefined, parsed.keys);
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true});
return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys);
};
@@ -2084,12 +2084,6 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
};
});
}
function parseRouteUrl(url) {
var strippedUrl = stripQueryAndHash(url);
var parseOptions = {caseInsensitiveMatch: true, ignoreTrailingSlashes: true};
return routeToRegExp(strippedUrl, parseOptions);
}
}
function assertArgDefined(args, index, name) {
@@ -2098,124 +2092,110 @@ function assertArgDefined(args, index, name) {
}
}
function stripQueryAndHash(url) {
return url.replace(/[?#].*$/, '');
}
function MockHttpExpectation(expectedMethod, expectedUrl, expectedData, expectedHeaders,
expectedKeys) {
function MockHttpExpectation(method, url, data, headers, keys) {
this.data = expectedData;
this.headers = expectedHeaders;
function getUrlParams(u) {
var params = u.slice(u.indexOf('?') + 1).split('&');
return params.sort();
}
this.match = function(method, url, data, headers) {
if (expectedMethod !== method) return false;
if (!this.matchUrl(url)) return false;
if (angular.isDefined(data) && !this.matchData(data)) return false;
if (angular.isDefined(headers) && !this.matchHeaders(headers)) return false;
function compareUrl(u) {
return (url.slice(0, url.indexOf('?')) === u.slice(0, u.indexOf('?')) &&
getUrlParams(url).join() === getUrlParams(u).join());
}
this.data = data;
this.headers = headers;
this.match = function(m, u, d, h) {
if (method !== m) return false;
if (!this.matchUrl(u)) return false;
if (angular.isDefined(d) && !this.matchData(d)) return false;
if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
return true;
};
this.matchUrl = function(url) {
if (!expectedUrl) return true;
if (angular.isFunction(expectedUrl.test)) return expectedUrl.test(url);
if (angular.isFunction(expectedUrl)) return expectedUrl(url);
return (expectedUrl === url || compareUrlWithQuery(url));
this.matchUrl = function(u) {
if (!url) return true;
if (angular.isFunction(url.test)) return url.test(u);
if (angular.isFunction(url)) return url(u);
return (url === u || compareUrl(u));
};
this.matchHeaders = function(headers) {
if (angular.isUndefined(expectedHeaders)) return true;
if (angular.isFunction(expectedHeaders)) return expectedHeaders(headers);
return angular.equals(expectedHeaders, headers);
this.matchHeaders = function(h) {
if (angular.isUndefined(headers)) return true;
if (angular.isFunction(headers)) return headers(h);
return angular.equals(headers, h);
};
this.matchData = function(data) {
if (angular.isUndefined(expectedData)) return true;
if (expectedData && angular.isFunction(expectedData.test)) return expectedData.test(data);
if (expectedData && angular.isFunction(expectedData)) return expectedData(data);
if (expectedData && !angular.isString(expectedData)) {
return angular.equals(angular.fromJson(angular.toJson(expectedData)), angular.fromJson(data));
this.matchData = function(d) {
if (angular.isUndefined(data)) return true;
if (data && angular.isFunction(data.test)) return data.test(d);
if (data && angular.isFunction(data)) return data(d);
if (data && !angular.isString(data)) {
return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
}
// eslint-disable-next-line eqeqeq
return expectedData == data;
return data == d;
};
this.toString = function() {
return expectedMethod + ' ' + expectedUrl;
return method + ' ' + url;
};
this.params = function(url) {
var queryStr = url.indexOf('?') === -1 ? '' : url.substring(url.indexOf('?') + 1);
var strippedUrl = stripQueryAndHash(url);
this.params = function(u) {
return angular.extend(parseQuery(), pathParams());
return angular.extend(extractParamsFromQuery(queryStr), extractParamsFromPath(strippedUrl));
};
function pathParams() {
var keyObj = {};
if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj;
function compareUrlWithQuery(url) {
var urlWithQueryRe = /^([^?]*)\?(.*)$/;
var expectedMatch = urlWithQueryRe.exec(expectedUrl);
var actualMatch = urlWithQueryRe.exec(url);
return !!(expectedMatch && actualMatch) &&
(expectedMatch[1] === actualMatch[1]) &&
(normalizeQuery(expectedMatch[2]) === normalizeQuery(actualMatch[2]));
}
function normalizeQuery(queryStr) {
return queryStr.split('&').sort().join('&');
}
function extractParamsFromPath(strippedUrl) {
var keyObj = {};
if (!expectedUrl || !angular.isFunction(expectedUrl.test) ||
!expectedKeys || !expectedKeys.length) return keyObj;
var match = expectedUrl.exec(strippedUrl);
if (!match) return keyObj;
for (var i = 1, len = match.length; i < len; ++i) {
var key = expectedKeys[i - 1];
var val = match[i];
if (key && val) {
keyObj[key.name || key] = val;
}
}
return keyObj;
}
function extractParamsFromQuery(queryStr) {
var obj = {},
keyValuePairs = queryStr.split('&').
filter(angular.identity). // Ignore empty segments.
map(function(keyValue) { return keyValue.replace(/\+/g, '%20').split('='); });
angular.forEach(keyValuePairs, function(pair) {
var key = tryDecodeURIComponent(pair[0]);
if (angular.isDefined(key)) {
var val = angular.isDefined(pair[1]) ? tryDecodeURIComponent(pair[1]) : true;
if (!hasOwnProperty.call(obj, key)) {
obj[key] = val;
} else if (angular.isArray(obj[key])) {
obj[key].push(val);
} else {
obj[key] = [obj[key], val];
var m = url.exec(u);
if (!m) return keyObj;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
var val = m[i];
if (key && val) {
keyObj[key.name || key] = val;
}
}
});
return obj;
}
function tryDecodeURIComponent(value) {
try {
return decodeURIComponent(value);
} catch (e) {
// Ignore any invalid uri component
return keyObj;
}
}
function parseQuery() {
var obj = {}, key_value, key,
queryStr = u.indexOf('?') > -1
? u.substring(u.indexOf('?') + 1)
: '';
angular.forEach(queryStr.split('&'), function(keyValue) {
if (keyValue) {
key_value = keyValue.replace(/\+/g,'%20').split('=');
key = tryDecodeURIComponent(key_value[0]);
if (angular.isDefined(key)) {
var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
if (!hasOwnProperty.call(obj, key)) {
obj[key] = val;
} else if (angular.isArray(obj[key])) {
obj[key].push(val);
} else {
obj[key] = [obj[key],val];
}
}
}
});
return obj;
}
function tryDecodeURIComponent(value) {
try {
return decodeURIComponent(value);
} catch (e) {
// Ignore any invalid uri component
}
}
};
}
function createMockXhr() {
@@ -2919,13 +2899,13 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
*/
/**
* @ngdoc method
* @name $httpBackend#matchLatestDefinitionEnabled
* @name $httpBackend#matchLatestDefinition
* @module ngMockE2E
* @description
* This method can be used to change which mocked responses `$httpBackend` returns, when defining
* them with {@link ngMock.$httpBackend#when $httpBackend.when()} (and shortcut methods).
* By default, `$httpBackend` returns the first definition that matches. When setting
* `$http.matchLatestDefinitionEnabled(true)`, it will use the last response that matches, i.e. the
* `$http.matchLatestDefinition(true)`, it will use the last response that matches, i.e. the
* one that was added last.
*
* ```js
@@ -2933,7 +2913,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* hb.when('GET', '/url1').respond(201, 'another', {});
* hb('GET', '/url1'); // receives "content"
*
* $http.matchLatestDefinitionEnabled(true)
* $http.matchLatestDefinition(true)
* hb('GET', '/url1'); // receives "another"
*
* hb.when('GET', '/url1').respond(201, 'onemore', {});
@@ -2942,7 +2922,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
*
* This is useful if a you have a default response that is overriden inside specific tests.
*
* Note that different from config methods on providers, `matchLatestDefinitionEnabled()` can be changed
* Note that different from config methods on providers, `matchLatestDefinition()` can be changed
* even when the application is already running.
*
* @param {Boolean=} value value to set, either `true` or `false`. Default is `false`.
-6
View File
@@ -6,7 +6,6 @@
* @ngdoc module
* @name ngParseExt
* @packageName angular-parse-ext
*
* @description
*
* The `ngParseExt` module provides functionality to allow Unicode characters in
@@ -16,11 +15,6 @@
* to be used as an identifier in an AngularJS expression. ES6 delegates some of the identifier
* rules definition to Unicode, this module uses ES6 and Unicode 8.0 identifiers convention.
*
* <div class="alert alert-warning">
* You cannot use Unicode characters for variable names in the {@link ngRepeat} or {@link ngOptions}
* expressions (e.g. `ng-repeat="f in поля"`), because even with `ngParseExt` included, these
* special expressions are not parsed by the {@link $parse} service.
* </div>
*/
/* global angularParseExtModule: true,
+1 -2
View File
@@ -225,7 +225,6 @@ function $RouteProvider() {
}
routes[path] = angular.extend(
routeCopy,
{originalPath: path},
path && routeToRegExp(path, routeCopy)
);
@@ -236,7 +235,7 @@ function $RouteProvider() {
: path + '/';
routes[redirectPath] = angular.extend(
{originalPath: path, redirectTo: path},
{redirectTo: path},
routeToRegExp(redirectPath, routeCopy)
);
}
+9 -9
View File
@@ -3,16 +3,15 @@
/* global routeToRegExp: true */
/**
* @param {string} path - The path to parse. (It is assumed to have query and hash stripped off.)
* @param {Object} opts - Options.
* @return {Object} - An object containing an array of path parameter names (`keys`) and a regular
* expression (`regexp`) that can be used to identify a matching URL and extract the path
* parameter values.
* @param path {string} path
* @param opts {Object} options
* @return {?Object}
*
* @description
* Parses the given path, extracting path parameter names and a regular expression to match URLs.
* Normalizes the given path, returning a regular expression
* and the original path.
*
* Originally inspired by `pathRexp` in `visionmedia/express/lib/utils.js`.
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
*/
function routeToRegExp(path, opts) {
var keys = [];
@@ -22,11 +21,11 @@ function routeToRegExp(path, opts) {
.replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) {
var optional = option === '?' || option === '*?';
var star = option === '*' || option === '*?';
keys.push({name: key, optional: optional});
keys.push({ name: key, optional: optional });
slash = slash || '';
return (
(optional ? '(?:' + slash : slash + '(?:') +
(star ? '(.+?)' : '([^/]+)') +
(star ? '([^?#]+?)' : '([^/?#]+)') +
(optional ? '?)?' : ')')
);
})
@@ -37,6 +36,7 @@ function routeToRegExp(path, opts) {
}
return {
originalPath: path,
keys: keys,
regexp: new RegExp(
'^' + pattern + '(?:[?#]|$)',
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html ng-app="test">
<body ng-class="{hacked: internalFnCalled}">
<form>
<input id="input1" type="hidden" value="{{value}}" />
<input id="input2" type="hidden" ng-value="value" />
<textarea ng-model="value"></textarea>
</form>
<script src="angular.js"></script>
<script src="script.js"></script>
</body>
</html>
-11
View File
@@ -1,11 +0,0 @@
'use strict';
angular
.module('test', [])
.run(function($rootScope) {
$rootScope.internalFnCalled = false;
$rootScope.internalFn = function() {
$rootScope.internalFnCalled = true;
};
});
+1 -1
View File
@@ -7,7 +7,7 @@ describe('SCE URL policy when base tags are present', function() {
it('allows the page URL (location.href)', function() {
expectToBeTrusted(browser.getCurrentUrl(), true);
expectToBeTrusted(browser.getLocationAbsUrl(), true);
});
it('blocks off-origin URLs', function() {
-68
View File
@@ -14,72 +14,4 @@ describe('hidden thingy', function() {
var expectedValue = browser.params.browser === 'safari' ? '{{ 7 * 6 }}' : '';
expect(element(by.css('input')).getAttribute('value')).toEqual(expectedValue);
});
it('should prevent browser autofill on browser.refresh', function() {
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');
element(by.css('textarea')).sendKeys('{{ internalFn() }}');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');
browser.refresh();
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
it('should prevent browser autofill on location.reload', function() {
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');
element(by.css('textarea')).sendKeys('{{ internalFn() }}');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');
browser.driver.executeScript('location.reload()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
it('should prevent browser autofill on history.back', function() {
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');
element(by.css('textarea')).sendKeys('{{ internalFn() }}');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');
loadFixture('sample');
browser.driver.executeScript('history.back()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
it('should prevent browser autofill on history.forward', function() {
loadFixture('sample');
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');
element(by.css('textarea')).sendKeys('{{ internalFn() }}');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');
browser.driver.executeScript('history.back()');
browser.driver.executeScript('history.forward()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
});
-22
View File
@@ -67,7 +67,6 @@ afterEach(function() {
} else {
dump('LEAK', key, angular.toJson(value));
}
delete expando.data[key];
});
});
if (count) {
@@ -313,28 +312,7 @@ window.dump = function() {
function generateInputCompilerHelper(helper) {
beforeEach(function() {
helper.validationCounter = {};
module(function($compileProvider) {
$compileProvider.directive('validationSpy', function() {
return {
priority: 1,
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var validationName = attrs.validationSpy;
var originalValidator = ctrl.$validators[validationName];
helper.validationCounter[validationName] = 0;
ctrl.$validators[validationName] = function(modelValue, viewValue) {
helper.validationCounter[validationName]++;
return originalValidator(modelValue, viewValue);
};
}
};
});
$compileProvider.directive('attrCapture', function() {
return function(scope, element, $attrs) {
helper.attrs = $attrs;
+25 -93
View File
@@ -11,9 +11,8 @@ function MockWindow(options) {
}
var events = {};
var timeouts = this.timeouts = [];
var locationHref = window.document.createElement('a');
var committedHref = window.document.createElement('a');
locationHref.href = committedHref.href = 'http://server/';
var locationHref = 'http://server/';
var committedHref = 'http://server/';
var mockWindow = this;
var msie = options.msie;
var ieState;
@@ -61,28 +60,28 @@ function MockWindow(options) {
this.location = {
get href() {
return committedHref.href;
return committedHref;
},
set href(value) {
locationHref.href = value;
locationHref = value;
mockWindow.history.state = null;
historyEntriesLength++;
if (!options.updateAsync) this.flushHref();
},
get hash() {
return getHash(committedHref.href);
return getHash(committedHref);
},
set hash(value) {
locationHref.href = replaceHash(locationHref.href, value);
locationHref = replaceHash(locationHref, value);
if (!options.updateAsync) this.flushHref();
},
replace: function(url) {
locationHref.href = url;
locationHref = url;
mockWindow.history.state = null;
if (!options.updateAsync) this.flushHref();
},
flushHref: function() {
committedHref.href = locationHref.href;
committedHref = locationHref;
}
};
@@ -92,13 +91,13 @@ function MockWindow(options) {
historyEntriesLength++;
},
replaceState: function(state, title, url) {
locationHref.href = url;
if (!options.updateAsync) committedHref.href = locationHref.href;
locationHref = url;
if (!options.updateAsync) committedHref = locationHref;
mockWindow.history.state = copy(state);
if (!options.updateAsync) this.flushHref();
},
flushHref: function() {
committedHref.href = locationHref.href;
committedHref = locationHref;
}
};
// IE 10-11 deserialize history.state on each read making subsequent reads
@@ -399,18 +398,18 @@ describe('browser', function() {
it('should return current location.href', function() {
fakeWindow.location.href = 'http://test.com';
expect(browser.url()).toEqual('http://test.com/');
expect(browser.url()).toEqual('http://test.com');
fakeWindow.location.href = 'https://another.com';
expect(browser.url()).toEqual('https://another.com/');
expect(browser.url()).toEqual('https://another.com');
});
it('should strip an empty hash fragment', function() {
fakeWindow.location.href = 'http://test.com/#';
expect(browser.url()).toEqual('http://test.com/');
fakeWindow.location.href = 'http://test.com#';
expect(browser.url()).toEqual('http://test.com');
fakeWindow.location.href = 'https://another.com/#foo';
expect(browser.url()).toEqual('https://another.com/#foo');
fakeWindow.location.href = 'https://another.com#foo';
expect(browser.url()).toEqual('https://another.com#foo');
});
it('should use history.pushState when available', function() {
@@ -418,7 +417,7 @@ describe('browser', function() {
browser.url('http://new.org');
expect(pushState).toHaveBeenCalledOnce();
expect(pushState.calls.argsFor(0)[2]).toEqual('http://new.org/');
expect(pushState.calls.argsFor(0)[2]).toEqual('http://new.org');
expect(replaceState).not.toHaveBeenCalled();
expect(locationReplace).not.toHaveBeenCalled();
@@ -430,7 +429,7 @@ describe('browser', function() {
browser.url('http://new.org', true);
expect(replaceState).toHaveBeenCalledOnce();
expect(replaceState.calls.argsFor(0)[2]).toEqual('http://new.org/');
expect(replaceState.calls.argsFor(0)[2]).toEqual('http://new.org');
expect(pushState).not.toHaveBeenCalled();
expect(locationReplace).not.toHaveBeenCalled();
@@ -441,7 +440,7 @@ describe('browser', function() {
sniffer.history = false;
browser.url('http://new.org');
expect(fakeWindow.location.href).toEqual('http://new.org/');
expect(fakeWindow.location.href).toEqual('http://new.org');
expect(pushState).not.toHaveBeenCalled();
expect(replaceState).not.toHaveBeenCalled();
@@ -474,7 +473,7 @@ describe('browser', function() {
sniffer.history = false;
browser.url('http://new.org', true);
expect(locationReplace).toHaveBeenCalledWith('http://new.org/');
expect(locationReplace).toHaveBeenCalledWith('http://new.org');
expect(pushState).not.toHaveBeenCalled();
expect(replaceState).not.toHaveBeenCalled();
@@ -508,9 +507,9 @@ describe('browser', function() {
it('should not set URL when the URL is already set', function() {
var current = fakeWindow.location.href;
sniffer.history = false;
fakeWindow.location.href = 'http://dontchange/';
fakeWindow.location.href = 'dontchange';
browser.url(current);
expect(fakeWindow.location.href).toBe('http://dontchange/');
expect(fakeWindow.location.href).toBe('dontchange');
});
it('should not read out location.href if a reload was triggered but still allow to change the url', function() {
@@ -689,73 +688,6 @@ describe('browser', function() {
expect(replaceState).not.toHaveBeenCalled();
expect(locationReplace).not.toHaveBeenCalled();
});
it('should not do pushState with a URL using relative protocol', function() {
browser.url('http://server/');
pushState.calls.reset();
replaceState.calls.reset();
locationReplace.calls.reset();
browser.url('//server');
expect(pushState).not.toHaveBeenCalled();
expect(replaceState).not.toHaveBeenCalled();
expect(locationReplace).not.toHaveBeenCalled();
});
it('should not do pushState with a URL only adding a trailing slash after domain', function() {
// A domain without a trailing /
browser.url('http://server');
pushState.calls.reset();
replaceState.calls.reset();
locationReplace.calls.reset();
// A domain from something such as window.location.href with a trailing slash
browser.url('http://server/');
expect(pushState).not.toHaveBeenCalled();
expect(replaceState).not.toHaveBeenCalled();
expect(locationReplace).not.toHaveBeenCalled();
});
it('should not do pushState with a URL only removing a trailing slash after domain', function() {
// A domain from something such as window.location.href with a trailing slash
browser.url('http://server/');
pushState.calls.reset();
replaceState.calls.reset();
locationReplace.calls.reset();
// A domain without a trailing /
browser.url('http://server');
expect(pushState).not.toHaveBeenCalled();
expect(replaceState).not.toHaveBeenCalled();
expect(locationReplace).not.toHaveBeenCalled();
});
it('should do pushState with a URL only adding a trailing slash after the path', function() {
browser.url('http://server/foo');
pushState.calls.reset();
replaceState.calls.reset();
locationReplace.calls.reset();
browser.url('http://server/foo/');
expect(pushState).toHaveBeenCalledOnce();
expect(fakeWindow.location.href).toEqual('http://server/foo/');
});
it('should do pushState with a URL only removing a trailing slash after the path', function() {
browser.url('http://server/foo/');
pushState.calls.reset();
replaceState.calls.reset();
locationReplace.calls.reset();
browser.url('http://server/foo');
expect(pushState).toHaveBeenCalledOnce();
expect(fakeWindow.location.href).toEqual('http://server/foo');
});
};
}
});
@@ -880,7 +812,7 @@ describe('browser', function() {
it('should not fire urlChange if changed by browser.url method', function() {
sniffer.history = false;
browser.onUrlChange(callback);
browser.url('http://new.com/');
browser.url('http://new.com');
fakeWindow.fire('hashchange');
expect(callback).not.toHaveBeenCalled();
@@ -1160,7 +1092,7 @@ describe('browser', function() {
it('should not interfere with legacy browser url replace behavior', function() {
inject(function($rootScope) {
var current = fakeWindow.location.href;
var newUrl = 'http://notyet/';
var newUrl = 'notyet';
sniffer.history = false;
expect(historyEntriesLength).toBe(1);
browser.url(newUrl, true);
+7 -93
View File
@@ -3577,15 +3577,6 @@ describe('$compile', function() {
})
);
it('should support non-interpolated `src` and `data-src` on the same element',
inject(function($rootScope, $compile) {
var element = $compile('<img src="abc" data-src="123">')($rootScope);
expect(element.attr('src')).toEqual('abc');
expect(element.attr('data-src')).toEqual('123');
$rootScope.$digest();
expect(element.attr('src')).toEqual('abc');
expect(element.attr('data-src')).toEqual('123');
}));
it('should call observer only when the attribute value changes', function() {
module(function() {
@@ -4222,40 +4213,6 @@ describe('$compile', function() {
expect(element.attr('ng-my-attr')).toBeUndefined();
});
it('should set the value to lowercased keys for boolean attrs', function() {
attr.$set('disabled', 'value');
expect(element.attr('disabled')).toEqual('disabled');
element.removeAttr('disabled');
attr.$set('dISaBlEd', 'VaLuE');
expect(element.attr('disabled')).toEqual('disabled');
});
it('should call removeAttr for boolean attrs when value is `false`', function() {
attr.$set('disabled', 'value');
spyOn(jqLite.prototype, 'attr').and.callThrough();
spyOn(jqLite.prototype, 'removeAttr').and.callThrough();
attr.$set('disabled', false);
expect(element.attr).not.toHaveBeenCalled();
expect(element.removeAttr).toHaveBeenCalledWith('disabled');
expect(element.attr('disabled')).toEqual(undefined);
attr.$set('disabled', 'value');
element.attr.calls.reset();
element.removeAttr.calls.reset();
attr.$set('dISaBlEd', false);
expect(element.attr).not.toHaveBeenCalled();
expect(element.removeAttr).toHaveBeenCalledWith('disabled');
expect(element.attr('disabled')).toEqual(undefined);
});
it('should not set DOM element attr if writeAttr false', function() {
attr.$set('test', 'value', false);
@@ -11746,37 +11703,37 @@ describe('$compile', function() {
// All interpolations are disallowed.
$rootScope.onClickJs = '';
expect(function() {
$compile('<button onclick="{{onClickJs}}"></button>');
$compile('<button onclick="{{onClickJs}}"></script>');
}).toThrowMinErr(
'$compile', 'nodomevents', 'Interpolations for HTML DOM event attributes are disallowed');
expect(function() {
$compile('<button ONCLICK="{{onClickJs}}"></button>');
$compile('<button ONCLICK="{{onClickJs}}"></script>');
}).toThrowMinErr(
'$compile', 'nodomevents', 'Interpolations for HTML DOM event attributes are disallowed');
expect(function() {
$compile('<button ng-attr-onclick="{{onClickJs}}"></button>');
$compile('<button ng-attr-onclick="{{onClickJs}}"></script>');
}).toThrowMinErr(
'$compile', 'nodomevents', 'Interpolations for HTML DOM event attributes are disallowed');
expect(function() {
$compile('<button ng-attr-ONCLICK="{{onClickJs}}"></button>');
$compile('<button ng-attr-ONCLICK="{{onClickJs}}"></script>');
}).toThrowMinErr(
'$compile', 'nodomevents', 'Interpolations for HTML DOM event attributes are disallowed');
}));
it('should pass through arbitrary values on onXYZ event attributes that contain a hyphen', inject(function($compile, $rootScope) {
element = $compile('<button on-click="{{onClickJs}}"></button>')($rootScope);
element = $compile('<button on-click="{{onClickJs}}"></script>')($rootScope);
$rootScope.onClickJs = 'javascript:doSomething()';
$rootScope.$apply();
expect(element.attr('on-click')).toEqual('javascript:doSomething()');
}));
it('should pass through arbitrary values on "on" and "data-on" attributes', inject(function($compile, $rootScope) {
element = $compile('<button data-on="{{dataOnVar}}"></button>')($rootScope);
element = $compile('<button data-on="{{dataOnVar}}"></script>')($rootScope);
$rootScope.dataOnVar = 'data-on text';
$rootScope.$apply();
expect(element.attr('data-on')).toEqual('data-on text');
element = $compile('<button on="{{onVar}}"></button>')($rootScope);
element = $compile('<button on="{{onVar}}"></script>')($rootScope);
$rootScope.onVar = 'on text';
$rootScope.$apply();
expect(element.attr('on')).toEqual('on text');
@@ -12127,49 +12084,6 @@ describe('$compile', function() {
expect(element.attr('test6')).toBe('Misko');
}));
describe('with media url attributes', function() {
it('should work with interpolated ng-attr-src', inject(function() {
$rootScope.name = 'some-image.png';
element = $compile('<img ng-attr-src="{{name}}">')($rootScope);
expect(element.attr('src')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('src')).toBe('some-image.png');
$rootScope.name = 'other-image.png';
$rootScope.$digest();
expect(element.attr('src')).toBe('other-image.png');
}));
it('should work with interpolated ng-attr-data-src', inject(function() {
$rootScope.name = 'some-image.png';
element = $compile('<img ng-attr-data-src="{{name}}">')($rootScope);
expect(element.attr('data-src')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('data-src')).toBe('some-image.png');
$rootScope.name = 'other-image.png';
$rootScope.$digest();
expect(element.attr('data-src')).toBe('other-image.png');
}));
it('should work alongside constant [src]-attribute and [ng-attr-data-src] attributes', inject(function() {
$rootScope.name = 'some-image.png';
element = $compile('<img src="constant.png" ng-attr-data-src="{{name}}">')($rootScope);
expect(element.attr('data-src')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('src')).toBe('constant.png');
expect(element.attr('data-src')).toBe('some-image.png');
$rootScope.name = 'other-image.png';
$rootScope.$digest();
expect(element.attr('src')).toBe('constant.png');
expect(element.attr('data-src')).toBe('other-image.png');
}));
});
describe('when an attribute has a dash-separated name', function() {
it('should work with different prefixes', inject(function() {
$rootScope.name = 'JamieMason';
+2 -303
View File
@@ -839,22 +839,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="month" ng-model="value" validation-spy="min" min="{{ minVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="month" ng-model="value" validation-spy="min" ng-min="minVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
});
describe('max', function() {
@@ -914,22 +898,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="month" ng-model="value" validation-spy="max" max="{{ maxVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="month" ng-model="value" validation-spy="max" ng-max="maxVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
});
});
@@ -1146,22 +1114,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="week" ng-model="value" validation-spy="min" min="{{ minVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="week" ng-model="value" validation-spy="min" ng-min="minVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
});
describe('max', function() {
@@ -1224,22 +1176,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="week" ng-model="value" validation-spy="max" max="{{ maxVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="week" ng-model="value" validation-spy="max" ng-max="maxVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
});
});
@@ -1570,23 +1506,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="datetime-local" ng-model="value" validation-spy="min" min="{{ minVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="datetime-local" ng-model="value" validation-spy="min" ng-min="minVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
});
describe('max', function() {
@@ -1646,22 +1565,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="datetime-local" ng-model="value" validation-spy="max" max="{{ maxVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="datetime-local" ng-model="value" validation-spy="max" ng-max="maxVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
});
@@ -2069,22 +1972,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="time" ng-model="value" validation-spy="min" min="{{ minVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="time" ng-model="value" validation-spy="min" ng-min="minVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
});
describe('max', function() {
@@ -2132,22 +2019,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="time" ng-model="value" validation-spy="max" max="{{ maxVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="time" ng-model="value" validation-spy="max" ng-max="maxVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
});
@@ -2490,26 +2361,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.min).toBeFalsy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.minVal = '2000-01-01';
$rootScope.value = new Date(2010, 1, 1, 0, 0, 0);
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="date" ng-model="value" validation-spy="min" min="{{ minVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="date" ng-model="value" validation-spy="min" ng-min="minVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
});
describe('max', function() {
@@ -2577,25 +2428,6 @@ describe('input', function() {
expect($rootScope.form.alias.$error.max).toBeFalsy();
expect($rootScope.form.alias.$valid).toBeTruthy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.maxVal = '2000-01-01';
$rootScope.value = new Date(2020, 1, 1, 0, 0, 0);
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="date" ng-model="value" validation-spy="max" max="{{ maxVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="date" ng-model="value" validation-spy="max" ng-max="maxVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
});
@@ -3231,18 +3063,6 @@ describe('input', function() {
$rootScope.$digest();
expect(inputElm).toBeValid();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.value = 5;
$rootScope.minVal = 3;
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="number" ng-model="value" validation-spy="min" min="{{ minVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
});
describe('ngMin', function() {
@@ -3311,17 +3131,6 @@ describe('input', function() {
$rootScope.$digest();
expect(inputElm).toBeValid();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.value = 5;
$rootScope.minVal = 3;
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="number" ng-model="value" validation-spy="min" ng-min="minVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
});
@@ -3391,18 +3200,6 @@ describe('input', function() {
$rootScope.$digest();
expect(inputElm).toBeValid();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.value = 5;
$rootScope.maxVal = 3;
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="number" ng-model="value" validation-spy="max" name="alias" max="{{ maxVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
});
describe('ngMax', function() {
@@ -3471,17 +3268,6 @@ describe('input', function() {
$rootScope.$digest();
expect(inputElm).toBeValid();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.value = 5;
$rootScope.maxVal = 3;
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="number" ng-model="value" validation-spy="max" ng-max="maxVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
});
@@ -3578,7 +3364,7 @@ describe('input', function() {
expect(inputElm.val()).toBe('10');
expect(inputElm).toBeInvalid();
expect(ngModel.$error.step).toBe(true);
expect($rootScope.value).toBe(10); // an initially invalid value should not be changed
expect($rootScope.value).toBeUndefined();
helper.changeInputValueTo('15');
expect(inputElm).toBeValid();
@@ -3658,17 +3444,6 @@ describe('input', function() {
expect($rootScope.value).toBe(1.16);
}
);
it('should validate only once after compilation inside ngRepeat', function() {
$rootScope.step = 10;
$rootScope.value = 20;
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="number" ng-model="value" name="alias" ' + attrHtml + ' validation-spy="step" />' +
'</div>');
expect(helper.validationCounter.step).toBe(1);
});
});
});
@@ -3710,16 +3485,6 @@ describe('input', function() {
expect(inputElm).toBeValid();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.value = 'text';
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input ng-model="value" validation-spy="required" required />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.required).toBe(1);
});
});
describe('ngRequired', function() {
@@ -3769,17 +3534,6 @@ describe('input', function() {
expect($rootScope.value).toBeUndefined();
expect($rootScope.form.numberInput.$error.required).toBeFalsy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.value = 'text';
$rootScope.isRequired = true;
var inputElm = helper.compileInput('<div ng-repeat="input in [0]">' +
'<input ng-model="value" validation-spy="required" ng-required="isRequired" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.required).toBe(1);
});
});
describe('when the ngRequired expression initially evaluates to false', function() {
@@ -4094,17 +3848,6 @@ describe('input', function() {
expect(inputElm.val()).toBe('20');
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.minVal = 5;
$rootScope.value = 10;
helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="range" ng-model="value" validation-spy="min" min="minVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
} else {
// input[type=range] will become type=text in browsers that don't support it
@@ -4183,16 +3926,6 @@ describe('input', function() {
expect(inputElm.val()).toBe('15');
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.minVal = 5;
$rootScope.value = 10;
helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="range" ng-model="value" validation-spy="min" min="minVal" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.min).toBe(1);
});
}
});
@@ -4273,17 +4006,6 @@ describe('input', function() {
expect(inputElm.val()).toBe('0');
});
it('should only validate once after compilation when inside ngRepeat and the value is valid', function() {
$rootScope.maxVal = 5;
$rootScope.value = 5;
helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="range" ng-model="value" validation-spy="max" max="{{ maxVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
} else {
it('should validate if "range" is not implemented', function() {
var inputElm = helper.compileInput('<input type="range" ng-model="value" name="alias" max="10" />');
@@ -4359,17 +4081,6 @@ describe('input', function() {
expect(scope.value).toBe(5);
expect(inputElm.val()).toBe('5');
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.maxVal = 5;
$rootScope.value = 10;
helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="range" ng-model="value" validation-spy="max" max="{{ maxVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.max).toBe(1);
});
}
});
@@ -4472,18 +4183,6 @@ describe('input', function() {
expect(scope.value).toBe(10);
expect(scope.form.alias.$error.step).toBeFalsy();
});
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.stepVal = 5;
$rootScope.value = 10;
helper.compileInput('<div ng-repeat="input in [0]">' +
'<input type="range" ng-model="value" validation-spy="step" step="{{ stepVal }}" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.step).toBe(1);
});
} else {
it('should validate if "range" is not implemented', function() {
@@ -4570,7 +4269,7 @@ describe('input', function() {
expect(inputElm.val()).toBe('10');
expect(inputElm).toBeInvalid();
expect(ngModel.$error.step).toBe(true);
expect($rootScope.value).toBe(10);
expect($rootScope.value).toBeUndefined();
helper.changeInputValueTo('15');
expect(inputElm).toBeValid();
-6
View File
@@ -88,12 +88,6 @@ describe('ngClass', function() {
expect(element.hasClass('AnotB')).toBeFalsy();
}));
it('should not break when passed non-string/array/object, truthy values', inject(function($rootScope, $compile) {
element = $compile('<div ng-class="42"></div>')($rootScope);
$rootScope.$digest();
expect(element.hasClass('42')).toBeTruthy();
}));
it('should support adding multiple classes via an array mixed with conditionally via a map', inject(function($rootScope, $compile) {
element = $compile('<div class="existing" ng-class="[\'A\', {\'B\': condition}]"></div>')($rootScope);
$rootScope.$digest();
+2 -2
View File
@@ -68,8 +68,8 @@ describe('ngHref', function() {
}));
// Support: IE 9-11 only, Edge 12-17
if (msie || /\bEdge\/1[2-7]\.[\d.]+\b/.test(window.navigator.userAgent)) {
// Support: IE 9-11 only, Edge 12-15+
if (msie || /\bEdge\/[\d.]+\b/.test(window.navigator.userAgent)) {
// IE/Edge fail when setting a href to a URL containing a % that isn't a valid escape sequence
// See https://github.com/angular/angular.js/issues/13388
it('should throw error if ng-href contains a non-escaped percent symbol', inject(function($rootScope, $compile) {
+1
View File
@@ -863,6 +863,7 @@ describe('ngModel', function() {
});
});
describe('view -> model update', function() {
it('should always perform validations using the parsed model value', function() {
-37
View File
@@ -400,43 +400,6 @@ describe('ngRepeat', function() {
expect(element.find('input')[1].checked).toBe(true);
expect(element.find('input')[2].checked).toBe(true);
}));
it('should invoke track by with correct locals', function() {
scope.trackBy = jasmine.createSpy().and.callFake(function(k, v) {
return [k, v].join('');
});
element = $compile(
'<ul>' +
'<li ng-repeat="(k, v) in [1, 2] track by trackBy(k, v)"></li>' +
'</ul>')(scope);
scope.$digest();
expect(scope.trackBy).toHaveBeenCalledTimes(2);
expect(scope.trackBy.calls.argsFor(0)).toEqual([0, 1]);
expect(scope.trackBy.calls.argsFor(1)).toEqual([1, 2]);
});
// https://github.com/angular/angular.js/issues/16776
it('should invoke nested track by with correct locals', function() {
scope.trackBy = jasmine.createSpy().and.callFake(function(k1, v1, k2, v2) {
return [k1, v1, k2, v2].join('');
});
element = $compile(
'<ul>' +
'<li ng-repeat="(k1, v1) in [1, 2]">' +
'<div ng-repeat="(k2, v2) in [3, 4] track by trackBy(k1, v1, k2, v2)"></div>' +
'</li>' +
'</ul>')(scope);
scope.$digest();
expect(scope.trackBy).toHaveBeenCalledTimes(4);
expect(scope.trackBy.calls.argsFor(0)).toEqual([0, 1, 0, 3]);
expect(scope.trackBy.calls.argsFor(1)).toEqual([0, 1, 1, 4]);
expect(scope.trackBy.calls.argsFor(2)).toEqual([1, 2, 0, 3]);
expect(scope.trackBy.calls.argsFor(3)).toEqual([1, 2, 1, 4]);
});
});
describe('alias as', function() {
-14
View File
@@ -78,20 +78,6 @@ describe('ngSrc', function() {
expect(element.prop('src')).toEqual('http://somewhere/abc');
}));
}
it('should work with `src` attribute on the same element', inject(function($rootScope, $compile) {
$rootScope.imageUrl = 'dynamic';
element = $compile('<img ng-src="{{imageUrl}}" src="static">')($rootScope);
expect(element.attr('src')).toBe('static');
$rootScope.$digest();
expect(element.attr('src')).toBe('dynamic');
dealoc(element);
element = $compile('<img src="static" ng-src="{{imageUrl}}">')($rootScope);
expect(element.attr('src')).toBe('static');
$rootScope.$digest();
expect(element.attr('src')).toBe('dynamic');
}));
});
describe('iframe[ng-src]', function() {
-33
View File
@@ -120,38 +120,5 @@ describe('ngStyle', function() {
expect(element.css(preCompStyle)).not.toBe('88px');
expect(element.css(postCompStyle)).not.toBe('99px');
});
it('should clear style when the new model is null', function() {
scope.styleObj = {'height': '99px', 'width': '88px'};
scope.$apply();
expect(element.css(preCompStyle)).toBe('88px');
expect(element.css(postCompStyle)).toBe('99px');
scope.styleObj = null;
scope.$apply();
expect(element.css(preCompStyle)).not.toBe('88px');
expect(element.css(postCompStyle)).not.toBe('99px');
});
it('should clear style when the value is undefined or null', function() {
scope.styleObj = {'height': '99px', 'width': '88px'};
scope.$apply();
expect(element.css(preCompStyle)).toBe('88px');
expect(element.css(postCompStyle)).toBe('99px');
scope.styleObj = {'height': undefined, 'width': null};
scope.$apply();
expect(element.css(preCompStyle)).not.toBe('88px');
expect(element.css(postCompStyle)).not.toBe('99px');
});
it('should set style when the value is zero', function() {
scope.styleObj = {'height': '99px', 'width': '88px'};
scope.$apply();
expect(element.css(preCompStyle)).toBe('88px');
expect(element.css(postCompStyle)).toBe('99px');
scope.styleObj = {'height': 0, 'width': 0};
scope.$apply();
expect(element.css(preCompStyle)).toBe('0px');
expect(element.css(postCompStyle)).toBe('0px');
});
});
});
+1 -137
View File
@@ -230,29 +230,6 @@ describe('validators', function() {
expect(ctrl.$error.pattern).toBe(true);
expect(ctrlNg.$error.pattern).toBe(true);
}));
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.pattern = /\d{4}/;
helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" pattern="\\d{4}" validation-spy="pattern" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.pattern).toBe(1);
helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" ng-pattern="pattern" validation-spy="pattern" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.pattern).toBe(1);
});
});
@@ -335,31 +312,9 @@ describe('validators', function() {
expect(ctrl.$error.minlength).toBe(true);
expect(ctrlNg.$error.minlength).toBe(true);
}));
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.minlength = 5;
var element = helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" minlength="{{minlength}}" validation-spy="minlength" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.minlength).toBe(1);
element = helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" ng-minlength="minlength" validation-spy="minlength" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.minlength).toBe(1);
});
});
describe('maxlength', function() {
it('should invalidate values that are longer than the given maxlength', function() {
@@ -545,29 +500,6 @@ describe('validators', function() {
expect(ctrl.$error.maxlength).toBe(true);
expect(ctrlNg.$error.maxlength).toBe(true);
}));
it('should only validate once after compilation when inside ngRepeat', function() {
$rootScope.maxlength = 5;
var element = helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" maxlength="{{maxlength}}" validation-spy="maxlength" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.maxlength).toBe(1);
element = helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" ng-maxlength="maxlength" validation-spy="maxlength" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.maxlength).toBe(1);
});
});
@@ -694,73 +626,5 @@ describe('validators', function() {
expect(ctrl.$error.required).toBe(true);
expect(ctrlNg.$error.required).toBe(true);
}));
it('should override "required" when ng-required="false" is set', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="notDefined" required ng-required="false" />');
expect(inputElm).toBeValid();
});
it('should validate only once after compilation when inside ngRepeat', function() {
helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" required validation-spy="required" />' +
'</div>');
$rootScope.$digest();
expect(helper.validationCounter.required).toBe(1);
});
it('should validate only once after compilation when inside ngRepeat and ngRequired is true', function() {
$rootScope.isRequired = true;
helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" ng-required="isRequired" validation-spy="required" />' +
'</div>');
expect(helper.validationCounter.required).toBe(1);
});
it('should validate only once after compilation when inside ngRepeat and ngRequired is false', function() {
$rootScope.isRequired = false;
helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-model="value" ng-required="isRequired" validation-spy="required" />' +
'</div>');
expect(helper.validationCounter.required).toBe(1);
});
it('should validate once when inside ngRepeat, and set the "required" error when ngRequired is false by default', function() {
$rootScope.isRequired = false;
$rootScope.refs = {};
var elm = helper.compileInput(
'<div ng-repeat="input in [0]">' +
'<input type="text" ng-ref="refs.input" ng-ref-read="ngModel" ng-model="value" ng-required="isRequired" validation-spy="required" />' +
'</div>');
expect(helper.validationCounter.required).toBe(1);
expect($rootScope.refs.input.$error.required).toBeUndefined();
});
it('should validate only once when inside ngIf with required on non-input elements', inject(function($compile) {
$rootScope.value = '12';
$rootScope.refs = {};
helper.compileInput('<div ng-if="true"><span ng-model="value" ng-ref="refs.ctrl" ng-ref-read="ngModel" required validation-spy="required"></span></div>');
$rootScope.$digest();
expect(helper.validationCounter.required).toBe(1);
expect($rootScope.refs.ctrl.$error.required).not.toBe(true);
}));
});
});
-30
View File
@@ -155,34 +155,4 @@ describe('ngOn* event binding', function() {
expect(attrs.$attr.ngOnTitle).toBe('ng-on-title');
});
});
it('should correctly bind to kebab-cased event names', inject(function($compile, $rootScope) {
var element = $compile('<span ng-on-foo-bar="cb()"></span>')($rootScope);
var cb = $rootScope.cb = jasmine.createSpy('ng-on cb');
$rootScope.$digest();
element.triggerHandler('foobar');
element.triggerHandler('fooBar');
element.triggerHandler('foo_bar');
element.triggerHandler('foo:bar');
expect(cb).not.toHaveBeenCalled();
element.triggerHandler('foo-bar');
expect(cb).toHaveBeenCalled();
}));
it('should correctly bind to camelCased event names', inject(function($compile, $rootScope) {
var element = $compile('<span ng-on-foo_bar="cb()"></span>')($rootScope);
var cb = $rootScope.cb = jasmine.createSpy('ng-on cb');
$rootScope.$digest();
element.triggerHandler('foobar');
element.triggerHandler('foo-bar');
element.triggerHandler('foo_bar');
element.triggerHandler('foo:bar');
expect(cb).not.toHaveBeenCalled();
element.triggerHandler('fooBar');
expect(cb).toHaveBeenCalled();
}));
});
+2 -44
View File
@@ -49,48 +49,6 @@ describe('ngProp*', function() {
expect(element.prop('asdf')).toBe(true);
}));
// https://github.com/angular/angular.js/issues/16797
it('should support falsy property values', inject(function($rootScope, $compile) {
var element = $compile('<span ng-prop-text="myText" />')($rootScope);
// Initialize to truthy value
$rootScope.myText = 'abc';
$rootScope.$digest();
expect(element.prop('text')).toBe('abc');
// Assert various falsey values get assigned to the property
$rootScope.myText = '';
$rootScope.$digest();
expect(element.prop('text')).toBe('');
$rootScope.myText = 0;
$rootScope.$digest();
expect(element.prop('text')).toBe(0);
$rootScope.myText = false;
$rootScope.$digest();
expect(element.prop('text')).toBe(false);
$rootScope.myText = undefined;
$rootScope.$digest();
expect(element.prop('text')).toBeUndefined();
$rootScope.myText = null;
$rootScope.$digest();
expect(element.prop('text')).toBe(null);
}));
it('should directly map special properties (class)', inject(function($rootScope, $compile) {
var element = $compile('<span ng-prop-class="myText" />')($rootScope);
$rootScope.myText = 'abc';
$rootScope.$digest();
expect(element[0].class).toBe('abc');
expect(element).not.toHaveClass('abc');
}));
it('should not use jQuery .prop() to avoid jQuery propFix/hooks', inject(function($rootScope, $compile) {
var element = $compile('<span ng-prop-class="myText" />')($rootScope);
spyOn(jqLite.prototype, 'prop');
$rootScope.myText = 'abc';
$rootScope.$digest();
expect(jqLite.prototype.prop).not.toHaveBeenCalled();
}));
it('should support mixed case using underscore-separated names', inject(function($rootScope, $compile) {
var element = $compile('<span ng-prop-a_bcd_e="value" />')($rootScope);
$rootScope.value = 123;
@@ -189,11 +147,11 @@ describe('ngProp*', function() {
it('should disallow property binding to onclick', inject(function($compile, $rootScope) {
// All event prop bindings are disallowed.
expect(function() {
$compile('<button ng-prop-onclick="onClickJs"></button>');
$compile('<button ng-prop-onclick="onClickJs"></script>');
}).toThrowMinErr(
'$compile', 'nodomevents', 'Property bindings for HTML DOM event properties are disallowed');
expect(function() {
$compile('<button ng-prop-ONCLICK="onClickJs"></button>');
$compile('<button ng-prop-ONCLICK="onClickJs"></script>');
}).toThrowMinErr(
'$compile', 'nodomevents', 'Property bindings for HTML DOM event properties are disallowed');
}));
-13
View File
@@ -31,19 +31,6 @@ describe('urlUtils', function() {
var parsed = urlResolve('/');
expect(parsed.pathname).toBe('/');
});
it('should return an IPv6 hostname wrapped in brackets', function() {
// Support: IE 9-11 only, Edge 16-17 only (fixed in 18 Preview)
// IE/Edge don't wrap IPv6 addresses' hostnames in square brackets
// when parsed out of an anchor element.
var parsed = urlResolve('http://[::1]/');
expect(parsed.hostname).toBe('[::1]');
});
it('should not put the domain in brackets for the hostname field', function() {
var parsed = urlResolve('https://google.com/');
expect(parsed.hostname).toBe('google.com');
});
});
-1
View File
@@ -4,7 +4,6 @@
},
"globals": {
"getDomNode": false,
"helpers": false,
"mergeAnimationDetails": false,
"prepareAnimationOptions": false,
"applyAnimationStyles": false,
+2 -2
View File
@@ -1822,7 +1822,7 @@ describe('ngAnimate $animateCss', function() {
they('should not place a CSS transition block if options.skipBlocking is provided',
['enter', 'leave', 'move', 'addClass', 'removeClass'], function(event) {
inject(function($animateCss, $rootElement, $document) {
inject(function($animateCss, $rootElement, $document, $window) {
var element = angular.element('<div></div>');
$rootElement.append(element);
angular.element($document[0].body).append($rootElement);
@@ -1840,7 +1840,7 @@ describe('ngAnimate $animateCss', function() {
data.event = event;
}
var blockSpy = spyOn(helpers, 'blockTransitions').and.callThrough();
var blockSpy = spyOn($window, 'blockTransitions').and.callThrough();
data.skipBlocking = true;
var animator = $animateCss(element, data);
-81
View File
@@ -25,7 +25,6 @@ describe('ngAnimate integration tests', function() {
ss.destroy();
});
it('should cancel a running and started removeClass animation when a follow-up addClass animation adds the same class',
inject(function($animate, $rootScope, $$rAF, $document, $rootElement) {
@@ -363,7 +362,6 @@ describe('ngAnimate integration tests', function() {
});
});
it('should add the preparation class for an enter animation before a parent class-based animation is applied', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
@@ -399,7 +397,6 @@ describe('ngAnimate integration tests', function() {
});
});
it('should avoid adding the ng-enter-prepare method to a parent structural animation that contains child animations', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$rAF) {
@@ -471,84 +468,6 @@ describe('ngAnimate integration tests', function() {
});
});
it('should remove the prepare classes when different structural animations happen in the same digest', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$animateCache) {
element = jqLite(
// Class animation on parent element is neeeded so the child elements get the prepare class
'<div id="outer" ng-class="{blue: cond}" ng-switch="cond">' +
'<div id="default" ng-switch-default></div>' +
'<div id="truthy" ng-switch-when="true"></div>' +
'</div>'
);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
$rootScope.cond = false;
$rootScope.$digest();
$rootScope.cond = true;
$rootScope.$digest();
var parent = element;
var truthySwitch = jqLite(parent[0].querySelector('#truthy'));
var defaultSwitch = jqLite(parent[0].querySelector('#default'));
expect(parent).not.toHaveClass('blue');
expect(parent).toHaveClass('blue-add');
expect(truthySwitch).toHaveClass('ng-enter-prepare');
expect(defaultSwitch).toHaveClass('ng-leave-prepare');
$animate.flush();
expect(parent).toHaveClass('blue');
expect(parent).not.toHaveClass('blue-add');
expect(truthySwitch).not.toHaveClass('ng-enter-prepare');
expect(defaultSwitch).not.toHaveClass('ng-leave-prepare');
});
});
it('should respect the element node for caching when animations with the same type happen in the same digest', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$animateCache) {
ss.addRule('.animate.ng-enter', 'transition:2s linear all;');
element = jqLite(
'<div>' +
'<div>' +
'<div id="noanimate" ng-if="cond"></div>' +
'</div>' +
'<div>' +
'<div id="animate" class="animate" ng-if="cond"></div>' +
'</div>' +
'</div>'
);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
$rootScope.cond = true;
$rootScope.$digest();
var parent = element;
var noanimate = jqLite(parent[0].querySelector('#noanimate'));
var animate = jqLite(parent[0].querySelector('#animate'));
expect(noanimate).not.toHaveClass('ng-enter');
expect(animate).toHaveClass('ng-enter');
$animate.closeAndFlush();
expect(noanimate).not.toHaveClass('ng-enter');
expect(animate).not.toHaveClass('ng-enter');
});
});
it('should pack level elements into their own RAF flush', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
+29 -48
View File
@@ -20,7 +20,7 @@ describe('ngAnimateSwap', function() {
}));
it('should render a new container when the expression changes', function() {
it('should render a new container when the expression changes', inject(function() {
element = $compile('<div><div ng-animate-swap="exp">{{ exp }}</div></div>')($rootScope);
$rootScope.$digest();
@@ -40,9 +40,9 @@ describe('ngAnimateSwap', function() {
expect(third.textContent).toBe('super');
expect(third).not.toEqual(second);
expect(second.parentNode).toBeFalsy();
});
}));
it('should render a new container only when the expression property changes', function() {
it('should render a new container only when the expression property changes', inject(function() {
element = $compile('<div><div ng-animate-swap="exp.prop">{{ exp.value }}</div></div>')($rootScope);
$rootScope.exp = {
prop: 'hello',
@@ -66,9 +66,9 @@ describe('ngAnimateSwap', function() {
var three = element.find('div')[0];
expect(three.textContent).toBe('planet');
expect(three).not.toBe(two);
});
}));
it('should watch the expression as a collection', function() {
it('should watch the expression as a collection', inject(function() {
element = $compile('<div><div ng-animate-swap="exp">{{ exp.a }} {{ exp.b }} {{ exp.c }}</div></div>')($rootScope);
$rootScope.exp = {
a: 1,
@@ -99,24 +99,26 @@ describe('ngAnimateSwap', function() {
var four = element.find('div')[0];
expect(four.textContent.trim()).toBe('4');
expect(four).not.toEqual(three);
});
}));
they('should consider $prop as a falsy value', [false, undefined, null], function(value) {
element = $compile('<div><div ng-animate-swap="value">{{ value }}</div></div>')($rootScope);
$rootScope.value = true;
$rootScope.$digest();
inject(function() {
element = $compile('<div><div ng-animate-swap="value">{{ value }}</div></div>')($rootScope);
$rootScope.value = true;
$rootScope.$digest();
var one = element.find('div')[0];
expect(one).toBeTruthy();
var one = element.find('div')[0];
expect(one).toBeTruthy();
$rootScope.value = value;
$rootScope.$digest();
$rootScope.value = value;
$rootScope.$digest();
var two = element.find('div')[0];
expect(two).toBeFalsy();
var two = element.find('div')[0];
expect(two).toBeFalsy();
});
});
it('should consider "0" as a truthy value', function() {
it('should consider "0" as a truthy value', inject(function() {
element = $compile('<div><div ng-animate-swap="value">{{ value }}</div></div>')($rootScope);
$rootScope.$digest();
@@ -128,9 +130,9 @@ describe('ngAnimateSwap', function() {
var two = element.find('div')[0];
expect(two).toBeTruthy();
});
}));
it('should create a new (non-isolate) scope for each inserted clone', function() {
it('should create a new (non-isolate) scope for each inserted clone', inject(function() {
var parentScope = $rootScope.$new();
parentScope.foo = 'bar';
@@ -145,9 +147,9 @@ describe('ngAnimateSwap', function() {
expect(scopeTwo.foo).toBe('bar');
expect(scopeOne).not.toBe(scopeTwo);
});
}));
it('should destroy the previous scope when removing the element', function() {
it('should destroy the previous scope when removing the element', inject(function() {
element = $compile('<div><div ng-animate-swap="value">{{ value }}</div></div>')($rootScope);
$rootScope.$apply('value = 1');
@@ -164,9 +166,9 @@ describe('ngAnimateSwap', function() {
// Removing the old element (without inserting a new one).
$rootScope.$apply('value = null');
expect(scopeTwo.$$destroyed).toBe(true);
});
}));
it('should destroy the previous scope when swapping elements', function() {
it('should destroy the previous scope when swapping elements', inject(function() {
element = $compile('<div><div ng-animate-swap="value">{{ value }}</div></div>')($rootScope);
$rootScope.$apply('value = 1');
@@ -175,34 +177,13 @@ describe('ngAnimateSwap', function() {
$rootScope.$apply('value = 2');
expect(scopeOne.$$destroyed).toBe(true);
});
it('should work with `ngIf` on the same element', function() {
var tmpl = '<div><div ng-animate-swap="exp" ng-if="true">{{ exp }}</div></div>';
element = $compile(tmpl)($rootScope);
$rootScope.$digest();
var first = element.find('div')[0];
expect(first).toBeFalsy();
$rootScope.exp = 'yes';
$rootScope.$digest();
var second = element.find('div')[0];
expect(second.textContent).toBe('yes');
$rootScope.exp = 'super';
$rootScope.$digest();
var third = element.find('div')[0];
expect(third.textContent).toBe('super');
expect(third).not.toEqual(second);
expect(second.parentNode).toBeFalsy();
});
}));
describe('animations', function() {
it('should trigger a leave animation followed by an enter animation upon swap',function() {
it('should trigger a leave animation followed by an enter animation upon swap',
inject(function() {
element = $compile('<div><div ng-animate-swap="exp">{{ exp }}</div></div>')($rootScope);
$rootScope.exp = 1;
$rootScope.$digest();
@@ -227,6 +208,6 @@ describe('ngAnimateSwap', function() {
var forth = $animate.queue.shift();
expect(forth.event).toBe('leave');
expect($animate.queue.length).toBe(0);
});
}));
});
});
+20 -109
View File
@@ -954,115 +954,6 @@ describe('$aria', function() {
expect(clickEvents).toEqual(['div(true)', 'li(true)', 'div(true)', 'li(true)']);
});
it('should trigger a click in browsers that provide `event.which` instead of `event.keyCode`',
function() {
compileElement(
'<section>' +
'<div ng-click="onClick($event)"></div>' +
'<ul><li ng-click="onClick($event)"></li></ul>' +
'</section>');
var divElement = element.find('div');
var liElement = element.find('li');
divElement.triggerHandler({type: 'keydown', which: 13});
liElement.triggerHandler({type: 'keydown', which: 13});
divElement.triggerHandler({type: 'keydown', which: 32});
liElement.triggerHandler({type: 'keydown', which: 32});
expect(clickEvents).toEqual(['div(true)', 'li(true)', 'div(true)', 'li(true)']);
}
);
it('should not prevent default keyboard action if the target element has editable content',
inject(function($document) {
// Note:
// `contenteditable` is an enumarated (not a boolean) attribute (see
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable).
// We need to check the following conditions:
// - No attribute.
// - Value: ""
// - Value: "true"
// - Value: "false"
function eventFor(keyCode) {
return {bubbles: true, cancelable: true, keyCode: keyCode};
}
compileElement(
'<section>' +
// No attribute.
'<div id="no-attribute">' +
'<div ng-click="onClick($event)"></div>' +
'<ul ng-click="onClick($event)"><li></li></ul>' +
'</div>' +
// Value: ""
'<div id="value-empty">' +
'<div ng-click="onClick($event)" contenteditable=""></div>' +
'<ul ng-click="onClick($event)"><li contenteditable=""></li></ul>' +
'</div>' +
// Value: "true"
'<div id="value-true">' +
'<div ng-click="onClick($event)" contenteditable="true"></div>' +
'<ul ng-click="onClick($event)"><li contenteditable="true"></li></ul>' +
'</div>' +
// Value: "false"
'<div id="value-false">' +
'<div ng-click="onClick($event)" contenteditable="false"></div>' +
'<ul ng-click="onClick($event)"><li contenteditable="false"></li></ul>' +
'</div>' +
'</section>');
// Support: Safari 11-12+
// Attach to DOM, because otherwise Safari will not update the `isContentEditable` property
// based on the `contenteditable` attribute.
$document.find('body').append(element);
var containers = element.children();
var container;
// Using `browserTrigger()`, because it supports event bubbling.
// No attribute | Elements are not editable.
container = containers.eq(0);
browserTrigger(container.find('div'), 'keydown', eventFor(13));
browserTrigger(container.find('ul'), 'keydown', eventFor(32));
browserTrigger(container.find('li'), 'keydown', eventFor(13));
expect(clickEvents).toEqual(['div(true)', 'ul(true)', 'li(true)']);
// Value: "" | Elements are editable.
clickEvents = [];
container = containers.eq(1);
browserTrigger(container.find('div'), 'keydown', eventFor(32));
browserTrigger(container.find('ul'), 'keydown', eventFor(13));
browserTrigger(container.find('li'), 'keydown', eventFor(32));
expect(clickEvents).toEqual(['div(false)', 'ul(true)', 'li(false)']);
// Value: "true" | Elements are editable.
clickEvents = [];
container = containers.eq(2);
browserTrigger(container.find('div'), 'keydown', eventFor(13));
browserTrigger(container.find('ul'), 'keydown', eventFor(32));
browserTrigger(container.find('li'), 'keydown', eventFor(13));
expect(clickEvents).toEqual(['div(false)', 'ul(true)', 'li(false)']);
// Value: "false" | Elements are not editable.
clickEvents = [];
container = containers.eq(3);
browserTrigger(container.find('div'), 'keydown', eventFor(32));
browserTrigger(container.find('ul'), 'keydown', eventFor(13));
browserTrigger(container.find('li'), 'keydown', eventFor(32));
expect(clickEvents).toEqual(['div(true)', 'ul(true)', 'li(true)']);
})
);
they('should not prevent default keyboard action if an interactive $type element' +
'is nested inside ng-click', nodeBlackList, function(elementType) {
function createHTML(type) {
@@ -1090,6 +981,26 @@ describe('$aria', function() {
}
);
it('should trigger a click in browsers that provide `event.which` instead of `event.keyCode`',
function() {
compileElement(
'<section>' +
'<div ng-click="onClick($event)"></div>' +
'<ul><li ng-click="onClick($event)"></li></ul>' +
'</section>');
var divElement = element.find('div');
var liElement = element.find('li');
divElement.triggerHandler({type: 'keydown', which: 13});
liElement.triggerHandler({type: 'keydown', which: 13});
divElement.triggerHandler({type: 'keydown', which: 32});
liElement.triggerHandler({type: 'keydown', which: 32});
expect(clickEvents).toEqual(['div(true)', 'li(true)', 'div(true)', 'li(true)']);
}
);
they('should not bind to key events if there is existing `ng-$prop`',
['keydown', 'keypress', 'keyup'], function(eventName) {
scope.onKeyEvent = jasmine.createSpy('onKeyEvent');
-1
View File
@@ -132,7 +132,6 @@ describe('$$cookieWriter', function() {
describe('cookie options', function() {
var fakeDocument, $$cookieWriter;
var isUndefined = angular.isUndefined;
function getLastCookieAssignment(key) {
return fakeDocument[0].cookie
+7 -15
View File
@@ -1,9 +1,7 @@
'use strict';
describe('ngMock', function() {
var noop = angular.noop;
var extend = angular.extend;
describe('TzDate', function() {
@@ -1109,13 +1107,11 @@ describe('ngMock', function() {
var mock = { log: 'module' };
beforeEach(function() {
angular.module('stringRefModule', []).service('stringRef', function() {});
module({
'service': mock,
'other': { some: 'replacement'}
},
'stringRefModule',
'ngResource',
function($provide) { $provide.value('example', 'win'); }
);
});
@@ -1134,9 +1130,9 @@ describe('ngMock', function() {
});
it('should integrate with string and function', function() {
inject(function(service, stringRef, example) {
inject(function(service, $resource, example) {
expect(service).toEqual(mock);
expect(stringRef).toBeDefined();
expect($resource).toBeDefined();
expect(example).toEqual('win');
});
});
@@ -2255,7 +2251,7 @@ describe('ngMock', function() {
}
);
they('should ignore query params when matching in ' + routeShortcut + ' $prop method', methods,
function(method) {
function() {
angular.forEach([
{route: '/route1/:id', url: '/route1/Alpha', expectedParams: {id: 'Alpha'}},
{route: '/route2/:id', url: '/route2/Bravo/?', expectedParams: {id: 'Bravo'}},
@@ -2272,14 +2268,14 @@ describe('ngMock', function() {
], function(testDataEntry) {
callback.calls.reset();
var paramsSpy = jasmine.createSpy('params');
hb[routeShortcut](method, testDataEntry.route).respond(
hb[routeShortcut](this, testDataEntry.route).respond(
function(method, url, data, headers, params) {
paramsSpy(params);
// status, response, headers, statusText, xhrStatus
return [200, 'path', { 'x-header': 'foo' }, 'OK', 'complete'];
}
);
hb(method, testDataEntry.url, undefined, callback);
hb(this, testDataEntry.url, undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'path', 'x-header: foo', 'OK', 'complete');
expect(paramsSpy).toHaveBeenCalledOnceWith(testDataEntry.expectedParams);
@@ -2837,10 +2833,6 @@ describe('ngMock', function() {
describe('ngMockE2E', function() {
var noop = angular.noop;
var extend = angular.extend;
describe('$httpBackend', function() {
var hb, realHttpBackend, realHttpBackendBrowser, $http, callback;
@@ -3183,7 +3175,7 @@ describe('ngMockE2E', function() {
if (!browserSupportsCssAnimations()) return;
var element = angular.element('<div></div>');
var element = jqLite('<div></div>');
$rootElement.append(element);
// Make sure the animation has valid $animateCss options
-2
View File
@@ -1,8 +1,6 @@
'use strict';
describe('resource', function() {
var noop = angular.noop;
var extend = angular.extend;
describe('basic usage', function() {
var $resource, CreditCard, callback, $httpBackend, resourceProvider, $q;
-40
View File
@@ -77,45 +77,5 @@ describe('$routeParams', function() {
});
});
it('should correctly extract path params containing hashes and/or question marks', function() {
module(function($routeProvider) {
$routeProvider.when('/foo/:bar', {});
$routeProvider.when('/zoo/:bar/:baz/:qux', {});
});
inject(function($location, $rootScope, $routeParams) {
$location.path('/foo/bar?baz');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar?baz'});
$location.path('/foo/bar?baz=val');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar?baz=val'});
$location.path('/foo/bar#baz');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar#baz'});
$location.path('/foo/bar?baz#qux');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar?baz#qux'});
$location.path('/foo/bar?baz=val#qux');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar?baz=val#qux'});
$location.path('/foo/bar#baz?qux');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar#baz?qux'});
$location.path('/zoo/bar?p1=v1#h1/baz?p2=v2#h2/qux?p3=v3#h3');
$rootScope.$digest();
expect($routeParams).toEqual({
bar: 'bar?p1=v1#h1',
baz: 'baz?p2=v2#h2',
qux: 'qux?p3=v3#h3'
});
});
});
});
+2 -2
View File
@@ -261,8 +261,8 @@ describe('HTML', function() {
});
});
if (!/Edge\/\d{2,}/.test(window.navigator.userAgent)) {
// Skip test on Edge due to a browser bug.
if (!/Edge\/(16|17)/.test(window.navigator.userAgent)) {
// Skip test on Edge 16 due to browser bug.
it('should throw on a form with an input named "nextSibling"', function() {
inject(function($sanitize) {
+981 -1550
View File
File diff suppressed because it is too large Load Diff