Compare commits

..

2 Commits

Author SHA1 Message Date
7196243628 chore(changelog): update change log to alpha-45 2015-10-28 17:36:56 -07:00
b34340e456 chore: bump version to 2.0.0-alpha.45 2015-10-28 17:32:43 -07:00
1930 changed files with 92222 additions and 106835 deletions

View File

@ -1,3 +1,3 @@
{
"directory" : "bower_components"
}
"directory": "bower_components"
}

View File

@ -1,16 +0,0 @@
**IMPORTANT**: This repository's issues are reserved for feature requests and bug reports. Do not submit support requests here, see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question.
**Steps to reproduce and a minimal demo of the problem**
_Use https://plnkr.co or similar -- try this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5_
_What steps should we try in your demo to see the problem?_
**Current behavior**
**Expected/desired behavior**
**Other information**

View File

@ -1,24 +0,0 @@
* **Please check if the PR fulfills these requirements**
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-format
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)
* **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
* **What is the current behavior?** (You can also link to an open issue here)
* **What is the new behavior (if this is a feature change)?**
* **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?)
* **Other information**:

13
.gitignore vendored
View File

@ -21,13 +21,7 @@ tmp
*.js.deps
*.js.map
# Files created by the template compiler
**/*.ngfactory.ts
**/*.css.ts
**/*.css.shim.ts
# Or type definitions we mirror from github
# (NB: these lines are removed in publish-build-artifacts.sh)
**/typings/**/*.d.ts
**/typings/tsd.cached.json
@ -35,7 +29,6 @@ tmp
pubspec.lock
.c9
.idea/
.settings/
*.swo
modules/.settings
.vscode
@ -51,9 +44,3 @@ npm-debug.log
# build-analytics
.build-analytics
# built dart payload tests
/modules_dart/payload/**/build
# rollup-test output
/modules/rollup-test/dist/

2
.nvmrc
View File

@ -1 +1 @@
5.4.1
4.2.1

12
.settings/settings.json Normal file
View File

@ -0,0 +1,12 @@
{
"search.exclude": {
".git" : true,
".idea": true,
"node_modules" : true,
"bower_components" : true,
"packages" : true,
"build" : true,
"dist" : true,
"tmp" : true
}
}

View File

@ -1,192 +1,123 @@
language: node_js
sudo: false
node_js:
- '5.4.1'
addons:
# firefox: "38.0"
apt:
sources:
# needed to install g++ that is used by npms's native modules
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- '4.2.1'
branches:
except:
- g3_v2_0
- g3sync
cache:
directories:
- ./node_modules
- ./.chrome/chromium
# - $HOME/.pub-cache
#before_cache:
# # Undo the pollution of the typescript_next build before the cache is primed for future use
# - if [[ "$MODE" == "typescript_next" ]]; then npm install typescript; fi
- node_modules
- $HOME/.pub-cache
env:
global:
# - KARMA_JS_BROWSERS=ChromeNoSandbox
# - E2E_BROWSERS=ChromeOnTravis
# - LOGS_DIR=/tmp/angular-build/logs
# - ARCH=linux-x64
- KARMA_BROWSERS=DartiumWithWebPlatform
- E2E_BROWSERS=Dartium
- LOGS_DIR=/tmp/angular-build/logs
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
- ARCH=linux-x64
- DART_DEV_VERSION=latest
- DART_STABLE_VERSION=latest
# Token for tsd to increase github rate limit
# See https://github.com/DefinitelyTyped/tsd#tsdrc
# This does not use http://docs.travis-ci.com/user/environment-variables/#Secure-Variables
# because those are not visible for pull requests, and those should also be reliable.
# This SSO token belongs to github account angular-github-ratelimit-token which has no access
# (password is in Valentine)
- TSDRC='{"token":"ef474500309daea53d5991b3079159a29520a40b"}'
# GITHUB_TOKEN_ANGULAR
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
matrix:
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
- CI_MODE=js
- CI_MODE=lint
- CI_MODE=e2e
- CI_MODE=saucelabs_required
- CI_MODE=browserstack_required
# Order: slowest build on top, so that we don't hog VMs while waiting for others to complete.
- MODE=dart DART_CHANNEL=stable DART_VERSION=$DART_STABLE_VERSION
- MODE=dart DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
- MODE=saucelabs DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
- MODE=dart_experimental DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
- MODE=js DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
- MODE=router DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
- MODE=lint DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
#matrix:
# allow_failures:
# - env: "MODE=saucelabs_optional"
# - env: "MODE=browserstack_optional"
matrix:
allow_failures:
- env: "MODE=saucelabs DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION"
- env: "MODE=dart_experimental DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION"
addons:
firefox: "38.0"
before_install:
- echo ${TSDRC} > .tsdrc
- export DISPLAY=:99.0
- export GIT_SHA=$(git rev-parse HEAD)
- ./scripts/ci/init_android.sh
- ./scripts/ci/install_dart.sh ${DART_CHANNEL} ${DART_VERSION} ${ARCH}
- sh -e /etc/init.d/xvfb start
- if [[ -e SKIP_TRAVIS_TESTS ]]; then { cat SKIP_TRAVIS_TESTS ; exit 0; } fi
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "master" ] && SAUCE_USERNAME="angular2-ci" && SAUCE_ACCESS_KEY="693ebc16208a-0b5b-1614-8d66-a2662f4e" || true'
install:
- ./scripts/ci-lite/install.sh
# Check the size of caches
- du -sh ./node_modules || true
# Install npm dependecies
- npm install
before_script:
- mkdir -p $LOGS_DIR
- ./scripts/ci/presubmit-queue-setup.sh
script:
- ./scripts/ci-lite/build.sh && ./scripts/ci-lite/test.sh
- ./scripts/ci/build_and_test.sh ${MODE}
after_script:
- ./scripts/ci-lite/cleanup.sh
- ./scripts/ci/print-logs.sh
- ./scripts/ci/after-script.sh
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/1ef62e23078036f9cee4
# trigger Buildtime Trend Service to parse Travis CI log
- https://buildtimetrend.herokuapp.com/travis
on_success: always # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false
slack:
secure: EP4MzZ8JMyNQJ4S3cd5LEPWSMjC7ZRdzt3veelDiOeorJ6GwZfCDHncR+4BahDzQAuqyE/yNpZqaLbwRWloDi15qIUsm09vgl/1IyNky1Sqc6lEknhzIXpWSalo4/T9ZP8w870EoDvM/UO+LCV99R3wS8Nm9o99eLoWVb2HIUu0=
#branches:
# except:
# - g3_v2_0
#
#cache:
# directories:
# - $HOME/.pub-cache
# - $HOME/.chrome/chromium
#
#before_cache:
# # Undo the pollution of the typescript_next build before the cache is primed for future use
# - if [[ "$MODE" == "typescript_next" ]]; then npm install typescript; fi
#
#env:
# global:
# # Use newer verison of GCC to that is required to compile native npm modules for Node v4+ on Ubuntu Precise
# # more info: https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements
# - CXX=g++-4.8
# - KARMA_DART_BROWSERS=DartiumWithWebPlatform
# # No sandbox mode is needed for Chromium in Travis, it crashes otherwise: https://sites.google.com/a/chromium.org/chromedriver/help/chrome-doesn-t-start
# - KARMA_JS_BROWSERS=ChromeNoSandbox
# - E2E_BROWSERS=ChromeOnTravis
# - LOGS_DIR=/tmp/angular-build/logs
# - SAUCE_USERNAME=angular-ci
# - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
# - BROWSER_STACK_USERNAME=angularteam1
# - BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB
# - ARCH=linux-x64
# - DART_DEV_VERSION=latest
# - DART_STABLE_VERSION=latest
# - DART_CHANNEL=stable
# - DART_VERSION=$DART_STABLE_VERSION
# # Token for tsd to increase github rate limit
# # See https://github.com/DefinitelyTyped/tsd#tsdrc
# # This does not use http://docs.travis-ci.com/user/environment-variables/#Secure-Variables
# # because those are not visible for pull requests, and those should also be reliable.
# # This SSO token belongs to github account angular-github-ratelimit-token which has no access
# # (password is in Valentine)
# - TSDRC='{"token":"ef474500309daea53d5991b3079159a29520a40b"}'
# # GITHUB_TOKEN_ANGULAR
# - secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
# matrix:
# # Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
# - MODE=dart
# - MODE=dart DART_CHANNEL=dev
# - MODE=saucelabs_required
# - MODE=browserstack_required
# - MODE=saucelabs_optional
# - MODE=browserstack_optional
# - MODE=dart_ddc
# - MODE=js
# - MODE=router
# - MODE=build_only
# - MODE=typescript_next
# - MODE=lint
#
#matrix:
# allow_failures:
# - env: "MODE=saucelabs_optional"
# - env: "MODE=browserstack_optional"
#
#addons:
# firefox: "38.0"
# apt:
# sources:
# - ubuntu-toolchain-r-test
# packages:
# - g++-4.8
#
#before_install:
# - node tools/analytics/build-analytics start ci job
# - node tools/analytics/build-analytics start ci before_install
# - echo ${TSDRC} > .tsdrc
# - export CHROME_BIN=$HOME/.chrome/chromium/chrome-linux/chrome
# - export DISPLAY=:99.0
# - export GIT_SHA=$(git rev-parse HEAD)
# - ./scripts/ci/init_android.sh
# - sh -e /etc/init.d/xvfb start
# # Use a separate SauseLabs account for upstream/master builds in order for Sauce to create a badge representing the status of just upstream/master
# - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "master" ] && SAUCE_USERNAME="angular2-ci" && SAUCE_ACCESS_KEY="693ebc16208a-0b5b-1614-8d66-a2662f4e" || true'
# - node tools/analytics/build-analytics success ci before_install
#
#install:
# - node tools/analytics/build-analytics start ci install
# # Install version of npm that we are locked against
# - npm install -g npm@3.5.3
# # Install version of Chromium that we are locked against
# - ./scripts/ci/install_chromium.sh
# # Install version of Dart based on the matrix build variables
# - ./scripts/ci/install_dart.sh ${DART_CHANNEL} ${DART_VERSION} ${ARCH}
# # Print the size of caches to ease debugging
# - du -sh ./node_modules || true
# # Install npm dependecies
# # check-node-modules will exit(1) if we don't need to install
# # we need to manually kick off the postinstall script if check-node-modules exit(0)s
# - node tools/npm/check-node-modules --purge && npm install || npm run postinstall
# - node tools/analytics/build-analytics success ci install
#
#before_script:
# - node tools/analytics/build-analytics start ci before_script
# - mkdir -p $LOGS_DIR
# - ./scripts/ci/presubmit-queue-setup.sh
# - node tools/analytics/build-analytics success ci before_script
#
#script:
# - node tools/analytics/build-analytics start ci script
# - ./scripts/ci/build_and_test.sh ${MODE}
# - node tools/analytics/build-analytics success ci script
#
#after_script:
# - node tools/analytics/build-analytics start ci after_script
# - ./scripts/ci/print-logs.sh
# - ./scripts/ci/after-script.sh
# - ./scripts/publish/publish-build-artifacts.sh
# - node tools/analytics/build-analytics success ci after_script
# - tools/analytics/build-analytics $TRAVIS_TEST_RESULT ci job
#
#notifications:
# webhooks:
# urls:
# - https://webhooks.gitter.im/e/1ef62e23078036f9cee4
# # trigger Buildtime Trend Service to parse Travis CI log
# - https://buildtimetrend.herokuapp.com/travis
# - http://104.197.9.155:8484/hubot/travis/activity
# on_success: always # options: [always|never|change] default: always
# on_failure: always # options: [always|never|change] default: always
# on_start: never # default: never
# slack:
# secure: EP4MzZ8JMyNQJ4S3cd5LEPWSMjC7ZRdzt3veelDiOeorJ6GwZfCDHncR+4BahDzQAuqyE/yNpZqaLbwRWloDi15qIUsm09vgl/1IyNky1Sqc6lEknhzIXpWSalo4/T9ZP8w870EoDvM/UO+LCV99R3wS8Nm9o99eLoWVb2HIUu0=
deploy:
- provider: gcs
# This is for project angular-github-babysitter
access_key_id: GOOGIOQTDBEOPBUAWFZQ
secret_access_key:
secure: "MEDggllZ5fw4wI9CEUi8WR6jKsKXqdRF/DLxSNC2JpzM5RlVeBm0uqjntYT1Cf1dASvQ2/+vZCUikL/3A48NcoEYRHXGmxu8D6t/SvleQD8Xv434xFOdsa2QqP/HiCtqCLOI5jJz1JVoB5nNyKKZ33ogTUL1LV1TfcrAioyizW8="
# this bucket has a lifecycle to delete after 90 days:
# $ echo '{"rule": [{"action": {"type": "Delete"}, "condition": {"age": 90}}]}' > lifecycle.json
# $ gsutil lifecycle set lifecycle.json gs://angular2-snapshots
bucket: angular2-snapshots
# don't delete generated files
skip_cleanup: true
# serve to public at https://storage.googleapis.com/angular2-snapshots/SHA/dart_stable/dist.tgz
acl: public-read
# upload the .tgz archive created in scripts/ci/build_and_test.sh
local-dir: deploy
# create a "subdirectory" for each commit
upload-dir: $TRAVIS_COMMIT/dart_stable
on:
repo: angular/angular
condition: "$MODE = dart && $DART_CHANNEL = stable"
- provider: gcs
access_key_id: GOOGIOQTDBEOPBUAWFZQ
secret_access_key:
secure: "MEDggllZ5fw4wI9CEUi8WR6jKsKXqdRF/DLxSNC2JpzM5RlVeBm0uqjntYT1Cf1dASvQ2/+vZCUikL/3A48NcoEYRHXGmxu8D6t/SvleQD8Xv434xFOdsa2QqP/HiCtqCLOI5jJz1JVoB5nNyKKZ33ogTUL1LV1TfcrAioyizW8="
bucket: angular2-snapshots
skip_cleanup: true
acl: public-read
local-dir: deploy
upload-dir: $TRAVIS_COMMIT/js
on:
repo: angular/angular
condition: "$MODE = js"

File diff suppressed because it is too large Load Diff

View File

@ -11,11 +11,8 @@ Someone with committer access will do the rest.
We have automated the process for merging pull requests into master. Our goal is to minimize the disruption for
Angular committers and also prevent breakages on master.
When a PR has `pr_state: LGTM` and is ready to merge, you should add the `pr_action: merge` label.
Currently (late 2015), we need to ensure that each PR will cleanly merge into the Google-internal version control,
so the caretaker reviews the changes manually.
After this review, the caretaker adds `zomg_admin: do_merge` which is restricted to admins only.
When a PR is ready to merge, a project member in the CoreTeamMember list (see below) can add the special label,
`PR: merge`.
A robot running as [mary-poppins](https://github.com/mary-poppins)
is notified that the label was added by an authorized person,
and will create a new branch in the angular project, using the convention `presubmit-{username}-pr-{number}`.
@ -29,6 +26,6 @@ Finally, after merge `mary-poppins` removes the presubmit branch.
## Administration
The list of users who can trigger a merge by adding the `zomg_admin: do_merge` label is stored in our appengine app datastore.
The list of users who can trigger a merge by adding the label is stored in our appengine app datastore.
Edit the contents of the [CoreTeamMember Table](
https://console.developers.google.com/project/angular2-automation/datastore/query?queryType=KindQuery&namespace=&kind=CoreTeamMember)

View File

@ -48,18 +48,14 @@ features, by not reporting duplicate issues. Providing the following informatio
chances of your issue being dealt with quickly:
* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
* **Angular Version** - what version of Angular is affected (e.g. 2.0.0-alpha.53)
* **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you
* **Motivation for or Use Case** - explain why this is a bug for you
* **Browsers and Operating System** - is this a problem with all browsers?
* **Reproduce the Error** - provide a live example (using [Plunker][plunker],
[JSFiddle][jsfiddle] or [Runnable][runnable]) or a unambiguous set of steps
[JSFiddle][jsfiddle] or [Runnable][runnable]) or a unambiguous set of steps.
* **Related Issues** - has a similar issue been reported before?
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
causing the problem (line of code or commit)
You can file new issues by providing the above information [here](https://github.com/angular/angular/issues/new).
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
Before you submit your Pull Request (PR) consider the following guidelines:
@ -179,10 +175,9 @@ Must be one of the following:
semi-colons, etc)
* **refactor**: A code change that neither fixes a bug nor adds a feature
* **perf**: A code change that improves performance
* **test**: Adding missing tests or correcting existing tests
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
* **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
* **chore**: Other changes that don't modify `src` or `test` files
* **test**: Adding missing tests
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
generation
### Scope
The scope could be anything specifying place of the commit change. For example

View File

@ -7,9 +7,8 @@ JS and Dart versions. It also explains the basic mechanics of using `git`, `node
* [Getting the Sources](#getting-the-sources)
* [Environment Variable Setup](#environment-variable-setup)
* [Installing NPM Modules and Dart Packages](#installing-npm-modules-and-dart-packages)
* [Build commands](#build-commands)
* [Running Tests Locally](#running-tests-locally)
* [Code Style](#code-style)
* [Formatting](#clang-format)
* [Project Information](#project-information)
* [CI using Travis](#ci-using-travis)
* [Transforming Dart code](#transforming-dart-code)
@ -23,16 +22,7 @@ if you'd like to contribute to Angular.
Before you can build and test Angular, you must install and configure the
following products on your development machine:
* [Git](http://git-scm.com) and/or the **GitHub app** (for [Mac](http://mac.github.com) or
[Windows](http://windows.github.com)); [GitHub's Guide to Installing
Git](https://help.github.com/articles/set-up-git) is a good source of information.
* [Node.js](http://nodejs.org), (version `>=5.4.1 <6`) which is used to run a development web server,
run tests, and generate distributable files. We also use Node's Package Manager, `npm`
(version `>=3.5.3 <4.0`), which comes with Node. Depending on your system, you can install Node either from
source or as a pre-packaged bundle.
* *Optional*: [Dart](https://www.dartlang.org) (version ` >=1.13.2 <2.0.0`), specifically the Dart-SDK and
* [Dart](https://www.dartlang.org) (version ` >=1.12.0 <2.0.0`), specifically the Dart-SDK and
Dartium (a version of [Chromium](http://www.chromium.org) with native support for Dart through
the Dart VM). One of the **simplest** ways to get both is to install the **Dart Editor bundle**,
which includes the editor, SDK and Dartium. See the [Dart tools](https://www.dartlang.org/tools)
@ -42,6 +32,19 @@ following products on your development machine:
to the `Path` (e.g. `path-to-dart-sdk-folder\bin`) and a new `DARTIUM_BIN` environment variable must be
created, pointing to the executable (e.g. `path-to-dartium-folder\chrome.exe).`
* [Git](http://git-scm.com) and/or the **GitHub app** (for [Mac](http://mac.github.com) or
[Windows](http://windows.github.com)); [GitHub's Guide to Installing
Git](https://help.github.com/articles/set-up-git) is a good source of information.
* [Node.js](http://nodejs.org), (version `>=4.2.1 <5`) which is used to run a development web server,
run tests, and generate distributable files. We also use Node's Package Manager, `npm`
(version `>=2.14.7 <3.0`), which comes with Node. Depending on your system, you can install Node either from
source or as a pre-packaged bundle.
* [Chrome Canary](https://www.google.com/chrome/browser/canary.html), a version of Chrome with
bleeding edge functionality, built especially for developers (and early adopters).
* [Bower](http://bower.io/).
## Getting the Sources
@ -92,7 +95,7 @@ export DART_SDK="$DART_EDITOR_DIR/dart-sdk"
PATH+=":$DART_SDK/bin"
```
And specify where the pubs dependencies are downloaded. By default, this directory is located under .pub_cache
And specify where the pubs dependencies are downloaded. By default, this directory is located under .pub_cache
in your home directory (on Mac and Linux), or in AppData\Roaming\Pub\Cache (on Windows).
```shell
@ -142,6 +145,12 @@ You can selectively build either the JS or Dart versions as follows:
* `$(npm bin)/gulp build.js`
* `$(npm bin)/gulp build.dart`
Also note that in order for the whole test suite to succeed you will need to generate the type definitions by running:
```shell
$(npm bin)/gulp docs/typings
```
To clean out the `dist` folder, run:
```shell
@ -186,27 +195,6 @@ tests respectively.
**Note**: **watch mode** needs symlinks to work, so if you're using windows, ensure you have the
rights to built them in your operating system.
### Unit tests with Sauce Labs or Browser Stack
First, in a terminal, create a tunnel with [Sauce Connect](https://docs.saucelabs.com/reference/sauce-connect/) or [Browser Stack Local](https://www.browserstack.com/local-testing#command-line), and valid credentials.
Then, in another terminal:
- Define the credentials as environment variables, e.g.:
```
export SAUCE_USERNAME='my_user'; export SAUCE_ACCESS_KEY='my_key';
export BROWSER_STACK_USERNAME='my_user'; export BROWSER_STACK_ACCESS_KEY='my_key';
```
- Then run `gulp test.unit.js.(sauce|browserstack) --browsers=option1,option2,..,optionN`
The options are any mix of browsers and aliases which are defined in the [browser-providers.conf.js](https://github.com/angular/angular/blob/master/browser-providers.conf.js) file.
They are case insensitive, and the `SL_` or `BS_` prefix must not be added for browsers.
Some examples of commands:
```
gulp test.unit.js.sauce --browsers=Safari8,ie11 //run in Sauce Labs with Safari 8 and IE11
gulp test.unit.js.browserstack --browsers=Safari,IE //run in Browser Stack with Safari 7, Safari 8, Safari 9, IE 9, IE 10 and IE 11
gulp test.unit.js.sauce --browsers=IOS,safari8,android5.1 //run in Sauce Labs with iOS 7, iOS 8, iOs 9, Safari 8 and Android 5.1
```
### E2E tests
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder).
@ -227,9 +215,7 @@ Angular specific command line options when running protractor:
Angular specific command line options when running protractor (e.g. force gc, ...):
`$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
## Code Style
### Formatting with <a name="clang-format">clang-format</a>
## Formatting with <a name="clang-format">clang-format</a>
We use [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to automatically enforce code
style for our TypeScript code. This allows us to focus our code reviews more on the content, and
@ -247,7 +233,12 @@ Your life will be easier if you include the formatter in your standard workflow.
likely forget to check the formatting, and waste time waiting for a build on Travis that fails due
to some whitespace difference.
* Use `$(npm bin)/clang-format -i [file name]` to format a file (or multiple).
* Install clang-format with `npm install -g clang-format`.
* Use `clang-format -i [file name]` to format a file (or multiple).
Note that `clang-format` tries to load a `clang-format` node module close to the sources being
formatted, or from the `$CWD`, and only then uses the globally installed one - so the version used
should automatically match the one required by the project.
Use `clang-format -version` in case you get confused.
* Use `gulp enforce-format` to check if your code is `clang-format` clean. This also gives
you a command line to format your code.
* `clang-format` also includes a git hook, run `git clang-format` to format all files you
@ -269,24 +260,16 @@ to some whitespace difference.
- Synchronize files after execution: checked
- Open console: not checked
- Show in: Editor menu
- Program: `$ProjectFileDir$/node_modules/.bin/clang-format`
- Program: [path to clang-format, try `$ echo $(npm config get prefix)/bin/clang-format`]
- Parameters: `-i -style=file $FilePath$`
- Working directory: `$ProjectFileDir$`
* `clang-format` integrations are also available for many popular editors (`vim`, `emacs`,
`Sublime Text`, etc.).
### Linting
We use [tslint](https://github.com/palantir/tslint) for linting. See linting rules in [gulpfile](gulpfile.js). To lint, run
```shell
$ gulp lint
```
## Generating the API documentation
The following gulp task will generate the API docs in the `dist/angular.io/partials/api/angular2`:
```shell
$(npm bin)/gulp docs/angular.io
```
@ -296,8 +279,8 @@ You can serve the generated documentation to check how it would render on [angul
- install dependencies as described in the [angular.io README](https://github.com/angular/angular.io/blob/master/README.md),
- copy the generated documentation from your local angular repo at `angular/dist/angular.io/partials/api/angular2` to your local angular.io repo at `angular.io/public/docs/js/latest/api`,
- run `harp compile` at the root of the angular.io repo to check the generated documentation for errors,
- run `harp server` and open a browser at `http://localhost:9000/docs/js/latest/api/` to check the rendered documentation.
- run `harp server` and open a browser at `http://localhost:9000/docs/js/latest/api/` to check the rendered documentation.
## Project Information
### Folder structure

215
LICENSE
View File

@ -1,21 +1,202 @@
The MIT License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (c) 2014-2016 Google, Inc. http://angular.io
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. Definitions.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,24 +1,31 @@
[![Build Status](https://travis-ci.org/angular/angular.svg?branch=master)](https://travis-ci.org/angular/angular)
[![Build Status](https://travis-ci.org/angular/angular.svg?branch=master)](https://travis-ci.org/angular/angular)
[![Join the chat at https://gitter.im/angular/angular](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr)](http://issuestats.com/github/angular/angular)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue)](http://issuestats.com/github/angular/angular)
[![npm version](https://badge.fury.io/js/angular2.svg)](http://badge.fury.io/js/angular2)
[![Downloads](http://img.shields.io/npm/dm/angular2.svg)](https://npmjs.org/package/angular2)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/angular2-ci.svg)](https://saucelabs.com/u/angular2-ci)
Angular
Angular
=========
Angular is a development platform for building mobile and desktop web applications. This is the
repository for [Angular 2][ng2], both the JavaScript (JS) and [Dart][dart] versions.
Angular 2 is currently in **Beta**.
Angular 2 is currently in **Developer Preview**. We recommend using Angular 1.X for production
applications:
* [AngularJS][ngJS]: [angular/angular.js](http://github.com/angular/angular.js).
* [AngularDart][ngDart]: [angular/angular.dart](http://github.com/angular/angular.dart).
## Quickstart
[Get started in 5 minutes][quickstart].
## Setup & Install Angular 2
Follow the instructions given on the [Angular download page][download].
## Want to help?
@ -29,7 +36,8 @@ guidelines for [contributing][contributing] and then check out one of our issues
[contributing]: http://github.com/angular/angular/blob/master/CONTRIBUTING.md
[dart]: http://www.dartlang.org
[dartium]: http://www.dartlang.org/tools/dartium
[quickstart]: https://angular.io/docs/ts/latest/quickstart.html
[download]: http://angular.io/download/
[quickstart]: https://angular.io/docs/js/latest/quickstart.html
[ng2]: http://angular.io
[ngDart]: http://angulardart.org
[ngJS]: http://angularjs.org

View File

@ -21,7 +21,7 @@ By default the debugging tools are disabled.
Enable the debugging tools as follows:
```dart
import 'package:angular2/platform/browser.dart';
import 'package:angular2/tools.dart';
main() async {
var appRef = await bootstrap(Application);

View File

@ -14,7 +14,7 @@ Ctrl + Shift + j.
By default the debug tools are disabled. You can enable debug tools as follows:
```typescript
import {enableDebugTools} from 'angular2/platform/browser';
import 'angular2/tools';
bootstrap(Application).then((appRef) => {
enableDebugTools(appRef);
@ -117,7 +117,7 @@ speed things up is to use plain class fields in your expressions and avoid any
kinds of computation. Example:
```typescript
@Component({
@View({
template: '<button [enabled]="isEnabled">{{title}}</button>'
})
class FancyButton {

View File

@ -1,313 +0,0 @@
// Unique place to configure the browsers which are used in the different CI jobs in Sauce Labs (SL) and BrowserStack (BS).
// If the target is set to null, then the browser is not run anywhere during CI.
// If a category becomes empty (e.g. BS and required), then the corresponding job must be commented out in Travis configuration.
var CIconfiguration = {
'Chrome': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Firefox': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
// FirefoxBeta should be required:true
// https://github.com/angular/angular/issues/7560
'FirefoxBeta': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: false}},
'ChromeDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'FirefoxDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'IE9': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'IE10': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'IE11': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Edge': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Android4.1': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.2': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.3': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.4': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android5': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Safari7': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS7': { unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
// TODO(mlaval): iOS9 deactivated as not reliable, reactivate after https://github.com/angular/angular/issues/5408
'iOS9': { unitTest: {target: null, required: false}, e2e: {target: null, required: true}},
'WindowsPhone': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
};
var customLaunchers = {
'DartiumWithWebPlatform': {
base: 'Dartium',
flags: ['--enable-experimental-web-platform-features'] },
'ChromeNoSandbox': {
base: 'Chrome',
flags: ['--no-sandbox'] },
'SL_CHROME': {
base: 'SauceLabs',
browserName: 'chrome',
version: '50'
},
'SL_CHROMEBETA': {
base: 'SauceLabs',
browserName: 'chrome',
version: 'beta'
},
'SL_CHROMEDEV': {
base: 'SauceLabs',
browserName: 'chrome',
version: 'dev'
},
'SL_FIREFOX': {
base: 'SauceLabs',
browserName: 'firefox',
version: '45'
},
'SL_FIREFOXBETA': {
base: 'SauceLabs',
browserName: 'firefox',
version: 'beta'
},
'SL_FIREFOXDEV': {
base: 'SauceLabs',
browserName: 'firefox',
version: 'dev'
},
'SL_SAFARI7': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
},
'SL_SAFARI8': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.10',
version: '8'
},
'SL_SAFARI9': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.11',
version: '9.0'
},
'SL_IOS7': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '7.1'
},
'SL_IOS8': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '8.4'
},
'SL_IOS9': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '9.1'
},
'SL_IE9': {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 2008',
version: '9'
},
'SL_IE10': {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 2012',
version: '10'
},
'SL_IE11': {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
'SL_EDGE': {
base: 'SauceLabs',
browserName: 'microsoftedge',
platform: 'Windows 10',
version: '20.10240'
},
'SL_ANDROID4.1': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.1'
},
'SL_ANDROID4.2': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.2'
},
'SL_ANDROID4.3': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.3'
},
'SL_ANDROID4.4': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.4'
},
'SL_ANDROID5': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '5.1'
},
'BS_CHROME': {
base: 'BrowserStack',
browser: 'chrome',
os: 'OS X',
os_version: 'Yosemite'
},
'BS_FIREFOX': {
base: 'BrowserStack',
browser: 'firefox',
os: 'Windows',
os_version: '10'
},
'BS_SAFARI7': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mavericks'
},
'BS_SAFARI8': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Yosemite'
},
'BS_SAFARI9': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'El Capitan'
},
'BS_IOS7': {
base: 'BrowserStack',
device: 'iPhone 5S',
os: 'ios',
os_version: '7.0'
},
'BS_IOS8': {
base: 'BrowserStack',
device: 'iPhone 6',
os: 'ios',
os_version: '8.3'
},
'BS_IOS9': {
base: 'BrowserStack',
device: 'iPhone 6S',
os: 'ios',
os_version: '9.0'
},
'BS_IE9': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '9.0',
os: 'Windows',
os_version: '7'
},
'BS_IE10': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '10.0',
os: 'Windows',
os_version: '8'
},
'BS_IE11': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '11.0',
os: 'Windows',
os_version: '10'
},
'BS_EDGE': {
base: 'BrowserStack',
browser: 'edge',
os: 'Windows',
os_version: '10'
},
'BS_WINDOWSPHONE' : {
base: 'BrowserStack',
device: 'Nokia Lumia 930',
os: 'winphone',
os_version: '8.1'
},
'BS_ANDROID5': {
base: 'BrowserStack',
device: 'Google Nexus 5',
os: 'android',
os_version: '5.0'
},
'BS_ANDROID4.4': {
base: 'BrowserStack',
device: 'HTC One M8',
os: 'android',
os_version: '4.4'
},
'BS_ANDROID4.3': {
base: 'BrowserStack',
device: 'Samsung Galaxy S4',
os: 'android',
os_version: '4.3'
},
'BS_ANDROID4.2': {
base: 'BrowserStack',
device: 'Google Nexus 4',
os: 'android',
os_version: '4.2'
},
'BS_ANDROID4.1': {
base: 'BrowserStack',
device: 'Google Nexus 7',
os: 'android',
os_version: '4.1'
}
};
var sauceAliases = {
'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'SauceLabs';}),
'DESKTOP': ['SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9'],
'MOBILE': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5', 'SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
'ANDROID': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5'],
'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'],
'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9'],
'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'],
'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'],
'CI_REQUIRED': buildConfiguration('unitTest', 'SL', true),
'CI_OPTIONAL': buildConfiguration('unitTest', 'SL', false)
};
var browserstackAliases = {
'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'BrowserStack';}),
'DESKTOP': ['BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
'MOBILE': ['BS_ANDROID4.3', 'BS_ANDROID4.4', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_WINDOWSPHONE'],
'ANDROID': ['BS_ANDROID4.3', 'BS_ANDROID4.4'],
'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'],
'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9'],
'SAFARI': ['BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
'CI_REQUIRED': buildConfiguration('unitTest', 'BS', true),
'CI_OPTIONAL': buildConfiguration('unitTest', 'BS', false)
};
module.exports = {
customLaunchers: customLaunchers,
sauceAliases: sauceAliases,
browserstackAliases: browserstackAliases
};
function buildConfiguration(type, target, required) {
return Object.keys(CIconfiguration)
.filter((item) => {
var conf = CIconfiguration[item][type];
return conf.required === required && conf.target === target;
})
.map((item) => {
return target + '_' + item.toUpperCase();
});
}

112
build.sh
View File

@ -1,112 +0,0 @@
#!/usr/bin/env bash
set -e -o pipefail
cd `dirname $0`
TSCONFIG=./modules/tsconfig.json
echo "====== (all)COMPILING: \$(npm bin)/ng2tc -p ${TSCONFIG} ====="
rm -rf ./dist/all/
mkdir ./dist/all/
# prepare all files for e2e tests
cp -r ./modules/playground ./dist/all/
cp -r ./modules/playground/favicon.ico ./dist/
#rsync -aP ./modules/playground/* ./dist/all/playground/
mkdir ./dist/all/playground/vendor
cd ./dist/all/playground/vendor
ln -s ../../../../node_modules/es6-shim/es6-shim.js .
ln -s ../../../../node_modules/zone.js/dist/zone.js .
ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js .
ln -s ../../../../node_modules/systemjs/dist/system.src.js .
ln -s ../../../../node_modules/base64-js/lib/b64.js .
ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
ln -s ../../../../node_modules/rxjs/bundles/Rx.js .
ln -s ../../../../node_modules/angular/angular.js .
cd -
# compile ts code
$(npm bin)/ng2tc -p ${TSCONFIG}
rm -rf ./dist/packages-dist
for PACKAGE in \
core \
compiler \
common \
platform-browser \
platform-browser-dynamic \
platform-server \
http \
router \
router-deprecated \
upgrade
do
SRCDIR=./modules/@angular/${PACKAGE}
DESTDIR=./dist/packages-dist/${PACKAGE}
UMDES6PATH=${DESTDIR}/esm/${PACKAGE}.umd.js
UMDES5PATH=${DESTDIR}/${PACKAGE}.umd.js
if [[ ${PACKAGE} == "router-deprecated" ]]; then
echo "====== COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json ====="
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json
else
echo "====== COMPILING: \$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es5.json ====="
$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es5.json
fi
cp ${SRCDIR}/package.json ${DESTDIR}/
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
if [[ ${TRAVIS} ]]; then
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -e 's/\(^ *(static |private )*\)*readonly */\1/g'
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
else
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -e 's/\(^ *(static |private )*\)*readonly */\1/g'
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
fi
if [[ ${PACKAGE} == "router-deprecated" ]]; then
echo "====== (esm)COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json ====="
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json
else
echo "====== (esm)COMPILING: \$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es2015.json ====="
$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es2015.json
fi
echo "====== BUNDLING: ${SRCDIR} ====="
(
cd ${SRCDIR}
echo "..." # here just to have grep match something and not exit with 1
../../../node_modules/.bin/rollup -c rollup.config.js
) 2>&1 | grep -v "as external dependency"
# workaround for https://github.com/rollup/rollup/issues/626
if [[ ${TRAVIS} ]]; then
sed -i "s/ class exports\./ class /g" ${DESTDIR}/esm/${PACKAGE}.umd.js
else
sed -i '' "s/ class exports\./ class /g" ${DESTDIR}/esm/${PACKAGE}.umd.js
fi
$(npm bin)/tsc \
--out ${UMDES5PATH} \
--target es5 \
--allowJs \
${UMDES6PATH} \
modules/\@angular/manual_typings/globals.d.ts \
modules/\@angular/typings/es6-collections/es6-collections.d.ts \
modules/\@angular/typings/es6-promise/es6-promise.d.ts
rm ${UMDES6PATH}
cat ./modules/@angular/license-banner.txt > ${UMDES5PATH}.tmp
cat ${UMDES5PATH} >> ${UMDES5PATH}.tmp
mv ${UMDES5PATH}.tmp ${UMDES5PATH}
done

View File

@ -1,21 +0,0 @@
machine:
node:
version: 5.4.1
dependencies:
pre:
- npm install -g npm
override:
- npm install:
environment:
# Token for tsd to increase github rate limit
# See https://github.com/DefinitelyTyped/tsd#tsdrc
# This is not hidden using https://circleci.com/docs/fork-pr-builds#details
# because those are not visible for pull requests, and those should also be reliable.
# This SSO token belongs to github account angular-github-ratelimit-token which has no access
# (password is in Valentine)
TSD_GITHUB_TOKEN: ef474500309daea53d5991b3079159a29520a40b
test:
override:
- npm run build

View File

@ -0,0 +1,121 @@
var Package = require('dgeni').Package;
var jsdocPackage = require('dgeni-packages/jsdoc');
var nunjucksPackage = require('dgeni-packages/nunjucks');
var typescriptPackage = require('../typescript-package');
var gitPackage = require('dgeni-packages/git');
var path = require('canonical-path');
// Define the dgeni package for generating the docs
module.exports = new Package('angular-v2-docs', [jsdocPackage, nunjucksPackage, typescriptPackage, gitPackage])
// Register the processors
.processor(require('./processors/createTypeDefinitionFile'))
.config(function(readFilesProcessor, inlineTagProcessor) {
readFilesProcessor.basePath = path.resolve(__dirname, '../..');
// Don't run unwanted processors
readFilesProcessor.$enabled = false;
inlineTagProcessor.$enabled = false;
})
// Configure the log service
.config(function(log) {
log.level = 'info';
})
.config(function(renderDocsProcessor, versionInfo) {
renderDocsProcessor.extraData.versionInfo = versionInfo;
})
.config(function(readFilesProcessor, inlineTagProcessor, readTypeScriptModules, createTypeDefinitionFile) {
// Don't run unwanted processors
readFilesProcessor.$enabled = false; // We are not using the normal file reading processor
inlineTagProcessor.$enabled = false; // We are not actually processing the inline link tags
// Configure file reading
readFilesProcessor.basePath = path.resolve(__dirname, '../..');
readTypeScriptModules.sourceFiles = [
'angular2/angular2.ts',
'angular2/web_worker/worker.ts',
'angular2/web_worker/ui.ts',
'angular2/router.ts',
'angular2/http.ts',
'angular2/testing.ts'
];
readTypeScriptModules.basePath = path.resolve(path.resolve(__dirname, '../../modules'));
createTypeDefinitionFile.typeDefinitions = [
{
id: 'angular2/angular2',
references: [],
modules: {
'angular2/angular2': {namespace: 'ng', id: 'angular2/angular2'},
'angular2/web_worker/worker': {namespace: 'ngWorker', id: 'angular2/web_worker/worker'},
'angular2/web_worker/ui': {namespace: 'ngUi', id: 'angular2/web_worker/ui'}
}
},
{
id: 'angular2/router',
references: ['./angular2.d.ts'],
remapTypes: {Type: 'ng.Type', InjectableReference: 'ng.InjectableReference', ElementRef: 'ng.ElementRef', DynamicComponentLoader: 'ng.DynamicComponentLoader'},
modules: {'angular2/router': {namespace: 'ngRouter', id: 'angular2/router'}}
},
{
id: 'angular2/http',
references: ['./angular2.d.ts'],
remapTypes: {Type: 'ng.Type', Observable: 'ng.Observable', EventEmitter: 'ng.EventEmitter', InjectableReference: 'ng.InjectableReference' },
modules: {'angular2/http': {namespace: 'ngHttp', id: 'angular2/http'}}
},
{
id: 'angular2/testing',
references: ['./angular2.d.ts'],
remapTypes: { Type: 'ng.Type', Binding: 'ng.Binding', Provider: 'ng.Provider', ViewMetadata: 'ng.ViewMetadata', Injector: 'ng.Injector',
Predicate: 'ng.Predicate', ElementRef: 'ng.ElementRef', DebugElement: 'ng.DebugElement',
InjectableReference: 'ng.InjectableReference', ComponentRef: 'ng.ComponentRef' },
modules: {'angular2/testing': {namespace: 'ngTesting', id: 'angular2/testing'}}
}
];
})
.config(function(parseTagsProcessor, getInjectables) {
// We actually don't want to parse param docs in this package as we are getting the data out using TS
parseTagsProcessor.tagDefinitions.forEach(function(tagDef) {
if (tagDef.name === 'param') {
tagDef.docProperty = 'paramData';
tagDef.transforms = [];
}
});
})
// Configure file writing
.config(function(writeFilesProcessor) {
writeFilesProcessor.outputFolder = 'dist/docs';
})
// Configure rendering
.config(function(templateFinder, templateEngine) {
// Nunjucks and Angular conflict in their template bindings so change Nunjucks
templateEngine.config.tags = {
variableStart: '{$',
variableEnd: '$}'
};
templateFinder.templateFolders
.unshift(path.resolve(__dirname, 'templates'));
templateFinder.templatePatterns = [
'${ doc.template }',
'${ doc.id }.${ doc.docType }.template.html',
'${ doc.id }.template.html',
'${ doc.docType }.template.html',
'common.template.html'
];
});

View File

@ -0,0 +1,11 @@
var Package = require('dgeni').Package;
module.exports = function mockPackage() {
return new Package('mockPackage', [require('../')])
// provide a mock log service
.factory('log', function() { return require('dgeni/lib/mocks/log')(false); })
// .factory('templateEngine', function() { return {}; });
};

View File

@ -0,0 +1,220 @@
'use strict';
function DtsSerializer(remap) {
this.remap = remap;
}
DtsSerializer.prototype = {
_initializerRegex: /\s*=[^>][^,}]*/g,
constructor: DtsSerializer,
declaration: function(buffer, ast) {
buffer.push(ast.name);
if (ast.optional) buffer.push('?');
if (ast.typeParameters) {
buffer.push('<');
buffer.push(ast.typeParameters.join(', '));
buffer.push('>');
}
if (ast.parameters) {
buffer.push('(');
var parameters = ast.parameters;
for (var i = 0; i < parameters.length; i++) {
parameters[i] = parameters[i].replace(this._initializerRegex, '');
}
buffer.push(parameters.join(', '));
buffer.push(')');
}
if (!isConstructor(ast)) {
if (ast.returnType) {
buffer.push(': ', ast.returnType);
} else if (ast.parameters) {
buffer.push(': void');
} else {
buffer.push(': any');
}
}
buffer.push(';\n');
},
comment: function(buffer, commentText) {
if (!(commentText && commentText.match(/\S/))) return;
buffer.push('/**\n');
commentText.replace(/\n*$/, '').split('\n').forEach(function(line) {
buffer.push(' * ' + line + '\n');
});
buffer.push(' */\n');
},
member: function(buffer, ast) {
if (ast.private || ast.internal) return;
buffer.push('\n');
this.comment(buffer, ast.content);
if (ast.isStatic) buffer.push('static ');
this.declaration(buffer, ast);
},
interfaceOrClass: function(buffer, ast, isInterface) {
if (ast.abstract) {
buffer.push('abstract ');
}
buffer.push(isInterface ? 'interface ' : 'class ');
buffer.push(ast.name);
buffer.push(ast.typeParams);
buffer.push(ast.heritage);
buffer.push(' {');
buffer.indent();
if (ast.newMember) this.member(buffer, ast.newMember);
if (ast.callMember) this.member(buffer, ast.callMember);
if (ast.constructorDoc) this.member(buffer, ast.constructorDoc);
ast.statics.forEach(function(staticMember) {
this.member(buffer, staticMember);
}.bind(this));
ast.members.forEach(function(member) {
this.member(buffer, member);
}.bind(this));
buffer.unindent();
buffer.push('}');
},
enum: function(buffer, ast) {
buffer.push('enum ');
buffer.push(ast.name);
buffer.push(ast.typeParams);
buffer.push(ast.heritage);
buffer.push(' {');
buffer.indent();
ast.members.forEach(function(member, index) {
buffer.push('\n');
this.comment(buffer, member.content);
buffer.push(member.name);
if (index !== (ast.members.length - 1)) {
buffer.push(',\n');
}
}.bind(this));
buffer.unindent();
buffer.push('}\n');
},
function: function(buffer, ast) {
buffer.push('function ');
this.declaration(buffer, ast);
},
var: function(buffer, ast) {
buffer.push('var ');
this.declaration(buffer, ast);
},
let: function(buffer, ast) {
buffer.push('let ');
this.declaration(buffer, ast);
},
const: function(buffer, ast) {
buffer.push('const ');
this.declaration(buffer, ast);
},
typeAlias: function(buffer, ast) {
buffer.push('type ', ast.name, ' = ', ast.returnType);
},
serializeExport: function(ast) {
var buffer = new Buffer();
buffer.push('\n');
try {
this.comment(buffer, ast.content);
switch (ast.docType) {
case 'class': this.interfaceOrClass(buffer, ast, false); break;
case 'interface': this.interfaceOrClass(buffer, ast, true); break;
case 'function': this.function(buffer, ast); break;
case 'enum': this.enum(buffer, ast); break;
case 'var': this.var(buffer, ast); break;
case 'let': this.let(buffer, ast); break;
case 'const': this.const(buffer, ast); break;
case 'type-alias': this.typeAlias(buffer, ast); break;
default: throw new Error("unknown docType: " + ast.docType);
}
var string = buffer.toString();
for (var key in this.remap) {
if (this.remap.hasOwnProperty(key)) {
string = string.replace(new RegExp('\\b' + key + '\\b', 'gm'), this.remap[key]);
}
}
return string;
} catch (e) {
console.log(e.toString(), e.stack);
return 'ERROR: ' + e.toString();
}
}
};
function Buffer() {
this._globalBuffer = [];
this._indentedBuffer = [];
this._indentationLevel = 1;
}
Buffer.prototype = {
constructor: Buffer,
push: function() {
this._indentedBuffer.push.apply(this._indentedBuffer, arguments);
},
indent: function() {
this._globalBuffer.push({indentationLevel: this._indentationLevel, content: this._indentedBuffer.join('')});
this._indentationLevel++;
this._indentedBuffer = [];
},
unindent: function() {
this._globalBuffer.push({indentationLevel: this._indentationLevel, content: this._indentedBuffer.join('')});
this._indentationLevel--;
this._indentedBuffer = [];
},
toString: function() {
if (this._indentationLevel !== 1) {
throw new Exception("Forgot to unindent? Indentation level: " + this._indentationLevel);
}
this.unindent();
var string = '';
this._globalBuffer.forEach(function(indentedChunk) {
var indentation = (new Array(indentedChunk.indentationLevel * 2 + 1)).join(' ');
indentedChunk.content.split('\n').forEach(function(line) {
string += indentation + line + '\n';
});
});
return string;
}
};
function isConstructor(ast) {
return ast.parameters && ast.name === "constructor";
}
module.exports = {
DtsSerializer: DtsSerializer
};

View File

@ -0,0 +1,72 @@
var _ = require('lodash');
var path = require('canonical-path');
var codeGen = require('./code_gen.js');
module.exports = function createTypeDefinitionFile(log, convertPrivateClassesToInterfaces) {
return {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
$validate: {
dtsPath: { presence: true },
dtsExtension: { presence: true },
typeDefinitions: { presence: true }
},
dtsPath: 'typings',
dtsExtension: '.d.ts',
typeDefinitions: [],
$process: function(docs) {
var dtsPath = this.dtsPath;
var dtsExtension = this.dtsExtension;
// For each type definition that we wish to create we define a dgeni "doc" for it
var typeDefDocs = _.map(this.typeDefinitions, function(def) {
var id = def.id + dtsExtension;
var docPath = path.join(dtsPath, id);
return {
docType: 'type-definition',
id: id,
aliases: [id],
path: docPath,
outputPath: docPath,
// A type definition may include a number of top level modules
// And those modules could be aliased (such as 'angular2/angular2.api' ->
// 'angular2/angular2')
moduleDocs: _.transform(def.modules,
function(moduleDocs, props, alias) {
moduleDocs[props.id] = {
id: alias,
doc: null, namespace: props.namespace,
references: def.references
};
}),
dts: new codeGen.DtsSerializer(def.remapTypes)
};
});
// Now add all the module docs to their corresponding type definition doc
_.forEach(docs, function(doc) {
_.forEach(typeDefDocs, function(typeDefDoc) {
if(typeDefDoc.moduleDocs[doc.id]) {
// Add a copy, because we are going to modify it
typeDefDoc.moduleDocs[doc.id].doc = doc;
}
});
});
return _.filter(typeDefDocs, function(doc) {
_.forEach(doc.moduleDocs, function(modDoc, alias) {
if (!doc || !modDoc.doc) {
log.error('createTypeDefinitionFile processor: no such module "' + alias + '" (Did you forget to add it to the modules to load?)');
doc = null;
return;
}
convertPrivateClassesToInterfaces(modDoc.doc.exports, true);
});
return !!doc;
});
}
};
};

View File

@ -0,0 +1,53 @@
var mockPackage = require('../mocks/mockPackage');
var Dgeni = require('dgeni');
var path = require('canonical-path');
var _ = require('lodash');
describe('createTypeDefinitionFile processor', function() {
var dgeni, injector, processor;
beforeEach(function() {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();
processor = injector.get('createTypeDefinitionFile');
// Initialize the processor
processor.typeDefinitions = [{
id: 'angular2/angular2',
modules: {
'angular2/angular2': {
id: 'angular2/angular2',
namespace: 'ng'
}
}
}];
});
describe('classes with @internal constructors', function() {
it('should convert heritage from `implements` into `extends`', function() {
// Create some mock docs for testing
var docs = [
{
id: 'angular2/angular2',
exports: [
{ docType: 'class', heritage: 'implements Xyz', constructorDoc: { internal: true } }
]
}
];
docs = processor.$process(docs);
expect(docs.length).toEqual(1);
expect(docs[0].docType).toEqual('type-definition');
var moduleDoc = docs[0].moduleDocs['angular2/angular2'].doc;
expect(moduleDoc.exports.length).toEqual(2);
expect(moduleDoc.exports[0].heritage).toEqual('extends Xyz');
});
});
});

View File

@ -0,0 +1,17 @@
{% extends '../type-definition.template.html' %}
{% block staticDeclarations %}
interface Map<K,V> {}
{% for alias, module in doc.moduleDocs %}
declare module {$ module.namespace $} {
// See https://github.com/Microsoft/TypeScript/issues/1168
class BaseException /* extends Error */ {
message: string;
stack: string;
toString(): string;
}
interface InjectableReference {}
}
{% endfor %}
{% endblock %}

View File

@ -0,0 +1,43 @@
{%- macro commentBlock(doc, level) -%}
{%- if doc.content | trim %}
{% if level > 1 %}{$ '/**' | indent(level-1, true) | replace(r/\n$/, "") $}{% else %}/**{% endif %}
{$ doc.content | trim | replace(r/^/gm, "* ") | indent(level, true) | replace(r/\n$/, "") $}
{$ '*/' | indent(level, true) | replace(r/\n$/, "") $}{% endif -%}
{%- endmacro -%}
// Type definitions for Angular v{$ versionInfo.currentVersion.full | replace(r/\+/, "_") $}
// Project: http://angular.io/
// Definitions by: angular team <https://github.com/angular/>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
// ***********************************************************
// This file is generated by the Angular build process.
// Please do not create manual edits or send pull requests
// modifying this file.
// ***********************************************************
{% for alias, module in doc.moduleDocs %}
{%- if module.references.length %}
// {$ alias $} depends transitively on these libraries.
// If you don't have them installed you can install them using TSD
// https://github.com/DefinitelyTyped/tsd
{%- endif %}
{% for reference in module.references %}
///<reference path="{$ reference $}"/>{% endfor %}{% endfor %}
{% block staticDeclarations %}{% endblock %}
{% for alias, module in doc.moduleDocs %}
{$ commentBlock(module.doc, 1) $}
declare module {$ module.namespace $} {
{%- for export in module.doc.exports -%}
{$ doc.dts.serializeExport(export) $}
{% endfor %}
}
declare module "{$ alias $}" {
export = {$ module.namespace $};
}
{% endfor %}

View File

@ -0,0 +1,74 @@
var basePackage = require('dgeni-packages/base');
var jsdocPackage = require('dgeni-packages/jsdoc');
var Package = require('dgeni').Package;
var path = require('canonical-path');
// Define the dgeni package for generating the docs
module.exports = new Package('typescript-parsing', [basePackage, jsdocPackage])
// Register the services and file readers
.factory(require('./services/modules'))
.factory(require('./services/tsParser'))
.factory(require('./services/tsParser/createCompilerHost'))
.factory(require('./services/tsParser/getFileInfo'))
.factory(require('./services/tsParser/getExportDocType'))
.factory(require('./services/tsParser/getContent'))
.factory(require('./services/convertPrivateClassesToInterfaces'))
.factory('EXPORT_DOC_TYPES', function() {
return [
'class',
'interface',
'function',
'var',
'const',
'enum',
'type-alias'
];
})
// Register the processors
.processor(require('./processors/readTypeScriptModules'))
// Configure the log service
.config(function(log) {
log.level = 'warn';
})
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push({ name: 'internal', transforms: function() { return true; } });
})
// Configure ids and paths
.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) {
computeIdsProcessor.idTemplates.push({
docTypes: ['member'],
idTemplate: '${classDoc.id}.${name}',
getAliases: function(doc) { return [doc.id]; }
});
computePathsProcessor.pathTemplates.push({
docTypes: ['member'],
pathTemplate: '${classDoc.path}/${name}',
getOutputPath: function() {} // These docs are not written to their own file, instead they are part of their class doc
});
var MODULES_DOCS_PATH = 'partials/modules';
computePathsProcessor.pathTemplates.push({
docTypes: ['module'],
pathTemplate: '/${id}',
outputPathTemplate: MODULES_DOCS_PATH + '/${id}/index.html'
});
computePathsProcessor.pathTemplates.push({
docTypes: EXPORT_DOC_TYPES,
pathTemplate: '${moduleDoc.path}/${name}',
outputPathTemplate: MODULES_DOCS_PATH + '/${path}/index.html'
});
});

View File

@ -0,0 +1,11 @@
var Package = require('dgeni').Package;
module.exports = function mockPackage() {
return new Package('mockPackage', [require('../')])
// provide a mock log service
.factory('log', function() { return require('dgeni/lib/mocks/log')(false); })
.factory('templateEngine', function() { return {}; });
};

View File

@ -0,0 +1,4 @@
export var __esModule = true;
export class OKToExport {}
export function _thisIsPrivate() {}
export var thisIsOK = '!';

View File

@ -0,0 +1,5 @@
export interface MyInterface {
optionalProperty? : string
<T, U extends Findable<T>>(param: T) : U
new (param: number) : MyInterface
}

View File

@ -0,0 +1,6 @@
export class Test {
firstItem;
constructor() { this.doStuff(); }
otherMethod() {}
doStuff() {}
}

View File

@ -0,0 +1 @@
export var x = 10;

View File

@ -0,0 +1 @@
export { x as y} from './privateModule';

View File

@ -0,0 +1 @@
export var x = 100;

View File

@ -0,0 +1,34 @@
/**
* @module
* @description
* This is the module description
*/
export * from 'importedSrc';
/**
* This is some random other comment
*/
/**
* This is MyClass
*/
export class MyClass {
message: String;
/**
* Create a new MyClass
* @param {String} name The name to say hello to
*/
constructor(name) { this.message = 'hello ' + name; }
/**
* Return a greeting message
*/
greet() { return this.message; }
}
/**
* An exported function
*/
export var myFn = (val: number) => return val * 2;

View File

@ -0,0 +1,413 @@
var glob = require('glob');
var path = require('canonical-path');
var _ = require('lodash');
var ts = require('typescript');
module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo,
getExportDocType, getContent, log) {
return {
$runAfter: ['files-read'],
$runBefore: ['parsing-tags'],
$validate: {
sourceFiles: {presence: true},
basePath: {presence: true},
hidePrivateMembers: {inclusion: [true, false]},
sortClassMembers: {inclusion: [true, false]},
ignoreExportsMatching: {}
},
// A collection of globs that identify those modules for which we should create docs
sourceFiles: [],
// The base path from which to load the source files
basePath: '.',
// We can ignore members of classes that are private
hidePrivateMembers: true,
// We leave class members sorted in order of declaration
sortClassMembers: false,
// We can provide a collection of strings or regexes to ignore exports whose export names match
ignoreExportsMatching: ['___esModule'],
$process: function(docs) {
// Convert ignoreExportsMatching to an array of regexes
var ignoreExportsMatching = convertToRegexCollection(this.ignoreExportsMatching);
var hidePrivateMembers = this.hidePrivateMembers;
var sortClassMembers = this.sortClassMembers;
var basePath = path.resolve(this.basePath);
var filesPaths = expandSourceFiles(this.sourceFiles, basePath);
var parseInfo = tsParser.parse(filesPaths, this.basePath);
var moduleSymbols = parseInfo.moduleSymbols;
// Iterate through each of the modules that were parsed and generate a module doc
// as well as docs for each module's exports.
moduleSymbols.forEach(function(moduleSymbol) {
var moduleDoc = createModuleDoc(moduleSymbol, basePath);
// Add this module doc to the module lookup collection and the docs collection
modules[moduleDoc.id] = moduleDoc;
docs.push(moduleDoc);
// Iterate through this module's exports and generate a doc for each
moduleSymbol.exportArray.forEach(function(exportSymbol) {
// Ignore exports starting with an underscore
if (anyMatches(ignoreExportsMatching, exportSymbol.name)) return;
// If the symbol is an Alias then for most things we want the original resolved symbol
var resolvedExport = exportSymbol.resolvedSymbol || exportSymbol;
var exportDoc = createExportDoc(exportSymbol.name, resolvedExport, moduleDoc, basePath, parseInfo.typeChecker);
log.debug('>>>> EXPORT: ' + exportDoc.name + ' (' + exportDoc.docType + ') from ' + moduleDoc.id);
exportDoc.members = [];
exportDoc.statics = [];
// Generate docs for each of the export's members
if (resolvedExport.flags & ts.SymbolFlags.HasMembers) {
for(var memberName in resolvedExport.members) {
// FIXME(alexeagle): why do generic type params appear in members?
if (memberName === 'T') {
continue;
}
log.silly('>>>>>> member: ' + memberName + ' from ' + exportDoc.id + ' in ' + moduleDoc.id);
var memberSymbol = resolvedExport.members[memberName];
var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, parseInfo.typeChecker);
// We special case the constructor and sort the other members alphabetically
if (memberSymbol.flags & ts.SymbolFlags.Constructor) {
exportDoc.constructorDoc = memberDoc;
docs.push(memberDoc);
} else if (!hidePrivateMembers || memberSymbol.name.charAt(0) !== '_') {
docs.push(memberDoc);
exportDoc.members.push(memberDoc);
} else if (memberSymbol.name === '__call' && memberSymbol.flags & ts.SymbolFlags.Signature) {
docs.push(memberDoc);
exportDoc.callMember = memberDoc;
} else if (memberSymbol.name === '__new' && memberSymbol.flags & ts.SymbolFlags.Signature) {
docs.push(memberDoc);
exportDoc.newMember = memberDoc;
}
}
}
if (exportDoc.docType === 'enum') {
for(var memberName in resolvedExport.exports) {
log.silly('>>>>>> member: ' + memberName + ' from ' + exportDoc.id + ' in ' + moduleDoc.id);
var memberSymbol = resolvedExport.exports[memberName];
var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, parseInfo.typeChecker);
docs.push(memberDoc);
exportDoc.members.push(memberDoc);
}
} else if (resolvedExport.flags & ts.SymbolFlags.HasExports) {
for (var exported in resolvedExport.exports) {
if (exported === 'prototype') continue;
if (hidePrivateMembers && exported.charAt(0) === '_') continue;
var memberSymbol = resolvedExport.exports[exported];
var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, parseInfo.typeChecker);
memberDoc.isStatic = true;
docs.push(memberDoc);
exportDoc.statics.push(memberDoc);
}
}
if (sortClassMembers) {
exportDoc.members.sort(function(a, b) {
if (a.name > b.name) return 1;
if (a.name < b.name) return -1;
return 0;
});
}
// Add this export doc to its module doc
moduleDoc.exports.push(exportDoc);
docs.push(exportDoc);
});
});
}
};
function createModuleDoc(moduleSymbol, basePath) {
var id = moduleSymbol.name.replace(/^"|"$/g, '');
var moduleDoc = {
docType: 'module',
id: id,
aliases: [id],
moduleTree: moduleSymbol,
content: getContent(moduleSymbol),
exports: [],
fileInfo: getFileInfo(moduleSymbol, basePath),
location: getLocation(moduleSymbol)
};
return moduleDoc;
}
function createExportDoc(name, exportSymbol, moduleDoc, basePath, typeChecker) {
var typeParamString = '';
var heritageString = '';
var typeDefinition = '';
exportSymbol.declarations.forEach(function(decl) {
var sourceFile = ts.getSourceFileOfNode(decl);
if (decl.typeParameters) {
typeParamString = '<' + getText(sourceFile, decl.typeParameters) + '>';
}
if (decl.symbol.flags & ts.SymbolFlags.TypeAlias) {
typeDefinition = getText(sourceFile, decl.type);
}
if (decl.heritageClauses) {
decl.heritageClauses.forEach(function(heritage) {
if (heritage.token == ts.SyntaxKind.ExtendsKeyword) {
heritageString += " extends";
heritage.types.forEach(function(typ, idx) {
heritageString += (idx > 0 ? ',' : '') + typ.getFullText();
});
}
if (heritage.token == ts.SyntaxKind.ImplementsKeyword) {
heritageString += " implements";
heritage.types.forEach(function(typ, idx) {
heritageString += (idx > 0 ? ', ' : '') + typ.getFullText();
});
}
});
}
});
//Make sure duplicate aliases aren't created, so "Ambiguous link" warnings are prevented
var aliasNames = [name, moduleDoc.id + '/' + name];
if (typeParamString) {
aliasNames.push(name + typeParamString);
aliasNames.push(moduleDoc.id + '/' + name + typeParamString);
}
var exportDoc = {
docType: getExportDocType(exportSymbol),
name: name,
id: moduleDoc.id + '/' + name,
typeParams: typeParamString,
heritage: heritageString,
decorators: getDecorators(exportSymbol),
aliases: aliasNames,
moduleDoc: moduleDoc,
content: getContent(exportSymbol),
fileInfo: getFileInfo(exportSymbol, basePath),
location: getLocation(exportSymbol)
};
if (exportDoc.docType === 'var' || exportDoc.docType === 'const') {
exportDoc.symbolTypeName = exportSymbol.valueDeclaration.type &&
exportSymbol.valueDeclaration.type.typeName &&
exportSymbol.valueDeclaration.type.typeName.text;
}
if (exportDoc.docType === 'type-alias') {
exportDoc.returnType = getReturnType(typeChecker, exportSymbol);
}
if(exportSymbol.flags & ts.SymbolFlags.Function) {
exportDoc.parameters = getParameters(typeChecker, exportSymbol);
}
if(exportSymbol.flags & ts.SymbolFlags.Value) {
exportDoc.returnType = getReturnType(typeChecker, exportSymbol);
}
if (exportSymbol.flags & ts.SymbolFlags.TypeAlias) {
exportDoc.typeDefinition = typeDefinition;
}
if (isAbstract(exportSymbol)) {
exportDoc.abstract = true;
}
// Compute the original module name from the relative file path
exportDoc.originalModule = exportDoc.fileInfo.relativePath
.replace(new RegExp('\.' + exportDoc.fileInfo.extension + '$'), '');
return exportDoc;
}
function createMemberDoc(memberSymbol, classDoc, basePath, typeChecker) {
var memberDoc = {
docType: 'member',
classDoc: classDoc,
name: memberSymbol.name,
decorators: getDecorators(memberSymbol),
content: getContent(memberSymbol),
fileInfo: getFileInfo(memberSymbol, basePath),
location: getLocation(memberSymbol)
};
memberDoc.typeParameters = getTypeParameters(typeChecker, memberSymbol);
if(memberSymbol.flags & (ts.SymbolFlags.Signature) ) {
memberDoc.parameters = getParameters(typeChecker, memberSymbol);
memberDoc.returnType = getReturnType(typeChecker, memberSymbol);
switch(memberDoc.name) {
case '__call':
memberDoc.name = '';
break;
case '__new':
memberDoc.name = 'new';
break;
}
}
if (memberSymbol.flags & ts.SymbolFlags.Method) {
// NOTE: we use the property name `parameters` here so we don't conflict
// with the `params` property that will be updated by dgeni reading the
// `@param` tags from the docs
memberDoc.parameters = getParameters(typeChecker, memberSymbol);
}
if (memberSymbol.flags & ts.SymbolFlags.Constructor) {
memberDoc.parameters = getParameters(typeChecker, memberSymbol);
memberDoc.name = 'constructor';
}
if(memberSymbol.flags & ts.SymbolFlags.Value) {
memberDoc.returnType = getReturnType(typeChecker, memberSymbol);
}
if(memberSymbol.flags & ts.SymbolFlags.Optional) {
memberDoc.optional = true;
}
return memberDoc;
}
function getDecorators(symbol) {
var declaration = symbol.valueDeclaration || symbol.declarations[0];
var sourceFile = ts.getSourceFileOfNode(declaration);
var decorators = declaration.decorators && declaration.decorators.map(function(decorator) {
decorator = decorator.expression;
return {
name: decorator.expression ? decorator.expression.text : decorator.text,
arguments: decorator.arguments && decorator.arguments.map(function(argument) {
return getText(sourceFile, argument).trim();
})
};
});
return decorators;
}
function getParameters(typeChecker, symbol) {
var declaration = symbol.valueDeclaration || symbol.declarations[0];
var sourceFile = ts.getSourceFileOfNode(declaration);
if (!declaration.parameters) {
var location = getLocation(symbol);
throw new Error('missing declaration parameters for "' + symbol.name +
'" in ' + sourceFile.fileName +
' at line ' + location.start.line);
}
return declaration.parameters.map(function(parameter) {
var paramText = '';
if (parameter.dotDotDotToken) {
paramText += '...';
}
paramText += getText(sourceFile, parameter.name);
if (parameter.questionToken || parameter.initializer) {
paramText += '?';
}
if (parameter.type) {
paramText += ':' + getType(sourceFile, parameter.type);
} else {
paramText += ': any';
if (parameter.dotDotDotToken) {
paramText += '[]';
}
}
return paramText.trim();
});
}
function getTypeParameters(typeChecker, symbol) {
var declaration = symbol.valueDeclaration || symbol.declarations[0];
var sourceFile = ts.getSourceFileOfNode(declaration);
if (!declaration.typeParameters) return;
var typeParams = declaration.typeParameters.map(function(type) {
return getText(sourceFile, type).trim();
});
return typeParams;
}
function getReturnType(typeChecker, symbol) {
var declaration = symbol.valueDeclaration || symbol.declarations[0];
var sourceFile = ts.getSourceFileOfNode(declaration);
if (declaration.type) {
return getType(sourceFile, declaration.type).trim();
}
}
function isAbstract(symbol) {
var declaration = symbol.valueDeclaration || symbol.declarations[0];
return declaration.flags & ts.NodeFlags.Abstract;
}
function expandSourceFiles(sourceFiles, basePath) {
var filePaths = [];
sourceFiles.forEach(function(sourcePattern) {
filePaths = filePaths.concat(glob.sync(sourcePattern, { cwd: basePath }));
});
return filePaths;
}
function getText(sourceFile, node) {
return sourceFile.text.substring(node.pos, node.end);
}
// Strip any local renamed imports from the front of types
function getType(sourceFile, type) {
var text = getText(sourceFile, type);
while (text.indexOf(".") >= 0) {
// Keep namespaced symbols in RxNext
if (text.match(/^\s*RxNext\./)) break;
// handle the case List<thing.stuff> -> List<stuff>
text = text.replace(/([^.<]*)\.([^>]*)/, "$2");
}
return text;
}
function getLocation(symbol) {
var node = symbol.valueDeclaration || symbol.declarations[0];
var sourceFile = ts.getSourceFileOfNode(node);
var location = {
start: ts.getLineAndCharacterOfPosition(sourceFile, node.pos),
end: ts.getLineAndCharacterOfPosition(sourceFile, node.end)
};
return location;
}
};
function convertToRegexCollection(items) {
if (!items) return [];
// Must be an array
if (!_.isArray(items)) {
items = [items];
}
// Convert string to exact matching regexes
return items.map(function(item) {
return _.isString(item) ? new RegExp('^' + item + '$') : item;
});
}
function anyMatches(regexes, item) {
for(var i=0; i<regexes.length; ++i) {
if ( item.match(regexes[i]) ) return true;
}
return false;
}

View File

@ -0,0 +1,127 @@
var mockPackage = require('../mocks/mockPackage');
var Dgeni = require('dgeni');
var path = require('canonical-path');
var _ = require('lodash');
describe('readTypeScriptModules', function() {
var dgeni, injector, processor;
beforeEach(function() {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();
processor = injector.get('readTypeScriptModules');
processor.basePath = path.resolve(__dirname, '../mocks/readTypeScriptModules');
});
describe('exportDocs', function() {
it('should provide the original module if the export is re-exported', function() {
processor.sourceFiles = [ 'publicModule.ts' ];
var docs = [];
processor.$process(docs);
var exportedDoc = docs[1];
expect(exportedDoc.originalModule).toEqual('privateModule');
});
});
describe('ignoreExportsMatching', function() {
it('should ignore exports that match items in the `ignoreExportsMatching` property', function() {
processor.sourceFiles = [ 'ignoreExportsMatching.ts'];
processor.ignoreExportsMatching = [/^_/];
var docs = [];
processor.$process(docs);
var moduleDoc = docs[0];
expect(moduleDoc.docType).toEqual('module');
expect(moduleDoc.exports).toEqual([
jasmine.objectContaining({ name: 'OKToExport' }),
jasmine.objectContaining({ name: 'thisIsOK' })
]);
});
it('should only ignore `___esModule` exports by default', function() {
processor.sourceFiles = [ 'ignoreExportsMatching.ts'];
var docs = [];
processor.$process(docs);
var moduleDoc = docs[0];
expect(moduleDoc.docType).toEqual('module');
expect(getNames(moduleDoc.exports)).toEqual([
'OKToExport',
'_thisIsPrivate',
'thisIsOK'
]);
});
});
describe('interfaces', function() {
it('should mark optional properties', function() {
processor.sourceFiles = [ 'interfaces.ts'];
var docs = [];
processor.$process(docs);
var moduleDoc = docs[0];
var exportedInterface = moduleDoc.exports[0];
var member = exportedInterface.members[0];
expect(member.name).toEqual('optionalProperty');
expect(member.optional).toEqual(true);
});
it('should handle "call" type interfaces', function() {
processor.sourceFiles = [ 'interfaces.ts'];
var docs = [];
processor.$process(docs);
var moduleDoc = docs[0];
var exportedInterface = moduleDoc.exports[0];
expect(exportedInterface.callMember).toBeDefined();
expect(exportedInterface.callMember.parameters).toEqual(['param: T']);
expect(exportedInterface.callMember.returnType).toEqual('U');
expect(exportedInterface.callMember.typeParameters).toEqual(['T', 'U extends Findable<T>']);
expect(exportedInterface.newMember).toBeDefined();
expect(exportedInterface.newMember.parameters).toEqual(['param: number']);
expect(exportedInterface.newMember.returnType).toEqual('MyInterface');
});
});
describe('ordering of members', function() {
it('should order class members in order of appearance (by default)', function() {
processor.sourceFiles = ['orderingOfMembers.ts'];
var docs = [];
processor.$process(docs);
var classDoc = _.find(docs, { docType: 'class' });
expect(classDoc.docType).toEqual('class');
expect(getNames(classDoc.members)).toEqual([
'firstItem',
'otherMethod',
'doStuff',
]);
});
it('should not order class members if not sortClassMembers is false', function() {
processor.sourceFiles = ['orderingOfMembers.ts'];
processor.sortClassMembers = false;
var docs = [];
processor.$process(docs);
var classDoc = _.find(docs, { docType: 'class' });
expect(classDoc.docType).toEqual('class');
expect(getNames(classDoc.members)).toEqual([
'firstItem',
'otherMethod',
'doStuff'
]);
});
});
});
function getNames(collection) {
return collection.map(function(item) { return item.name; });
}

View File

@ -0,0 +1,31 @@
var _ = require('lodash');
module.exports = function convertPrivateClassesToInterfaces() {
return function(exportDocs, addInjectableReference) {
_.forEach(exportDocs, function(exportDoc) {
// Search for classes with a constructor marked as `@internal`
if (exportDoc.docType === 'class' && exportDoc.constructorDoc && exportDoc.constructorDoc.internal) {
// Convert this class to an interface with no constructor
exportDoc.docType = 'interface';
exportDoc.constructorDoc = null;
if (exportDoc.heritage) {
// convert the heritage since interfaces use `extends` not `implements`
exportDoc.heritage = exportDoc.heritage.replace('implements', 'extends');
}
if (addInjectableReference) {
// Add the `declare var SomeClass extends InjectableReference` construct
exportDocs.push({
docType: 'var',
name: exportDoc.name,
id: exportDoc.id,
returnType: 'InjectableReference'
});
}
}
});
};
};

View File

@ -0,0 +1,76 @@
var mockPackage = require('../mocks/mockPackage');
var Dgeni = require('dgeni');
var _ = require('lodash');
describe('readTypeScriptModules', function() {
var dgeni, injector, convertPrivateClassesToInterfaces;
beforeEach(function() {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();
convertPrivateClassesToInterfaces = injector.get('convertPrivateClassesToInterfaces');
});
it('should convert @internal class docs to interface docs', function() {
var docs = [
{
docType: 'class',
name: 'privateClass',
id: 'privateClass',
constructorDoc: { internal: true }
}
];
convertPrivateClassesToInterfaces(docs, false);
expect(docs[0].docType).toEqual('interface');
});
it('should not touch non-internal class docs', function() {
var docs = [
{
docType: 'class',
name: 'privateClass',
id: 'privateClass',
constructorDoc: { }
}
];
convertPrivateClassesToInterfaces(docs, false);
expect(docs[0].docType).toEqual('class');
});
it('should convert the heritage since interfaces use `extends` not `implements`', function() {
var docs = [
{
docType: 'class',
name: 'privateClass',
id: 'privateClass',
constructorDoc: { internal: true },
heritage: 'implements parentInterface'
}
];
convertPrivateClassesToInterfaces(docs, false);
expect(docs[0].heritage).toEqual('extends parentInterface');
});
it('should add new injectable reference types, if specified, to the passed in collection', function() {
var docs = [
{
docType: 'class',
name: 'privateClass',
id: 'privateClass',
constructorDoc: { internal: true },
heritage: 'implements parentInterface'
}
];
convertPrivateClassesToInterfaces(docs, true);
expect(docs[1]).toEqual({
docType : 'var',
name : 'privateClass',
id : 'privateClass',
returnType : 'InjectableReference'
});
});
});

View File

@ -0,0 +1,3 @@
module.exports = function modules() {
return {};
};

View File

@ -0,0 +1,75 @@
var ts = require('typescript');
var fs = require('fs');
var path = require('canonical-path');
// We need to provide our own version of CompilerHost because we want to set the
// base directory and specify what extensions to consider when trying to load a source
// file
module.exports = function createCompilerHost(log) {
return function createCompilerHost(options, baseDir, extensions) {
return {
getSourceFile: function(fileName, languageVersion, onError) {
var text, resolvedPath, resolvedPathWithExt;
// Strip off the extension and resolve relative to the baseDir
baseFilePath = fileName.replace(/\.[^.]+$/, '');
resolvedPath = path.resolve(baseDir, baseFilePath);
// Iterate through each possible extension and return the first source file that is actually found
for(var i=0; i<extensions.length; i++) {
// Try reading the content from files using each of the given extensions
try {
resolvedPathWithExt = resolvedPath + extensions[i];
log.silly('getSourceFile:', resolvedPathWithExt);
text = fs.readFileSync(resolvedPathWithExt, { encoding: options.charset });
log.debug('found source file:', fileName, resolvedPathWithExt);
return ts.createSourceFile(baseFilePath + extensions[i], text, languageVersion);
}
catch(e) {
// Try again if the file simply did not exist, otherwise report the error as a warning
if(e.code !== 'ENOENT') {
if (onError) onError(e.message);
log.warn('Error reading ' + resolvedPathWithExt + ' : ' + e.message);
}
}
}
},
getDefaultLibFileName: function(options) {
return path.resolve(path.dirname(ts.sys.getExecutingFilePath()), ts.getDefaultLibFileName(options));
},
writeFile: function(fileName, data, writeByteOrderMark, onError) {
// no-op
},
getCurrentDirectory: function() {
return baseDir;
},
useCaseSensitiveFileNames: function() {
return ts.sys.useCaseSensitiveFileNames;
},
getCanonicalFileName: function(fileName) {
// if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form.
// otherwise use toLowerCase as a canonical form.
return ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
},
getNewLine: function() {
return ts.sys.newLine;
},
fileExists: function(fileName) {
var resolvedPath = path.resolve(baseDir, fileName);
try {
fs.statSync(resolvedPath);
return true;
} catch (e) {
return false;
}
},
readFile: function(fileName) {
var resolvedPath = path.resolve(baseDir, fileName);
return fs.readFileSync(resolvedPath, { encoding: options.charset });
}
};
};
};

View File

@ -0,0 +1,80 @@
var mockPackage = require('../../mocks/mockPackage');
var Dgeni = require('dgeni');
var path = require('canonical-path');
var ts = require('typescript');
describe('createCompilerHost', function() {
var dgeni, injector, options, host, baseDir, extensions;
beforeEach(function() {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();
var createCompilerHost = injector.get('createCompilerHost');
options = { charset: 'utf8' };
baseDir = path.resolve(__dirname, '../../mocks/tsParser');
extensions = ['.ts', '.js'];
host = createCompilerHost(options, baseDir, extensions);
});
describe('getSourceFile', function() {
it('should return a SourceFile object for a given path, with fileName relative to baseDir', function() {
var sourceFile = host.getSourceFile('testSrc.ts');
expect(sourceFile.fileName).toEqual('testSrc.ts');
expect(sourceFile.pos).toEqual(0);
expect(sourceFile.text).toEqual(jasmine.any(String));
});
it('should try each of the configured extensions and update the filename to the correct extension', function() {
var sourceFile = host.getSourceFile('testSrc.js');
expect(sourceFile.fileName).toEqual('testSrc.ts');
sourceFile = host.getSourceFile('../mockPackage.ts');
expect(sourceFile.fileName).toEqual('../mockPackage.js');
});
});
describe('getDefaultLibFileName', function() {
it('should return a path to the default library', function() {
expect(host.getDefaultLibFileName(options)).toContain('typescript/lib/lib.d.ts');
});
});
describe('writeFile', function() {
it('should do nothing', function() {
host.writeFile();
});
});
describe('getCurrentDirectory', function() {
it('should return the baseDir', function() {
expect(host.getCurrentDirectory()).toEqual(baseDir);
});
});
describe('useCaseSensitiveFileNames', function() {
it('should return true if the OS is case sensitive', function() {
expect(host.useCaseSensitiveFileNames()).toBe(ts.sys.useCaseSensitiveFileNames);
});
});
describe('getCanonicalFileName', function() {
it('should lower case the filename', function() {
var expectedFilePath = host.useCaseSensitiveFileNames() ? 'SomeFile.ts' : 'somefile.ts';
expect(host.getCanonicalFileName('SomeFile.ts')).toEqual(expectedFilePath);
});
});
describe('getNewLine', function() {
it('should return the newline character for the OS', function() {
expect(host.getNewLine()).toEqual(require('os').EOL);
});
});
});

View File

@ -0,0 +1,49 @@
var ts = require('typescript');
var LEADING_STAR = /^[^\S\r\n]*\*[^\S\n\r]?/gm;
module.exports = function getContent() {
return function(symbol) {
var content = "";
if (!symbol.declarations) return content;
symbol.declarations.forEach(function(declaration) {
// If this is left side of dotted module declaration, there is no doc comment associated with this declaration
if (declaration.kind === ts.SyntaxKind.ModuleDeclaration && declaration.body.kind === ts.SyntaxKind.ModuleDeclaration) {
return content;
}
// If this is dotted module name, get the doc comments from the parent
while (declaration.kind === ts.SyntaxKind.ModuleDeclaration && declaration.parent.kind === ts.SyntaxKind.ModuleDeclaration) {
declaration = declaration.parent;
}
// If this is a variable declaration then we get the doc comments from the grand parent
if (declaration.kind === ts.SyntaxKind.VariableDeclaration) {
declaration = declaration.parent.parent;
}
// Get the source file of this declaration
var sourceFile = ts.getSourceFileOfNode(declaration);
var commentRanges = ts.getJsDocComments(declaration, sourceFile);
if (commentRanges) {
commentRanges.forEach(function(commentRange) {
content += sourceFile.text
.substring(commentRange.pos+ '/**'.length, commentRange.end - '*/'.length)
.replace(LEADING_STAR, '')
.trim();
if (commentRange.hasTrailingNewLine) {
content += '\n';
}
});
}
content += '\n';
});
return content;
};
};

View File

@ -0,0 +1,54 @@
var ts = require('typescript');
module.exports = function getExportDocType(log) {
return function(symbol) {
if(symbol.flags & ts.SymbolFlags.Function) {
return 'function';
}
if(symbol.flags & ts.SymbolFlags.Class) {
return 'class';
}
if(symbol.flags & ts.SymbolFlags.Interface) {
return 'interface';
}
if(symbol.flags & ts.SymbolFlags.ConstEnum) {
return 'enum';
}
if(symbol.flags & ts.SymbolFlags.RegularEnum) {
return 'enum';
}
if(symbol.flags & ts.SymbolFlags.Property) {
return 'module-property';
}
if(symbol.flags & ts.SymbolFlags.TypeAlias) {
return 'type-alias';
}
if(symbol.flags & ts.SymbolFlags.FunctionScopedVariable) {
return 'var';
}
if(symbol.flags & ts.SymbolFlags.BlockScopedVariable) {
return getBlockScopedVariableDocType(symbol);
}
log.warn('getExportDocType(): Unknown symbol type', {
symbolName: symbol.name,
symbolType: symbol.flags,
symbolTarget: symbol.target,
file: ts.getSourceFileOfNode(symbol.declarations[0]).fileName
});
return 'unknown';
};
function getBlockScopedVariableDocType(symbol) {
var node = symbol.valueDeclaration;
while(node) {
if ( node.flags & 0x2000 /* const */) {
return 'const';
}
node = node.parent;
}
return 'let';
}
};

View File

@ -0,0 +1,20 @@
var path = require('canonical-path');
var ts = require('typescript');
module.exports = function getFileInfo(log) {
return function (symbol, basePath) {
var fileName = ts.getSourceFileOfNode(symbol.declarations[0]).fileName;
var file = path.resolve(basePath, fileName);
var fileInfo = {
filePath: file,
baseName: path.basename(file, path.extname(file)),
extension: path.extname(file).replace(/^\./, ''),
basePath: basePath,
relativePath: fileName,
projectRelativePath: fileName
};
return fileInfo;
};
};

View File

@ -0,0 +1,74 @@
var ts = require('typescript');
var path = require('canonical-path');
module.exports = function tsParser(createCompilerHost, log) {
return {
// These are the extension that we should consider when trying to load a module
// During migration from Traceur, there is a mix of `.ts`, `.es6` and `.js` (atScript)
// files in the project and the TypeScript compiler only looks for `.ts` files when trying
// to load imports.
extensions: ['.ts', '.js'],
// The options for the TS compiler
options: {
allowNonTsExtensions: true,
charset: 'utf8'
},
parse: function(fileNames, baseDir) {
// "Compile" a program from the given module filenames, to get hold of a
// typeChecker that can be used to interrogate the modules, exports and so on.
var host = createCompilerHost(this.options, baseDir, this.extensions);
var program = ts.createProgram(fileNames, this.options, host);
var typeChecker = program.getTypeChecker();
// Create an array of module symbols for each file we were given
var moduleSymbols = [];
fileNames.forEach(function(fileName) {
var sourceFile = program.getSourceFile(fileName);
if (!sourceFile) {
throw new Error('Invalid source file: ' + fileName);
} else if (!sourceFile.symbol) {
// Some files contain only a comment and no actual module code
log.warn('No module code found in ' + fileName);
} else {
moduleSymbols.push(sourceFile.symbol);
}
});
moduleSymbols.forEach(function(tsModule) {
// The type checker has a nice helper function that returns an array of Symbols
// representing the exports for a given module
tsModule.exportArray = typeChecker.getExportsOfModule(tsModule);
// Although 'star' imports (e.g. `export * from 'some/module';) get resolved automatically
// by the compiler/binder, it seems that explicit imports (e.g. `export {SomeClass} from 'some/module'`)
// do not so we have to do a little work.
tsModule.exportArray.forEach(function(moduleExport) {
if (moduleExport.flags & ts.SymbolFlags.Alias) {
// To maintain the alias information (particularly the alias name)
// we just attach the original "resolved" symbol to the alias symbol
moduleExport.resolvedSymbol = typeChecker.getAliasedSymbol(moduleExport);
}
});
});
moduleSymbols.typeChecker = typeChecker;
return {
moduleSymbols: moduleSymbols,
typeChecker: typeChecker,
program: program,
host: host
};
}
};
};

View File

@ -0,0 +1,21 @@
var mockPackage = require('../../mocks/mockPackage');
var Dgeni = require('dgeni');
var path = require('canonical-path');
describe('tsParser', function() {
var dgeni, injector, parser;
beforeEach(function() {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();
parser = injector.get('tsParser');
});
it("should parse a TS file", function() {
var parseInfo = parser.parse(['testSrc.ts'], path.resolve(__dirname, '../../mocks/tsParser'));
var tsModules = parseInfo.moduleSymbols;
expect(tsModules.length).toEqual(1);
expect(tsModules[0].exportArray.length).toEqual(3);
expect(tsModules[0].exportArray.map(function(i) { return i.name; })).toEqual(['MyClass', 'myFn', 'x']);
});
});

File diff suppressed because it is too large Load Diff

86
karma-dart-evalcache.js Normal file
View File

@ -0,0 +1,86 @@
// This module provides a customFileHandler for karma
// that serves files with urls like /packages_<timestamp>/...
// with maximum cache.
// We are using these urls when we spawn isolates
// so that the isolates don't reload files every time.
var common = require('karma/lib/middleware/common');
var fs = require('fs');
var DART_EVAL_PATH_RE = /.*\/packages_\d+\/(.*)$/;
module.exports = createFactory;
function createFactory(proxyPaths) {
return {
'framework:dart-evalcache': ['factory', dartEvalCacheFactory]
};
function dartEvalCacheFactory(emitter, logger, customFileHandlers) {
var filesPromise = new common.PromiseContainer();
emitter.on('file_list_modified', function(files) {
filesPromise.set(Promise.resolve(files));
});
var serveFile = common.createServeFile(fs);
var log = logger.create('dart-evalcache');
customFileHandlers.push({
urlRegex: DART_EVAL_PATH_RE,
handler: handler
});
// See source_files handler
function handler(request, response, fa, fb, basePath) {
return filesPromise.then(function(files) {
try {
var requestedFilePath = mapUrlToFile(request.url, proxyPaths, basePath, log);
// TODO(vojta): change served to be a map rather then an array
var file = findByPath(files.served, requestedFilePath);
if (file) {
serveFile(file.contentPath || file.path, response, function() {
common.setHeavyCacheHeaders(response);
}, file.content);
} else {
response.writeHead(404);
response.end('Not found');
}
} catch (e) {
log.error(e.stack);
response.writeHead(500);
response.end('Error', e.stack);
}
});
}
};
}
function mapUrlToFile(url, proxyPaths, basePath, log) {
var originalUrl = url;
url = url.indexOf('?') > -1 ? url.substring(0, url.indexOf('?')) : url;
var match = DART_EVAL_PATH_RE.exec(url);
var packagePath = match[1];
var result = null;
var lastProxyFromLength = 0;
Object.keys(proxyPaths).forEach(function(proxyFrom) {
if (startsWith(packagePath, proxyFrom) && proxyFrom.length > lastProxyFromLength) {
lastProxyFromLength = proxyFrom.length;
result = proxyPaths[proxyFrom] + packagePath.substring(proxyFrom.length);
}
});
return basePath + '/' + result;
}
function startsWith(string, subString) {
return string.length >= subString.length && string.slice(0, subString.length) === subString;
}
function findByPath(files, path) {
for (var i = 0; i < files.length; i++) {
if (files[i].path === path) {
return files[i];
}
}
return null;
}

82
karma-dart.conf.js Normal file
View File

@ -0,0 +1,82 @@
var sauceConf = require('./sauce.conf');
var packageSources = {
// Dependencies installed with `pub install`.
'unittest': 'packages/unittest',
'guinness': 'packages/guinness',
'matcher': 'packages/matcher',
'stack_trace': 'packages/stack_trace',
'collection': 'packages/collection',
'path': 'packages/path',
'observe': 'packages/observe',
'quiver': 'packages/quiver',
'intl': 'packages/intl',
'smoke': 'packages/smoke',
'logging': 'packages/logging',
'utf': 'packages/utf',
// Local dependencies, transpiled from the source.
'angular2': 'dist/dart/angular2/lib',
'angular2/test/': 'dist/dart/angular2/test/',
'http': 'dist/dart/http/lib',
'angular2_material': 'dist/dart/angular2_material/lib',
'benchpress': 'dist/dart/benchpress/lib',
'examples': 'dist/dart/examples/lib'
};
var proxyPaths = {};
Object.keys(packageSources).map(function(packageName) {
var filePath = packageSources[packageName];
proxyPaths['/packages/'+packageName] = '/base/'+filePath;
});
// Karma configuration
// Generated on Thu Sep 25 2014 11:52:02 GMT-0700 (PDT)
module.exports = function(config) {
config.set({
frameworks: ['dart-unittest', 'dart-evalcache'],
files: [
// Init and configure guiness.
{pattern: 'test-init.dart', included: true},
// Unit test files needs to be included.
{pattern: 'dist/dart/**/*_spec.dart', included: true, watched: false},
// Karma-dart via the dart-unittest framework generates
// `__adapter_unittest.dart` that imports these files.
{pattern: 'dist/dart/**', included: false, watched: false},
// Dependencies, installed with `pub install`.
{pattern: 'packages/**/*.dart', included: false, watched: false},
// Init and configure guiness.
{pattern: 'test-main.dart', included: true},
{pattern: 'modules/**/test/**/static_assets/**', included: false, watched: false},
],
exclude: [
'dist/dart/**/packages/**',
'modules/angular1_router/**'
],
karmaDartImports: {
guinness: 'package:guinness/guinness_html.dart'
},
// Map packages to the correct urls where Karma serves them.
proxies: proxyPaths,
customLaunchers: sauceConf.customLaunchers,
browsers: ['DartiumWithWebPlatform'],
port: 9877,
plugins: [
require('karma-dart'),
require('karma-chrome-launcher'),
require('karma-sauce-launcher'),
require('./karma-dart-evalcache')(packageSources)
]
});
};

View File

@ -1,5 +1,4 @@
var browserProvidersConf = require('./browser-providers.conf.js');
var internalAngularReporter = require('./tools/karma/reporter.js');
var sauceConf = require('./sauce.conf');
// Karma configuration
// Generated on Thu Sep 25 2014 11:52:02 GMT-0700 (PDT)
@ -11,101 +10,56 @@ module.exports = function(config) {
files: [
// Sources and specs.
// Loaded through the System loader, in `test-main.js`.
{pattern: 'dist/all/@angular/**/*.js', included: false, watched: true},
{pattern: 'dist/all/angular2/**/*.js', included: false, watched: true},
{pattern: 'dist/js/dev/es5/**', included: false, watched: false},
'node_modules/es6-shim/es6-shim.js',
// include Angular v1 for upgrade module testing
'node_modules/angular/angular.min.js',
'node_modules/zone.js/dist/zone.js',
// zone-microtask must be included first as it contains a Promise monkey patch
'node_modules/zone.js/dist/zone-microtask.js',
'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js',
// Including systemjs because it defines `__eval`, which produces correct stack traces.
'shims_for_IE.js',
'modules/angular2/src/testing/shims_for_IE.js',
'node_modules/systemjs/dist/system.src.js',
{pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true},
{pattern: 'node_modules/@reactivex/rxjs/**', included: false, watched: false, served: true},
'node_modules/reflect-metadata/Reflect.js',
'tools/build/file2modulename.js',
'test-main.js',
{pattern: 'dist/all/empty.*', included: false, watched: false},
{pattern: 'modules/@angular/platform-browser/test/static_assets/**', included: false, watched: false},
{pattern: 'modules/@angular/platform-browser-dynamic/test/browser/static_assets/**', included: false, watched: false}
{pattern: 'modules/**/test/**/static_assets/**', included: false, watched: false}
],
exclude: [
'dist/all/@angular/**/e2e_test/**',
'dist/all/@angular/examples/**',
'dist/all/angular1_router.js',
'dist/all/@angular/platform-browser/testing/e2e_util.js'
],
exclude: ['dist/js/dev/es5/**/e2e_test/**', 'dist/js/dev/es5/angular2/examples/**', 'dist/angular1_router.js'],
customLaunchers: browserProvidersConf.customLaunchers,
customLaunchers: sauceConf.customLaunchers,
plugins: [
'karma-jasmine',
'karma-browserstack-launcher',
'karma-sauce-launcher',
'karma-chrome-launcher',
'karma-sourcemap-loader',
'karma-dart',
internalAngularReporter
],
preprocessors: {
'**/*.js': ['sourcemap']
},
reporters: ['internal-angular'],
sauceLabs: {
testName: 'Angular2',
retryLimit: 3,
startConnect: false,
recordVideo: false,
recordScreenshots: false,
options: {
'selenium-version': '2.48.2',
'selenium-version': '2.47.1',
'command-timeout': 600,
'idle-timeout': 600,
'max-duration': 5400
}
},
browserStack: {
project: 'Angular2',
startTunnel: false,
retryLimit: 3,
timeout: 600,
pollingTimeout: 10000
},
browsers: ['Chrome'],
port: 9876,
captureTimeout: 60000,
browserDisconnectTimeout : 60000,
browserDisconnectTolerance : 3,
browserNoActivityTimeout : 60000,
port: 9876
});
if (process.env.TRAVIS) {
var buildId = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
if (process.env.CI_MODE.startsWith('saucelabs')) {
config.sauceLabs.build = buildId;
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
if (process.env.TRAVIS && process.env.MODE === 'saucelabs') {
config.sauceLabs.build = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
// TODO(mlaval): remove once SauceLabs supports websockets.
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
console.log('>>>> setting socket.io transport to polling <<<<');
config.transports = ['polling'];
}
if (process.env.CI_MODE.startsWith('browserstack')) {
config.browserStack.build = buildId;
config.browserStack.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
}
// TODO(mlaval): remove once SauceLabs supports websockets.
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
console.log('>>>> setting socket.io transport to polling <<<<');
config.transports = ['polling'];
}
};

View File

@ -1 +0,0 @@
export 'index.dart';

View File

@ -1,5 +0,0 @@
export * from './src/pipes';
export * from './src/directives';
export * from './src/forms';
export * from './src/common_directives';
export * from './src/location';

View File

@ -1,13 +0,0 @@
{
"name": "@angular/common",
"version": "$$ANGULAR_VERSION$$",
"description": "",
"main": "index.js",
"jsnext:main": "esm/index.js",
"typings": "index.d.ts",
"author": "angular",
"license": "MIT",
"peerDependencies": {
"@angular/core": "$$ANGULAR_VERSION$$"
}
}

View File

@ -1,17 +0,0 @@
export default {
entry: '../../../dist/packages-dist/common/esm/index.js',
dest: '../../../dist/packages-dist/common/esm/common.umd.js',
format: 'umd',
moduleName: 'ng.common',
globals: {
'@angular/core': 'ng.core',
'rxjs/Subject': 'Rx',
'rxjs/observable/PromiseObservable': 'Rx', // this is wrong, but this stuff has changed in rxjs b.6 so we need to fix it when we update.
'rxjs/operator/toPromise': 'Rx.Observable.prototype',
'rxjs/Observable': 'Rx'
},
plugins: [
// nodeResolve({ jsnext: true, main: true }),
]
}

View File

@ -1,48 +0,0 @@
import {Type} from '@angular/core';
import {FORM_DIRECTIVES} from './forms';
import {CORE_DIRECTIVES} from './directives';
/**
* A collection of Angular core directives that are likely to be used in each and every Angular
* application. This includes core directives (e.g., NgIf and NgFor), and forms directives (e.g.,
* NgModel).
*
* This collection can be used to quickly enumerate all the built-in directives in the `directives`
* property of the `@Component` decorator.
*
* ### Example
*
* Instead of writing:
*
* ```typescript
* import {NgClass, NgIf, NgFor, NgSwitch, NgSwitchWhen, NgSwitchDefault, NgModel, NgForm} from
* '@angular/common';
* import {OtherDirective} from './myDirectives';
*
* @Component({
* selector: 'my-component',
* templateUrl: 'myComponent.html',
* directives: [NgClass, NgIf, NgFor, NgSwitch, NgSwitchWhen, NgSwitchDefault, NgModel, NgForm,
* OtherDirective]
* })
* export class MyComponent {
* ...
* }
* ```
* one could import all the common directives at once:
*
* ```typescript
* import {COMMON_DIRECTIVES} from '@angular/common';
* import {OtherDirective} from './myDirectives';
*
* @Component({
* selector: 'my-component',
* templateUrl: 'myComponent.html',
* directives: [COMMON_DIRECTIVES, OtherDirective]
* })
* export class MyComponent {
* ...
* }
* ```
*/
export const COMMON_DIRECTIVES: Type[][] = /*@ts2dart_const*/[CORE_DIRECTIVES, FORM_DIRECTIVES];

View File

@ -1,14 +0,0 @@
/**
* @module
* @description
* Common directives shipped with Angular.
*/
export {NgClass} from './directives/ng_class';
export {NgFor} from './directives/ng_for';
export {NgIf} from './directives/ng_if';
export {NgTemplateOutlet} from './directives/ng_template_outlet';
export {NgStyle} from './directives/ng_style';
export {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './directives/ng_switch';
export {NgPlural, NgPluralCase, NgLocalization} from './directives/ng_plural';
export * from './directives/observable_list_diff';
export {CORE_DIRECTIVES} from './directives/core_directives';

View File

@ -1,195 +0,0 @@
import {
DoCheck,
Directive,
ChangeDetectorRef,
IterableDiffer,
IterableDiffers,
ViewContainerRef,
TemplateRef,
EmbeddedViewRef,
TrackByFn,
DefaultIterableDiffer,
CollectionChangeRecord
} from '@angular/core';
import {isPresent, isBlank, getTypeNameForDebugging} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions';
export class NgForRow {
constructor(public $implicit: any, public index: number, public count: number) {}
get first(): boolean { return this.index === 0; }
get last(): boolean { return this.index === this.count - 1; }
get even(): boolean { return this.index % 2 === 0; }
get odd(): boolean { return !this.even; }
}
/**
* The `NgFor` directive instantiates a template once per item from an iterable. The context for
* each instantiated template inherits from the outer context with the given loop variable set
* to the current item from the iterable.
*
* ### Local Variables
*
* `NgFor` provides several exported values that can be aliased to local variables:
*
* * `index` will be set to the current loop iteration for each template context.
* * `first` will be set to a boolean value indicating whether the item is the first one in the
* iteration.
* * `last` will be set to a boolean value indicating whether the item is the last one in the
* iteration.
* * `even` will be set to a boolean value indicating whether this item has an even index.
* * `odd` will be set to a boolean value indicating whether this item has an odd index.
*
* ### Change Propagation
*
* When the contents of the iterator changes, `NgFor` makes the corresponding changes to the DOM:
*
* * When an item is added, a new instance of the template is added to the DOM.
* * When an item is removed, its template instance is removed from the DOM.
* * When items are reordered, their respective templates are reordered in the DOM.
* * Otherwise, the DOM element for that item will remain the same.
*
* Angular uses object identity to track insertions and deletions within the iterator and reproduce
* those changes in the DOM. This has important implications for animations and any stateful
* controls
* (such as `<input>` elements which accept user input) that are present. Inserted rows can be
* animated in, deleted rows can be animated out, and unchanged rows retain any unsaved state such
* as user input.
*
* It is possible for the identities of elements in the iterator to change while the data does not.
* This can happen, for example, if the iterator produced from an RPC to the server, and that
* RPC is re-run. Even if the data hasn't changed, the second response will produce objects with
* different identities, and Angular will tear down the entire DOM and rebuild it (as if all old
* elements were deleted and all new elements inserted). This is an expensive operation and should
* be avoided if possible.
*
* ### Syntax
*
* - `<li *ngFor="let item of items; let i = index">...</li>`
* - `<li template="ngFor let item of items; let i = index">...</li>`
* - `<template ngFor let-item [ngForOf]="items" let-i="index"><li>...</li></template>`
*
* ### Example
*
* See a [live demo](http://plnkr.co/edit/KVuXxDp0qinGDyo307QW?p=preview) for a more detailed
* example.
*/
@Directive({selector: '[ngFor][ngForOf]', inputs: ['ngForTrackBy', 'ngForOf', 'ngForTemplate']})
export class NgFor implements DoCheck {
/** @internal */
_ngForOf: any;
/** @internal */
_ngForTrackBy: TrackByFn;
private _differ: IterableDiffer;
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<NgForRow>,
private _iterableDiffers: IterableDiffers, private _cdr: ChangeDetectorRef) {}
set ngForOf(value: any) {
this._ngForOf = value;
if (isBlank(this._differ) && isPresent(value)) {
try {
this._differ = this._iterableDiffers.find(value).create(this._cdr, this._ngForTrackBy);
} catch (e) {
throw new BaseException(
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
}
}
}
set ngForTemplate(value: TemplateRef<NgForRow>) {
if (isPresent(value)) {
this._templateRef = value;
}
}
set ngForTrackBy(value: TrackByFn) { this._ngForTrackBy = value; }
ngDoCheck() {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._ngForOf);
if (isPresent(changes)) this._applyChanges(changes);
}
}
private _applyChanges(changes: DefaultIterableDiffer) {
// TODO(rado): check if change detection can produce a change record that is
// easier to consume than current.
var recordViewTuples: RecordViewTuple[] = [];
changes.forEachRemovedItem((removedRecord: CollectionChangeRecord) =>
recordViewTuples.push(new RecordViewTuple(removedRecord, null)));
changes.forEachMovedItem((movedRecord: CollectionChangeRecord) =>
recordViewTuples.push(new RecordViewTuple(movedRecord, null)));
var insertTuples = this._bulkRemove(recordViewTuples);
changes.forEachAddedItem((addedRecord: CollectionChangeRecord) =>
insertTuples.push(new RecordViewTuple(addedRecord, null)));
this._bulkInsert(insertTuples);
for (var i = 0; i < insertTuples.length; i++) {
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
}
for (var i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
var viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i);
viewRef.context.index = i;
viewRef.context.count = ilen;
}
changes.forEachIdentityChange((record) => {
var viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex);
viewRef.context.$implicit = record.item;
});
}
private _perViewChange(view: EmbeddedViewRef<NgForRow>, record: CollectionChangeRecord) {
view.context.$implicit = record.item;
}
private _bulkRemove(tuples: RecordViewTuple[]): RecordViewTuple[] {
tuples.sort((a: RecordViewTuple, b: RecordViewTuple) =>
a.record.previousIndex - b.record.previousIndex);
var movedTuples: RecordViewTuple[] = [];
for (var i = tuples.length - 1; i >= 0; i--) {
var tuple = tuples[i];
// separate moved views from removed views.
if (isPresent(tuple.record.currentIndex)) {
tuple.view =
<EmbeddedViewRef<NgForRow>>this._viewContainer.detach(tuple.record.previousIndex);
movedTuples.push(tuple);
} else {
this._viewContainer.remove(tuple.record.previousIndex);
}
}
return movedTuples;
}
private _bulkInsert(tuples: RecordViewTuple[]): RecordViewTuple[] {
tuples.sort((a, b) => a.record.currentIndex - b.record.currentIndex);
for (var i = 0; i < tuples.length; i++) {
var tuple = tuples[i];
if (isPresent(tuple.view)) {
this._viewContainer.insert(tuple.view, tuple.record.currentIndex);
} else {
tuple.view = this._viewContainer.createEmbeddedView(
this._templateRef, new NgForRow(null, null, null), tuple.record.currentIndex);
}
}
return tuples;
}
}
class RecordViewTuple {
view: EmbeddedViewRef<NgForRow>;
record: any;
constructor(record: any, view: EmbeddedViewRef<NgForRow>) {
this.record = record;
this.view = view;
}
}

View File

@ -1,148 +0,0 @@
import {
Directive,
ViewContainerRef,
TemplateRef,
ContentChildren,
QueryList,
Attribute,
AfterContentInit,
Input
} from '@angular/core';
import {isPresent, NumberWrapper} from '../../src/facade/lang';
import {Map} from '../../src/facade/collection';
import {SwitchView} from './ng_switch';
const _CATEGORY_DEFAULT = 'other';
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
/**
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression
* value, or failing that, DOM sub-trees that match the switch expression's pluralization category.
*
* To use this directive, you must provide an extension of `NgLocalization` that maps values to
* category names. You then define a container element that sets the `[ngPlural]` attribute to a
* switch expression.
* - Inner elements defined with an `[ngPluralCase]` attribute will display based on their
* expression.
* - If `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value
* matches the switch expression exactly.
* - Otherwise, the view will be treated as a "category match", and will only display if exact
* value matches aren't found and the value maps to its category using the `getPluralCategory`
* function provided.
*
* If no matching views are found for a switch expression, inner elements marked
* `[ngPluralCase]="other"` will be displayed.
*
* ```typescript
* class MyLocalization extends NgLocalization {
* getPluralCategory(value: any) {
* if(value < 5) {
* return 'few';
* }
* }
* }
*
* @Component({
* selector: 'app',
* providers: [provide(NgLocalization, {useClass: MyLocalization})]
* })
* @View({
* template: `
* <p>Value = {{value}}</p>
* <button (click)="inc()">Increment</button>
*
* <div [ngPlural]="value">
* <template ngPluralCase="=0">there is nothing</template>
* <template ngPluralCase="=1">there is one</template>
* <template ngPluralCase="few">there are a few</template>
* <template ngPluralCase="other">there is some number</template>
* </div>
* `,
* directives: [NgPlural, NgPluralCase]
* })
* export class App {
* value = 'init';
*
* inc() {
* this.value = this.value === 'init' ? 0 : this.value + 1;
* }
* }
*
* ```
*/
@Directive({selector: '[ngPluralCase]'})
export class NgPluralCase {
/** @internal */
_view: SwitchView;
constructor(@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
viewContainer: ViewContainerRef) {
this._view = new SwitchView(viewContainer, template);
}
}
@Directive({selector: '[ngPlural]'})
export class NgPlural implements AfterContentInit {
private _switchValue: number;
private _activeView: SwitchView;
private _caseViews = new Map<any, SwitchView>();
@ContentChildren(NgPluralCase) cases: QueryList<NgPluralCase> = null;
constructor(private _localization: NgLocalization) {}
@Input()
set ngPlural(value: number) {
this._switchValue = value;
this._updateView();
}
ngAfterContentInit() {
this.cases.forEach((pluralCase: NgPluralCase): void => {
this._caseViews.set(this._formatValue(pluralCase), pluralCase._view);
});
this._updateView();
}
/** @internal */
_updateView(): void {
this._clearViews();
var view: SwitchView = this._caseViews.get(this._switchValue);
if (!isPresent(view)) view = this._getCategoryView(this._switchValue);
this._activateView(view);
}
/** @internal */
_clearViews() {
if (isPresent(this._activeView)) this._activeView.destroy();
}
/** @internal */
_activateView(view: SwitchView) {
if (!isPresent(view)) return;
this._activeView = view;
this._activeView.create();
}
/** @internal */
_getCategoryView(value: number): SwitchView {
var category: string = this._localization.getPluralCategory(value);
var categoryView: SwitchView = this._caseViews.get(category);
return isPresent(categoryView) ? categoryView : this._caseViews.get(_CATEGORY_DEFAULT);
}
/** @internal */
_isValueView(pluralCase: NgPluralCase): boolean { return pluralCase.value[0] === "="; }
/** @internal */
_formatValue(pluralCase: NgPluralCase): any {
return this._isValueView(pluralCase) ? this._stripValue(pluralCase.value) : pluralCase.value;
}
/** @internal */
_stripValue(value: string): number { return NumberWrapper.parseInt(value.substring(1), 10); }
}

View File

@ -1,26 +0,0 @@
import {Directive, Input, ViewContainerRef, ViewRef, TemplateRef} from '@angular/core';
import {isPresent} from '../../src/facade/lang';
/**
* Creates and inserts an embedded view based on a prepared `TemplateRef`.
*
* ### Syntax
* - `<template [ngTemplateOutlet]="templateRefExpression"></template>`
*/
@Directive({selector: '[ngTemplateOutlet]'})
export class NgTemplateOutlet {
private _insertedViewRef: ViewRef;
constructor(private _viewContainerRef: ViewContainerRef) {}
@Input()
set ngTemplateOutlet(templateRef: TemplateRef<Object>) {
if (isPresent(this._insertedViewRef)) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._insertedViewRef));
}
if (isPresent(templateRef)) {
this._insertedViewRef = this._viewContainerRef.createEmbeddedView(templateRef);
}
}
}

View File

@ -1,10 +0,0 @@
// TS does not have Observables
// I need to be here to make TypeScript think this is a module.
import {} from '../../src/facade/lang';
/**
* This module exists in Dart, but not in Typescript. This exported symbol
* is only here to help Typescript think this is a module.
*/
export var workaround_empty_observable_list_diff: any;

View File

@ -1 +0,0 @@
../../facade/src

View File

@ -1,35 +0,0 @@
import {Directive, Renderer, ElementRef, Self, forwardRef, Provider} from '@angular/core';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
export const CHECKBOX_VALUE_ACCESSOR: any = /*@ts2dart_const*/ {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckboxControlValueAccessor),
multi: true
};
/**
* The accessor for writing a value and listening to changes on a checkbox input element.
*
* ### Example
* ```
* <input type="checkbox" ngControl="rememberLogin">
* ```
*/
@Directive({
selector:
'input[type=checkbox][ngControl],input[type=checkbox][ngFormControl],input[type=checkbox][ngModel]',
host: {'(change)': 'onChange($event.target.checked)', '(blur)': 'onTouched()'},
providers: [CHECKBOX_VALUE_ACCESSOR]
})
export class CheckboxControlValueAccessor implements ControlValueAccessor {
onChange = (_: any) => {};
onTouched = () => {};
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
writeValue(value: any): void {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', value);
}
registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
}

View File

@ -1,34 +0,0 @@
import {OpaqueToken} from '@angular/core';
/**
* A bridge between a control and a native element.
*
* A `ControlValueAccessor` abstracts the operations of writing a new value to a
* DOM element representing an input control.
*
* Please see {@link DefaultValueAccessor} for more information.
*/
export interface ControlValueAccessor {
/**
* Write a new value to the element.
*/
writeValue(obj: any): void;
/**
* Set the function to be called when the control receives a change event.
*/
registerOnChange(fn: any): void;
/**
* Set the function to be called when the control receives a touch event.
*/
registerOnTouched(fn: any): void;
}
/**
* Used to provide a {@link ControlValueAccessor} for form controls.
*
* See {@link DefaultValueAccessor} for how to implement one.
*/
export const NG_VALUE_ACCESSOR: OpaqueToken =
/*@ts2dart_const*/ new OpaqueToken("NgValueAccessor");

View File

@ -1,43 +0,0 @@
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
import {isBlank} from '../../../src/facade/lang';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
export const DEFAULT_VALUE_ACCESSOR: any = /*@ts2dart_const*/
/* @ts2dart_Provider */ {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
/**
* The default accessor for writing a value and listening to changes that is used by the
* {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives.
*
* ### Example
* ```
* <input type="text" ngControl="searchQuery">
* ```
*/
@Directive({
selector:
'input:not([type=checkbox])[ngControl],textarea[ngControl],input:not([type=checkbox])[ngFormControl],textarea[ngFormControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]',
// TODO: vsavkin replace the above selector with the one below it once
// https://github.com/angular/angular/issues/3011 is implemented
// selector: '[ngControl],[ngModel],[ngFormControl]',
host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
bindings: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {
onChange = (_: any) => {};
onTouched = () => {};
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
writeValue(value: any): void {
var normalizedValue = isBlank(value) ? '' : value;
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
}
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

View File

@ -1,111 +0,0 @@
import {
OnInit,
OnDestroy,
Directive,
Optional,
Inject,
Host,
SkipSelf,
forwardRef,
Provider,
Self
} from '@angular/core';
import {ControlContainer} from './control_container';
import {controlPath, composeValidators, composeAsyncValidators} from './shared';
import {ControlGroup} from '../model';
import {Form} from './form_interface';
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {AsyncValidatorFn, ValidatorFn} from './validators';
export const controlGroupProvider: any =
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
provide: ControlContainer,
useExisting: forwardRef(() => NgControlGroup)
};
/**
* Creates and binds a control group to a DOM element.
*
* This directive can only be used as a child of {@link NgForm} or {@link NgFormModel}.
*
* ### Example ([live demo](http://plnkr.co/edit/7EJ11uGeaggViYM6T5nq?p=preview))
*
* ```typescript
* @Component({
* selector: 'my-app',
* directives: [FORM_DIRECTIVES],
* template: `
* <div>
* <h2>Angular Control &amp; ControlGroup Example</h2>
* <form #f="ngForm">
* <div ngControlGroup="name" #cg-name="form">
* <h3>Enter your name:</h3>
* <p>First: <input ngControl="first" required></p>
* <p>Middle: <input ngControl="middle"></p>
* <p>Last: <input ngControl="last" required></p>
* </div>
* <h3>Name value:</h3>
* <pre>{{valueOf(cgName)}}</pre>
* <p>Name is {{cgName?.control?.valid ? "valid" : "invalid"}}</p>
* <h3>What's your favorite food?</h3>
* <p><input ngControl="food"></p>
* <h3>Form value</h3>
* <pre>{{valueOf(f)}}</pre>
* </form>
* </div>
* `
* })
* export class App {
* valueOf(cg: NgControlGroup): string {
* if (cg.control == null) {
* return null;
* }
* return JSON.stringify(cg.control.value, null, 2);
* }
* }
* ```
*
* This example declares a control group for a user's name. The value and validation state of
* this group can be accessed separately from the overall form.
*/
@Directive({
selector: '[ngControlGroup]',
providers: [controlGroupProvider],
inputs: ['name: ngControlGroup'],
exportAs: 'ngForm'
})
export class NgControlGroup extends ControlContainer implements OnInit,
OnDestroy {
/** @internal */
_parent: ControlContainer;
constructor(@Host() @SkipSelf() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
super();
this._parent = parent;
}
ngOnInit(): void { this.formDirective.addControlGroup(this); }
ngOnDestroy(): void { this.formDirective.removeControlGroup(this); }
/**
* Get the {@link ControlGroup} backing this binding.
*/
get control(): ControlGroup { return this.formDirective.getControlGroup(this); }
/**
* Get the path to this control group.
*/
get path(): string[] { return controlPath(this.name, this._parent); }
/**
* Get the {@link Form} to which this group belongs.
*/
get formDirective(): Form { return this._parent.formDirective; }
get validator(): ValidatorFn { return composeValidators(this._validators); }
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
}

View File

@ -1,101 +0,0 @@
import {
OnChanges,
SimpleChange,
Directive,
forwardRef,
Provider,
Inject,
Optional,
Self
} from '@angular/core';
import {EventEmitter, ObservableWrapper} from '../../../src/facade/async';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {NgControl} from './ng_control';
import {Control} from '../model';
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {
setUpControl,
isPropertyUpdated,
selectValueAccessor,
composeValidators,
composeAsyncValidators
} from './shared';
import {ValidatorFn, AsyncValidatorFn} from './validators';
export const formControlBinding: any =
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
provide: NgControl,
useExisting: forwardRef(() => NgModel)
};
/**
* Binds a domain model to a form control.
*
* ### Usage
*
* `ngModel` binds an existing domain model to a form control. For a
* two-way binding, use `[(ngModel)]` to ensure the model updates in
* both directions.
*
* ### Example ([live demo](http://plnkr.co/edit/R3UX5qDaUqFO2VYR0UzH?p=preview))
* ```typescript
* @Component({
* selector: "search-comp",
* directives: [FORM_DIRECTIVES],
* template: `<input type='text' [(ngModel)]="searchQuery">`
* })
* class SearchComp {
* searchQuery: string;
* }
* ```
*/
@Directive({
selector: '[ngModel]:not([ngControl]):not([ngFormControl])',
bindings: [formControlBinding],
inputs: ['model: ngModel'],
outputs: ['update: ngModelChange'],
exportAs: 'ngForm'
})
export class NgModel extends NgControl implements OnChanges {
/** @internal */
_control = new Control();
/** @internal */
_added = false;
update = new EventEmitter();
model: any;
viewModel: any;
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[]) {
super();
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
ngOnChanges(changes: {[key: string]: SimpleChange}) {
if (!this._added) {
setUpControl(this._control, this);
this._control.updateValueAndValidity({emitEvent: false});
this._added = true;
}
if (isPropertyUpdated(changes, this.viewModel)) {
this._control.updateValue(this.model);
this.viewModel = this.model;
}
}
get control(): Control { return this._control; }
get path(): string[] { return []; }
get validator(): ValidatorFn { return composeValidators(this._validators); }
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
ObservableWrapper.callEmit(this.update, newValue);
}
}

View File

@ -1,20 +0,0 @@
library angular2.core.forms.normalize_validators;
import 'package:angular2/src/common/forms/directives/validators.dart' show Validator;
Function normalizeValidator(dynamic validator){
if (validator is Validator) {
return (c) => validator.validate(c);
} else {
return validator;
}
}
Function normalizeAsyncValidator(dynamic validator){
if (validator is Validator) {
return (c) => validator.validate(c);
} else {
return validator;
}
}

View File

@ -1,18 +0,0 @@
import {AbstractControl} from '../model';
import {Validator, ValidatorFn, AsyncValidatorFn} from './validators';
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
if ((<Validator>validator).validate !== undefined) {
return (c: AbstractControl) => (<Validator>validator).validate(c);
} else {
return <ValidatorFn>validator;
}
}
export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn {
if ((<Validator>validator).validate !== undefined) {
return (c: AbstractControl) => Promise.resolve((<Validator>validator).validate(c));
} else {
return <AsyncValidatorFn>validator;
}
}

View File

@ -1,44 +0,0 @@
import {Directive, ElementRef, Renderer, Self, forwardRef, Provider} from '@angular/core';
import {NumberWrapper} from '../../../src/facade/lang';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
export const NUMBER_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NumberValueAccessor),
multi: true
};
/**
* The accessor for writing a number value and listening to changes that is used by the
* {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives.
*
* ### Example
* ```
* <input type="number" [(ngModel)]="age">
* ```
*/
@Directive({
selector:
'input[type=number][ngControl],input[type=number][ngFormControl],input[type=number][ngModel]',
host: {
'(change)': 'onChange($event.target.value)',
'(input)': 'onChange($event.target.value)',
'(blur)': 'onTouched()'
},
bindings: [NUMBER_VALUE_ACCESSOR]
})
export class NumberValueAccessor implements ControlValueAccessor {
onChange = (_: any) => {};
onTouched = () => {};
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
writeValue(value: number): void {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value);
}
registerOnChange(fn: (_: number) => void): void {
this.onChange = (value) => { fn(value == '' ? null : NumberWrapper.parseFloat(value)); };
}
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

View File

@ -1,126 +0,0 @@
import {
Directive,
ElementRef,
Renderer,
forwardRef,
Provider,
Input,
OnInit,
OnDestroy,
Injector,
Injectable
} from '@angular/core';
import {isPresent} from '../../../src/facade/lang';
import {ListWrapper} from '../../../src/facade/collection';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
import {NgControl} from './ng_control';
export const RADIO_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RadioControlValueAccessor),
multi: true
};
/**
* Internal class used by Angular to uncheck radio buttons with the matching name.
*/
@Injectable()
export class RadioControlRegistry {
private _accessors: any[] = [];
add(control: NgControl, accessor: RadioControlValueAccessor) {
this._accessors.push([control, accessor]);
}
remove(accessor: RadioControlValueAccessor) {
var indexToRemove = -1;
for (var i = 0; i < this._accessors.length; ++i) {
if (this._accessors[i][1] === accessor) {
indexToRemove = i;
}
}
ListWrapper.removeAt(this._accessors, indexToRemove);
}
select(accessor: RadioControlValueAccessor) {
this._accessors.forEach((c) => {
if (c[0].control.root === accessor._control.control.root && c[1] !== accessor) {
c[1].fireUncheck();
}
});
}
}
/**
* The value provided by the forms API for radio buttons.
*/
export class RadioButtonState {
constructor(public checked: boolean, public value: string) {}
}
/**
* The accessor for writing a radio control value and listening to changes that is used by the
* {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives.
*
* ### Example
* ```
* @Component({
* template: `
* <input type="radio" name="food" [(ngModel)]="foodChicken">
* <input type="radio" name="food" [(ngModel)]="foodFish">
* `
* })
* class FoodCmp {
* foodChicken = new RadioButtonState(true, "chicken");
* foodFish = new RadioButtonState(false, "fish");
* }
* ```
*/
@Directive({
selector:
'input[type=radio][ngControl],input[type=radio][ngFormControl],input[type=radio][ngModel]',
host: {'(change)': 'onChange()', '(blur)': 'onTouched()'},
providers: [RADIO_VALUE_ACCESSOR]
})
export class RadioControlValueAccessor implements ControlValueAccessor,
OnDestroy, OnInit {
/** @internal */
_state: RadioButtonState;
/** @internal */
_control: NgControl;
@Input() name: string;
/** @internal */
_fn: Function;
onChange = () => {};
onTouched = () => {};
constructor(private _renderer: Renderer, private _elementRef: ElementRef,
private _registry: RadioControlRegistry, private _injector: Injector) {}
ngOnInit(): void {
this._control = this._injector.get(NgControl);
this._registry.add(this._control, this);
}
ngOnDestroy(): void { this._registry.remove(this); }
writeValue(value: any): void {
this._state = value;
if (isPresent(value) && value.checked) {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', true);
}
}
registerOnChange(fn: (_: any) => {}): void {
this._fn = fn;
this.onChange = () => {
fn(new RadioButtonState(true, this._state.value));
this._registry.select(this);
};
}
fireUncheck(): void { this._fn(new RadioButtonState(false, this._state.value)); }
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
}

View File

@ -1,139 +0,0 @@
import {
Directive,
Renderer,
forwardRef,
Provider,
ElementRef,
Input,
Host,
OnDestroy,
Optional
} from '@angular/core';
import {
StringWrapper,
isPrimitive,
isPresent,
isBlank,
looseIdentical
} from '../../../src/facade/lang';
import {MapWrapper} from '../../../src/facade/collection';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
export const SELECT_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectControlValueAccessor),
multi: true
};
function _buildValueString(id: string, value: any): string {
if (isBlank(id)) return `${value}`;
if (!isPrimitive(value)) value = "Object";
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
}
function _extractId(valueString: string): string {
return valueString.split(":")[0];
}
/**
* The accessor for writing a value and listening to changes on a select element.
*
* Note: We have to listen to the 'change' event because 'input' events aren't fired
* for selects in Firefox and IE:
* https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045/
*
*/
@Directive({
selector: 'select[ngControl],select[ngFormControl],select[ngModel]',
host: {'(change)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
providers: [SELECT_VALUE_ACCESSOR]
})
export class SelectControlValueAccessor implements ControlValueAccessor {
value: any;
/** @internal */
_optionMap: Map<string, any> = new Map<string, any>();
/** @internal */
_idCounter: number = 0;
onChange = (_: any) => {};
onTouched = () => {};
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
writeValue(value: any): void {
this.value = value;
var valueString = _buildValueString(this._getOptionId(value), value);
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', valueString);
}
registerOnChange(fn: (value: any) => any): void {
this.onChange = (valueString: string) => { fn(this._getOptionValue(valueString)); };
}
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
/** @internal */
_registerOption(): string { return (this._idCounter++).toString(); }
/** @internal */
_getOptionId(value: any): string {
for (let id of MapWrapper.keys(this._optionMap)) {
if (looseIdentical(this._optionMap.get(id), value)) return id;
}
return null;
}
/** @internal */
_getOptionValue(valueString: string): any {
let value = this._optionMap.get(_extractId(valueString));
return isPresent(value) ? value : valueString;
}
}
/**
* Marks `<option>` as dynamic, so Angular can be notified when options change.
*
* ### Example
*
* ```
* <select ngControl="city">
* <option *ngFor="let c of cities" [value]="c"></option>
* </select>
* ```
*/
@Directive({selector: 'option'})
export class NgSelectOption implements OnDestroy {
id: string;
constructor(private _element: ElementRef, private _renderer: Renderer,
@Optional() @Host() private _select: SelectControlValueAccessor) {
if (isPresent(this._select)) this.id = this._select._registerOption();
}
@Input('ngValue')
set ngValue(value: any) {
if (this._select == null) return;
this._select._optionMap.set(this.id, value);
this._setElementValue(_buildValueString(this.id, value));
this._select.writeValue(this._select.value);
}
@Input('value')
set value(value: any) {
this._setElementValue(value);
if (isPresent(this._select)) this._select.writeValue(this._select.value);
}
/** @internal */
_setElementValue(value: string): void {
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
}
ngOnDestroy() {
if (isPresent(this._select)) {
this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value);
}
}
}

View File

@ -1,150 +0,0 @@
import {forwardRef, Attribute, Directive} from '@angular/core';
import {NumberWrapper} from '../../facade/lang';
import {Validators, NG_VALIDATORS} from '../validators';
import {AbstractControl} from '../model';
import * as modelModule from '../model';
/**
* An interface that can be implemented by classes that can act as validators.
*
* ## Usage
*
* ```typescript
* @Directive({
* selector: '[custom-validator]',
* providers: [provide(NG_VALIDATORS, {useExisting: CustomValidatorDirective, multi: true})]
* })
* class CustomValidatorDirective implements Validator {
* validate(c: Control): {[key: string]: any} {
* return {"custom": true};
* }
* }
* ```
*/
export interface Validator { validate(c: modelModule.AbstractControl): {[key: string]: any}; }
const REQUIRED = /*@ts2dart_const*/ Validators.required;
export const REQUIRED_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
provide: NG_VALIDATORS,
useValue: REQUIRED,
multi: true
};
/**
* A Directive that adds the `required` validator to any controls marked with the
* `required` attribute, via the {@link NG_VALIDATORS} binding.
*
* ### Example
*
* ```
* <input ngControl="fullName" required>
* ```
*/
@Directive({
selector: '[required][ngControl],[required][ngFormControl],[required][ngModel]',
providers: [REQUIRED_VALIDATOR]
})
export class RequiredValidator {
}
export interface ValidatorFn { (c: AbstractControl): {[key: string]: any}; }
export interface AsyncValidatorFn {
(c: AbstractControl): any /*Promise<{[key: string]: any}>|Observable<{[key: string]: any}>*/;
}
/**
* Provivder which adds {@link MinLengthValidator} to {@link NG_VALIDATORS}.
*
* ## Example:
*
* {@example common/forms/ts/validators/validators.ts region='min'}
*/
export const MIN_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MinLengthValidator),
multi: true
};
/**
* A directive which installs the {@link MinLengthValidator} for any `ngControl`,
* `ngFormControl`, or control with `ngModel` that also has a `minlength` attribute.
*/
@Directive({
selector: '[minlength][ngControl],[minlength][ngFormControl],[minlength][ngModel]',
providers: [MIN_LENGTH_VALIDATOR]
})
export class MinLengthValidator implements Validator {
private _validator: ValidatorFn;
constructor(@Attribute("minlength") minLength: string) {
this._validator = Validators.minLength(NumberWrapper.parseInt(minLength, 10));
}
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
}
/**
* Provider which adds {@link MaxLengthValidator} to {@link NG_VALIDATORS}.
*
* ## Example:
*
* {@example common/forms/ts/validators/validators.ts region='max'}
*/
export const MAX_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MaxLengthValidator),
multi: true
};
/**
* A directive which installs the {@link MaxLengthValidator} for any `ngControl, `ngFormControl`,
* or control with `ngModel` that also has a `maxlength` attribute.
*/
@Directive({
selector: '[maxlength][ngControl],[maxlength][ngFormControl],[maxlength][ngModel]',
providers: [MAX_LENGTH_VALIDATOR]
})
export class MaxLengthValidator implements Validator {
private _validator: ValidatorFn;
constructor(@Attribute("maxlength") maxLength: string) {
this._validator = Validators.maxLength(NumberWrapper.parseInt(maxLength, 10));
}
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
}
/**
* A Directive that adds the `pattern` validator to any controls marked with the
* `pattern` attribute, via the {@link NG_VALIDATORS} binding. Uses attribute value
* as the regex to validate Control value against. Follows pattern attribute
* semantics; i.e. regex must match entire Control value.
*
* ### Example
*
* ```
* <input [ngControl]="fullName" pattern="[a-zA-Z ]*">
* ```
*/
export const PATTERN_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => PatternValidator),
multi: true
};
@Directive({
selector: '[pattern][ngControl],[pattern][ngFormControl],[pattern][ngModel]',
providers: [PATTERN_VALIDATOR]
})
export class PatternValidator implements Validator {
private _validator: ValidatorFn;
constructor(@Attribute("pattern") pattern: string) {
this._validator = Validators.pattern(pattern);
}
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
}

View File

@ -1,112 +0,0 @@
import {Injectable} from '@angular/core';
import {StringMapWrapper} from '../../src/facade/collection';
import {isPresent, isArray} from '../../src/facade/lang';
import * as modelModule from './model';
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
/**
* Creates a form object from a user-specified configuration.
*
* ### Example ([live demo](http://plnkr.co/edit/ENgZo8EuIECZNensZCVr?p=preview))
*
* ```typescript
* @Component({
* selector: 'my-app',
* viewBindings: [FORM_BINDINGS]
* template: `
* <form [ngFormModel]="loginForm">
* <p>Login <input ngControl="login"></p>
* <div ngControlGroup="passwordRetry">
* <p>Password <input type="password" ngControl="password"></p>
* <p>Confirm password <input type="password" ngControl="passwordConfirmation"></p>
* </div>
* </form>
* <h3>Form value:</h3>
* <pre>{{value}}</pre>
* `,
* directives: [FORM_DIRECTIVES]
* })
* export class App {
* loginForm: ControlGroup;
*
* constructor(builder: FormBuilder) {
* this.loginForm = builder.group({
* login: ["", Validators.required],
* passwordRetry: builder.group({
* password: ["", Validators.required],
* passwordConfirmation: ["", Validators.required, asyncValidator]
* })
* });
* }
*
* get value(): string {
* return JSON.stringify(this.loginForm.value, null, 2);
* }
* }
* ```
*/
@Injectable()
export class FormBuilder {
/**
* Construct a new {@link ControlGroup} with the given map of configuration.
* Valid keys for the `extra` parameter map are `optionals` and `validator`.
*
* See the {@link ControlGroup} constructor for more details.
*/
group(controlsConfig: {[key: string]: any},
extra: {[key: string]: any} = null): modelModule.ControlGroup {
var controls = this._reduceControls(controlsConfig);
var optionals = <{[key: string]: boolean}>(
isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null);
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
var asyncValidator: AsyncValidatorFn =
isPresent(extra) ? StringMapWrapper.get(extra, "asyncValidator") : null;
return new modelModule.ControlGroup(controls, optionals, validator, asyncValidator);
}
/**
* Construct a new {@link Control} with the given `value`,`validator`, and `asyncValidator`.
*/
control(value: Object, validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null): modelModule.Control {
return new modelModule.Control(value, validator, asyncValidator);
}
/**
* Construct an array of {@link Control}s from the given `controlsConfig` array of
* configuration, with the given optional `validator` and `asyncValidator`.
*/
array(controlsConfig: any[], validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null): modelModule.ControlArray {
var controls = controlsConfig.map(c => this._createControl(c));
return new modelModule.ControlArray(controls, validator, asyncValidator);
}
/** @internal */
_reduceControls(controlsConfig: {[k: string]:
any}): {[key: string]: modelModule.AbstractControl} {
var controls: {[key: string]: modelModule.AbstractControl} = {};
StringMapWrapper.forEach(controlsConfig, (controlConfig: any, controlName: string) => {
controls[controlName] = this._createControl(controlConfig);
});
return controls;
}
/** @internal */
_createControl(controlConfig: any): modelModule.AbstractControl {
if (controlConfig instanceof modelModule.Control ||
controlConfig instanceof modelModule.ControlGroup ||
controlConfig instanceof modelModule.ControlArray) {
return controlConfig;
} else if (isArray(controlConfig)) {
var value = controlConfig[0];
var validator: ValidatorFn = controlConfig.length > 1 ? controlConfig[1] : null;
var asyncValidator: AsyncValidatorFn = controlConfig.length > 2 ? controlConfig[2] : null;
return this.control(value, validator, asyncValidator);
} else {
return this.control(controlConfig);
}
}
}

View File

@ -1,144 +0,0 @@
import {OpaqueToken} from '@angular/core';
import {isBlank, isPresent, isString} from '../../src/facade/lang';
import {PromiseWrapper} from '../../src/facade/promise';
import {ObservableWrapper} from '../../src/facade/async';
import {StringMapWrapper} from '../../src/facade/collection';
import * as modelModule from './model';
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
/**
* Providers for validators to be used for {@link Control}s in a form.
*
* Provide this using `multi: true` to add validators.
*
* ### Example
*
* {@example core/forms/ts/ng_validators/ng_validators.ts region='ng_validators'}
*/
export const NG_VALIDATORS: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken("NgValidators");
/**
* Providers for asynchronous validators to be used for {@link Control}s
* in a form.
*
* Provide this using `multi: true` to add validators.
*
* See {@link NG_VALIDATORS} for more details.
*/
export const NG_ASYNC_VALIDATORS: OpaqueToken =
/*@ts2dart_const*/ new OpaqueToken("NgAsyncValidators");
/**
* Provides a set of validators used by form controls.
*
* A validator is a function that processes a {@link Control} or collection of
* controls and returns a map of errors. A null map means that validation has passed.
*
* ### Example
*
* ```typescript
* var loginControl = new Control("", Validators.required)
* ```
*/
export class Validators {
/**
* Validator that requires controls to have a non-empty value.
*/
static required(control: modelModule.AbstractControl): {[key: string]: boolean} {
return isBlank(control.value) || (isString(control.value) && control.value == "") ?
{"required": true} :
null;
}
/**
* Validator that requires controls to have a value of a minimum length.
*/
static minLength(minLength: number): ValidatorFn {
return (control: modelModule.AbstractControl): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
var v: string = control.value;
return v.length < minLength ?
{"minlength": {"requiredLength": minLength, "actualLength": v.length}} :
null;
};
}
/**
* Validator that requires controls to have a value of a maximum length.
*/
static maxLength(maxLength: number): ValidatorFn {
return (control: modelModule.AbstractControl): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
var v: string = control.value;
return v.length > maxLength ?
{"maxlength": {"requiredLength": maxLength, "actualLength": v.length}} :
null;
};
}
/**
* Validator that requires a control to match a regex to its value.
*/
static pattern(pattern: string): ValidatorFn {
return (control: modelModule.AbstractControl): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
let regex = new RegExp(`^${pattern}$`);
let v: string = control.value;
return regex.test(v) ? null :
{"pattern": {"requiredPattern": `^${pattern}$`, "actualValue": v}};
};
}
/**
* No-op validator.
*/
static nullValidator(c: modelModule.AbstractControl): {[key: string]: boolean} { return null; }
/**
* Compose multiple validators into a single function that returns the union
* of the individual error maps.
*/
static compose(validators: ValidatorFn[]): ValidatorFn {
if (isBlank(validators)) return null;
var presentValidators = validators.filter(isPresent);
if (presentValidators.length == 0) return null;
return function(control: modelModule.AbstractControl) {
return _mergeErrors(_executeValidators(control, presentValidators));
};
}
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn {
if (isBlank(validators)) return null;
var presentValidators = validators.filter(isPresent);
if (presentValidators.length == 0) return null;
return function(control: modelModule.AbstractControl) {
let promises = _executeAsyncValidators(control, presentValidators).map(_convertToPromise);
return PromiseWrapper.all(promises).then(_mergeErrors);
};
}
}
function _convertToPromise(obj: any): any {
return PromiseWrapper.isPromise(obj) ? obj : ObservableWrapper.toPromise(obj);
}
function _executeValidators(control: modelModule.AbstractControl,
validators: ValidatorFn[]): any[] {
return validators.map(v => v(control));
}
function _executeAsyncValidators(control: modelModule.AbstractControl,
validators: AsyncValidatorFn[]): any[] {
return validators.map(v => v(control));
}
function _mergeErrors(arrayOfErrors: any[]): {[key: string]: any} {
var res: {[key: string]: any} =
arrayOfErrors.reduce((res: {[key: string]: any}, errors: {[key: string]: any}) => {
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
}, {});
return StringMapWrapper.isEmpty(res) ? null : res;
}

View File

@ -1,5 +0,0 @@
export * from './location/platform_location';
export * from './location/location_strategy';
export * from './location/hash_location_strategy';
export * from './location/path_location_strategy';
export * from './location/location';

View File

@ -1,103 +0,0 @@
import {Injectable, Inject, Optional} from '@angular/core';
import {isPresent} from '../../src/facade/lang';
import {LocationStrategy, APP_BASE_HREF} from './location_strategy';
import {Location} from './location';
import {UrlChangeListener, PlatformLocation} from './platform_location';
/**
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
* {@link Location} service to represent its state in the
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
* of the browser's URL.
*
* For instance, if you call `location.go('/foo')`, the browser's URL will become
* `example.com#/foo`.
*
* ### Example
*
* ```
* import {Component, provide} from '@angular/core';
* import {
* Location,
* LocationStrategy,
* HashLocationStrategy
* } from '@angular/common';
* import {
* ROUTER_DIRECTIVES,
* ROUTER_PROVIDERS,
* RouteConfig
* } from '@angular/router';
*
* @Component({directives: [ROUTER_DIRECTIVES]})
* @RouteConfig([
* {...},
* ])
* class AppCmp {
* constructor(location: Location) {
* location.go('/foo');
* }
* }
*
* bootstrap(AppCmp, [
* ROUTER_PROVIDERS,
* provide(LocationStrategy, {useClass: HashLocationStrategy})
* ]);
* ```
*/
@Injectable()
export class HashLocationStrategy extends LocationStrategy {
private _baseHref: string = '';
constructor(private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
super();
if (isPresent(_baseHref)) {
this._baseHref = _baseHref;
}
}
onPopState(fn: UrlChangeListener): void {
this._platformLocation.onPopState(fn);
this._platformLocation.onHashChange(fn);
}
getBaseHref(): string { return this._baseHref; }
path(): string {
// the hash value is always prefixed with a `#`
// and if it is empty then it will stay empty
var path = this._platformLocation.hash;
if (!isPresent(path)) path = '#';
// Dart will complain if a call to substring is
// executed with a position value that extends the
// length of string.
return (path.length > 0 ? path.substring(1) : path);
}
prepareExternalUrl(internal: string): string {
var url = Location.joinWithSlash(this._baseHref, internal);
return url.length > 0 ? ('#' + url) : url;
}
pushState(state: any, title: string, path: string, queryParams: string) {
var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
if (url.length == 0) {
url = this._platformLocation.pathname;
}
this._platformLocation.pushState(state, title, url);
}
replaceState(state: any, title: string, path: string, queryParams: string) {
var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
if (url.length == 0) {
url = this._platformLocation.pathname;
}
this._platformLocation.replaceState(state, title, url);
}
forward(): void { this._platformLocation.forward(); }
back(): void { this._platformLocation.back(); }
}

View File

@ -1,61 +0,0 @@
import {OpaqueToken} from '@angular/core';
import {UrlChangeListener} from './platform_location';
/**
* `LocationStrategy` is responsible for representing and reading route state
* from the browser's URL. Angular provides two strategies:
* {@link HashLocationStrategy} and {@link PathLocationStrategy} (default).
*
* This is used under the hood of the {@link Location} service.
*
* Applications should use the {@link Router} or {@link Location} services to
* interact with application route state.
*
* For instance, {@link HashLocationStrategy} produces URLs like
* `http://example.com#/foo`, and {@link PathLocationStrategy} produces
* `http://example.com/foo` as an equivalent URL.
*
* See these two classes for more.
*/
export abstract class LocationStrategy {
abstract path(): string;
abstract prepareExternalUrl(internal: string): string;
abstract pushState(state: any, title: string, url: string, queryParams: string): void;
abstract replaceState(state: any, title: string, url: string, queryParams: string): void;
abstract forward(): void;
abstract back(): void;
abstract onPopState(fn: UrlChangeListener): void;
abstract getBaseHref(): string;
}
/**
* The `APP_BASE_HREF` token represents the base href to be used with the
* {@link PathLocationStrategy}.
*
* If you're using {@link PathLocationStrategy}, you must provide a provider to a string
* representing the URL prefix that should be preserved when generating and recognizing
* URLs.
*
* ### Example
*
* ```
* import {Component} from '@angular/core';
* import {ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig} from '@angular/router';
* import {APP_BASE_HREF} from '@angular/common';
*
* @Component({directives: [ROUTER_DIRECTIVES]})
* @RouteConfig([
* {...},
* ])
* class AppCmp {
* // ...
* }
*
* bootstrap(AppCmp, [
* ROUTER_PROVIDERS,
* provide(APP_BASE_HREF, {useValue: '/my/app'})
* ]);
* ```
*/
export const APP_BASE_HREF: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken('appBaseHref');

View File

@ -1,106 +0,0 @@
import {Injectable, Inject, Optional} from '@angular/core';
import {isBlank} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions';
import {PlatformLocation, UrlChangeListener} from './platform_location';
import {LocationStrategy, APP_BASE_HREF} from './location_strategy';
import {Location} from './location';
/**
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
* {@link Location} service to represent its state in the
* [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the
* browser's URL.
*
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
* provided in {@link ROUTER_PROVIDERS}.
*
* If you're using `PathLocationStrategy`, you must provide a provider for
* {@link APP_BASE_HREF} to a string representing the URL prefix that should
* be preserved when generating and recognizing URLs.
*
* For instance, if you provide an `APP_BASE_HREF` of `'/my/app'` and call
* `location.go('/foo')`, the browser's URL will become
* `example.com/my/app/foo`.
*
* ### Example
*
* ```
* import {Component, provide} from '@angular/core';
* import {bootstrap} from '@angular/platform-browser/browser';
* import {
* Location,
* APP_BASE_HREF
* } from '@angular/common';
* import {
* ROUTER_DIRECTIVES,
* ROUTER_PROVIDERS,
* RouteConfig
* } from '@angular/router';
*
* @Component({directives: [ROUTER_DIRECTIVES]})
* @RouteConfig([
* {...},
* ])
* class AppCmp {
* constructor(location: Location) {
* location.go('/foo');
* }
* }
*
* bootstrap(AppCmp, [
* ROUTER_PROVIDERS, // includes binding to PathLocationStrategy
* provide(APP_BASE_HREF, {useValue: '/my/app'})
* ]);
* ```
*/
@Injectable()
export class PathLocationStrategy extends LocationStrategy {
private _baseHref: string;
constructor(private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) href?: string) {
super();
if (isBlank(href)) {
href = this._platformLocation.getBaseHrefFromDOM();
}
if (isBlank(href)) {
throw new BaseException(
`No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.`);
}
this._baseHref = href;
}
onPopState(fn: UrlChangeListener): void {
this._platformLocation.onPopState(fn);
this._platformLocation.onHashChange(fn);
}
getBaseHref(): string { return this._baseHref; }
prepareExternalUrl(internal: string): string {
return Location.joinWithSlash(this._baseHref, internal);
}
path(): string {
return this._platformLocation.pathname +
Location.normalizeQueryParams(this._platformLocation.search);
}
pushState(state: any, title: string, url: string, queryParams: string) {
var externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams));
this._platformLocation.pushState(state, title, externalUrl);
}
replaceState(state: any, title: string, url: string, queryParams: string) {
var externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams));
this._platformLocation.replaceState(state, title, externalUrl);
}
forward(): void { this._platformLocation.forward(); }
back(): void { this._platformLocation.back(); }
}

View File

@ -1,48 +0,0 @@
/**
* This class should not be used directly by an application developer. Instead, use
* {@link Location}.
*
* `PlatformLocation` encapsulates all calls to DOM apis, which allows the Router to be platform
* agnostic.
* This means that we can have different implementation of `PlatformLocation` for the different
* platforms
* that angular supports. For example, the default `PlatformLocation` is {@link
* BrowserPlatformLocation},
* however when you run your app in a WebWorker you use {@link WebWorkerPlatformLocation}.
*
* The `PlatformLocation` class is used directly by all implementations of {@link LocationStrategy}
* when
* they need to interact with the DOM apis like pushState, popState, etc...
*
* {@link LocationStrategy} in turn is used by the {@link Location} service which is used directly
* by
* the {@link Router} in order to navigate between routes. Since all interactions between {@link
* Router} /
* {@link Location} / {@link LocationStrategy} and DOM apis flow through the `PlatformLocation`
* class
* they are all platform independent.
*/
export abstract class PlatformLocation {
abstract getBaseHrefFromDOM(): string;
abstract onPopState(fn: UrlChangeListener): void;
abstract onHashChange(fn: UrlChangeListener): void;
/* abstract */ get pathname(): string { return null; }
/* abstract */ get search(): string { return null; }
/* abstract */ get hash(): string { return null; }
abstract replaceState(state: any, title: string, url: string): void;
abstract pushState(state: any, title: string, url: string): void;
abstract forward(): void;
abstract back(): void;
}
/**
* A serializable version of the event from onPopState or onHashChange
*/
export interface UrlChangeEvent { type: string; }
export interface UrlChangeListener { (e: UrlChangeEvent): any; }

View File

@ -1,37 +0,0 @@
/**
* @module
* @description
* This module provides a set of common Pipes.
*/
import {AsyncPipe} from './async_pipe';
import {UpperCasePipe} from './uppercase_pipe';
import {LowerCasePipe} from './lowercase_pipe';
import {JsonPipe} from './json_pipe';
import {SlicePipe} from './slice_pipe';
import {DatePipe} from './date_pipe';
import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe';
import {ReplacePipe} from './replace_pipe';
import {I18nPluralPipe} from './i18n_plural_pipe';
import {I18nSelectPipe} from './i18n_select_pipe';
/**
* A collection of Angular core pipes that are likely to be used in each and every
* application.
*
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes`
* property of the `@Component` decorator.
*/
export const COMMON_PIPES = /*@ts2dart_const*/[
AsyncPipe,
UpperCasePipe,
LowerCasePipe,
JsonPipe,
SlicePipe,
DecimalPipe,
PercentPipe,
CurrencyPipe,
DatePipe,
ReplacePipe,
I18nPluralPipe,
I18nSelectPipe
];

View File

@ -1,54 +0,0 @@
import {Injectable, PipeTransform, Pipe} from '@angular/core';
import {isStringMap, StringWrapper, isPresent, RegExpWrapper} from '../../src/facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
var interpolationExp: RegExp = RegExpWrapper.create('#');
/**
*
* Maps a value to a string that pluralizes the value properly.
*
* ## Usage
*
* expression | i18nPlural:mapping
*
* where `expression` is a number and `mapping` is an object that indicates the proper text for
* when the `expression` evaluates to 0, 1, or some other number. You can interpolate the actual
* value into the text using the `#` sign.
*
* ## Example
*
* ```
* <div>
* {{ messages.length | i18nPlural: messageMapping }}
* </div>
*
* class MyApp {
* messages: any[];
* messageMapping: any = {
* '=0': 'No messages.',
* '=1': 'One message.',
* 'other': '# messages.'
* }
* ...
* }
* ```
*
*/
@Pipe({name: 'i18nPlural', pure: true})
@Injectable()
export class I18nPluralPipe implements PipeTransform {
transform(value: number, pluralMap: {[count: string]: string}): string {
var key: string;
var valueStr: string;
if (!isStringMap(pluralMap)) {
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);
}
key = value === 0 || value === 1 ? `=${value}` : 'other';
valueStr = isPresent(value) ? value.toString() : '';
return StringWrapper.replaceAll(pluralMap[key], interpolationExp, valueStr);
}
}

View File

@ -1,45 +0,0 @@
import {Injectable, PipeTransform, Pipe} from '@angular/core';
import {isStringMap} from '../../src/facade/lang';
import {StringMapWrapper} from '../../src/facade/collection';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
*
* Generic selector that displays the string that matches the current value.
*
* ## Usage
*
* expression | i18nSelect:mapping
*
* where `mapping` is an object that indicates the text that should be displayed
* for different values of the provided `expression`.
*
* ## Example
*
* ```
* <div>
* {{ gender | i18nSelect: inviteMap }}
* </div>
*
* class MyApp {
* gender: string = 'male';
* inviteMap: any = {
* 'male': 'Invite her.',
* 'female': 'Invite him.',
* 'other': 'Invite them.'
* }
* ...
* }
* ```
*/
@Pipe({name: 'i18nSelect', pure: true})
@Injectable()
export class I18nSelectPipe implements PipeTransform {
transform(value: string, mapping: {[key: string]: string}): string {
if (!isStringMap(mapping)) {
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
}
return StringMapWrapper.contains(mapping, value) ? mapping[value] : mapping['other'];
}
}

View File

@ -1,8 +0,0 @@
import {Type, stringify} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions';
export class InvalidPipeArgumentException extends BaseException {
constructor(type: Type, value: Object) {
super(`Invalid argument '${value}' for pipe '${stringify(type)}'`);
}
}

View File

@ -1,16 +0,0 @@
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
import {Json} from '../../src/facade/lang';
/**
* Transforms any input value using `JSON.stringify`. Useful for debugging.
*
* ### Example
* {@example core/pipes/ts/json_pipe/json_pipe_example.ts region='JsonPipe'}
*/
/* @ts2dart_const */
@Pipe({name: 'json', pure: false})
@Injectable()
export class JsonPipe implements PipeTransform {
transform(value: any): string { return Json.stringify(value); }
}

View File

@ -1,23 +0,0 @@
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
import {isString, isBlank} from '../../src/facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
* Transforms text to lowercase.
*
* ### Example
*
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe'}
*/
/* @ts2dart_const */
@Pipe({name: 'lowercase'})
@Injectable()
export class LowerCasePipe implements PipeTransform {
transform(value: string): string {
if (isBlank(value)) return value;
if (!isString(value)) {
throw new InvalidPipeArgumentException(LowerCasePipe, value);
}
return value.toLowerCase();
}
}

View File

@ -1,85 +0,0 @@
import {Injectable, PipeTransform, Pipe} from '@angular/core';
import {
isBlank,
isString,
isNumber,
isFunction,
RegExpWrapper,
StringWrapper
} from '../../src/facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
* Creates a new String with some or all of the matches of a pattern replaced by
* a replacement.
*
* The pattern to be matched is specified by the 'pattern' parameter.
*
* The replacement to be set is specified by the 'replacement' parameter.
*
* An optional 'flags' parameter can be set.
*
* ### Usage
*
* expression | replace:pattern:replacement
*
* All behavior is based on the expected behavior of the JavaScript API
* String.prototype.replace() function.
*
* Where the input expression is a [String] or [Number] (to be treated as a string),
* the `pattern` is a [String] or [RegExp],
* the 'replacement' is a [String] or [Function].
*
* --Note--: The 'pattern' parameter will be converted to a RegExp instance. Make sure to escape the
* string properly if you are matching for regular expression special characters like parenthesis,
* brackets etc.
*/
@Pipe({name: 'replace'})
@Injectable()
export class ReplacePipe implements PipeTransform {
transform(value: any, pattern: string | RegExp, replacement: Function | string): any {
if (isBlank(value)) {
return value;
}
if (!this._supportedInput(value)) {
throw new InvalidPipeArgumentException(ReplacePipe, value);
}
var input = value.toString();
if (!this._supportedPattern(pattern)) {
throw new InvalidPipeArgumentException(ReplacePipe, pattern);
}
if (!this._supportedReplacement(replacement)) {
throw new InvalidPipeArgumentException(ReplacePipe, replacement);
}
// template fails with literal RegExp e.g /pattern/igm
// var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern);
if (isFunction(replacement)) {
var rgxPattern = isString(pattern) ? RegExpWrapper.create(<string>pattern) : <RegExp>pattern;
return StringWrapper.replaceAllMapped(input, rgxPattern, <Function>replacement);
}
if (pattern instanceof RegExp) {
// use the replaceAll variant
return StringWrapper.replaceAll(input, pattern, <string>replacement);
}
return StringWrapper.replace(input, <string>pattern, <string>replacement);
}
private _supportedInput(input: any): boolean { return isString(input) || isNumber(input); }
private _supportedPattern(pattern: any): boolean {
return isString(pattern) || pattern instanceof RegExp;
}
private _supportedReplacement(replacement: any): boolean {
return isString(replacement) || isFunction(replacement);
}
}

View File

@ -1,22 +0,0 @@
import {PipeTransform, WrappedValue, Injectable, Pipe} from '@angular/core';
import {isString, isBlank} from '../../src/facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
* Implements uppercase transforms to text.
*
* ### Example
*
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe'}
*/
@Pipe({name: 'uppercase'})
@Injectable()
export class UpperCasePipe implements PipeTransform {
transform(value: string): string {
if (isBlank(value)) return value;
if (!isString(value)) {
throw new InvalidPipeArgumentException(UpperCasePipe, value);
}
return value.toUpperCase();
}
}

View File

@ -1,536 +0,0 @@
import {
beforeEach,
beforeEachProviders,
ddescribe,
xdescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {ComponentFixture, TestComponentBuilder} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {ListWrapper, StringMapWrapper, SetWrapper} from '../../src/facade/collection';
import {Component, provide} from '@angular/core';
import {NgFor, NgClass} from '@angular/common';
function detectChangesAndCheck(fixture: ComponentFixture<any>, classes: string) {
fixture.detectChanges();
expect(fixture.debugElement.children[0].nativeElement.className).toEqual(classes);
}
export function main() {
describe('binding to CSS class list', () => {
it('should clean up when the directive is destroyed',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div *ngFor="let item of items" [ngClass]="item"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['0']];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = [['1']];
detectChangesAndCheck(fixture, '1');
async.done();
});
}));
describe('expressions evaluating to objects', () => {
it('should add classes specified in an object literal',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="{foo: true, bar: false}"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
it('should add classes specified in an object literal without change in class names',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="{'foo-bar': true, 'fooBar': true}"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo-bar fooBar');
async.done();
});
}));
it('should add and remove classes based on changes in object literal values',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="{foo: condition, bar: !condition}"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.condition = false;
detectChangesAndCheck(fixture, 'bar');
async.done();
});
}));
it('should add and remove classes based on changes to the expression object',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'foo bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'baz', true);
detectChangesAndCheck(fixture, 'foo bar baz');
StringMapWrapper.delete(fixture.debugElement.componentInstance.objExpr, 'bar');
detectChangesAndCheck(fixture, 'foo baz');
async.done();
});
}));
it('should add and remove classes based on reference changes to the expression object',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.objExpr = {foo: true, bar: true};
detectChangesAndCheck(fixture, 'foo bar');
fixture.debugElement.componentInstance.objExpr = {baz: true};
detectChangesAndCheck(fixture, 'baz');
async.done();
});
}));
it('should remove active classes when expression evaluates to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, '');
fixture.debugElement.componentInstance.objExpr = {'foo': false, 'bar': true};
detectChangesAndCheck(fixture, 'bar');
async.done();
});
}));
it('should allow multiple classes per expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.objExpr = {
'bar baz': true,
'bar1 baz1': true
};
detectChangesAndCheck(fixture, 'bar baz bar1 baz1');
fixture.debugElement.componentInstance.objExpr = {
'bar baz': false,
'bar1 baz1': true
};
detectChangesAndCheck(fixture, 'bar1 baz1');
async.done();
});
}));
it('should split by one or more spaces between classes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.objExpr = {'foo bar baz': true};
detectChangesAndCheck(fixture, 'foo bar baz');
async.done();
});
}));
});
describe('expressions evaluating to lists', () => {
it('should add classes specified in a list literal',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="['foo', 'bar', 'foo-bar', 'fooBar']"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
async.done();
});
}));
it('should add and remove classes based on changes to the expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var arrExpr: string[] = fixture.debugElement.componentInstance.arrExpr;
detectChangesAndCheck(fixture, 'foo');
arrExpr.push('bar');
detectChangesAndCheck(fixture, 'foo bar');
arrExpr[1] = 'baz';
detectChangesAndCheck(fixture, 'foo baz');
ListWrapper.remove(fixture.debugElement.componentInstance.arrExpr, 'baz');
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
it('should add and remove classes when a reference changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.arrExpr = ['bar'];
detectChangesAndCheck(fixture, 'bar');
async.done();
});
}));
it('should take initial classes into account when a reference changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.arrExpr = ['bar'];
detectChangesAndCheck(fixture, 'foo bar');
async.done();
});
}));
it('should ignore empty or blank class names',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.arrExpr = ['', ' '];
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
it('should trim blanks from class names',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.arrExpr = [' bar '];
detectChangesAndCheck(fixture, 'foo bar');
async.done();
});
}));
it('should allow multiple classes per item in arrays',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.arrExpr =
['foo bar baz', 'foo1 bar1 baz1'];
detectChangesAndCheck(fixture, 'foo bar baz foo1 bar1 baz1');
fixture.debugElement.componentInstance.arrExpr = ['foo bar baz foobar'];
detectChangesAndCheck(fixture, 'foo bar baz foobar');
async.done();
});
}));
});
describe('expressions evaluating to sets', () => {
it('should add and remove classes if the set instance changed',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="setExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var setExpr = new Set<string>();
setExpr.add('bar');
fixture.debugElement.componentInstance.setExpr = setExpr;
detectChangesAndCheck(fixture, 'bar');
setExpr = new Set<string>();
setExpr.add('baz');
fixture.debugElement.componentInstance.setExpr = setExpr;
detectChangesAndCheck(fixture, 'baz');
async.done();
});
}));
});
describe('expressions evaluating to string', () => {
it('should add classes specified in a string literal',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="'foo bar foo-bar fooBar'"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
async.done();
});
}));
it('should add and remove classes based on changes to the expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="strExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = 'foo bar';
detectChangesAndCheck(fixture, 'foo bar');
fixture.debugElement.componentInstance.strExpr = 'baz';
detectChangesAndCheck(fixture, 'baz');
async.done();
});
}));
it('should remove active classes when switching from string to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="strExpr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = null;
detectChangesAndCheck(fixture, '');
async.done();
});
}));
it('should take initial classes into account when switching from string to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = null;
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
it('should ignore empty and blank strings',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.strExpr = '';
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
});
describe('cooperation with other class-changing constructs', () => {
it('should co-operate with the class attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr" class="init foo"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, 'init bar');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, 'init foo');
async.done();
});
}));
it('should co-operate with the interpolated class attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, `init foo bar`);
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, `init bar`);
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, `init foo`);
async.done();
});
}));
it('should co-operate with the class attribute and binding to it',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, `init foo bar`);
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, `init bar`);
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, `init foo`);
async.done();
});
}));
it('should co-operate with the class attribute and class.name binding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'init foo baz');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo baz bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, 'init baz bar');
fixture.debugElement.componentInstance.condition = false;
detectChangesAndCheck(fixture, 'init bar');
async.done();
});
}));
it('should co-operate with initial class and class attribute binding when binding changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'init foo');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo bar');
fixture.debugElement.componentInstance.strExpr = 'baz';
detectChangesAndCheck(fixture, 'init bar baz foo');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, 'init baz');
async.done();
});
}));
});
})
}
@Component({selector: 'test-cmp', directives: [NgClass, NgFor], template: ''})
class TestComponent {
condition: boolean = true;
items: any[];
arrExpr: string[] = ['foo'];
setExpr: Set<string> = new Set<string>();
objExpr = {'foo': true, 'bar': false};
strExpr = 'foo';
constructor() { this.setExpr.add('foo'); }
}

View File

@ -1,525 +0,0 @@
import {
beforeEach,
beforeEachProviders,
ddescribe,
describe,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {ListWrapper} from '../../src/facade/collection';
import {IS_DART} from '../../src/facade/lang';
import {Component, TemplateRef, ContentChild} from '@angular/core';
import {NgFor} from '@angular/common';
import {NgIf} from '@angular/common';
import {By} from '@angular/platform-browser/src/dom/debug/by';
export function main() {
describe('ngFor', () => {
var TEMPLATE =
'<div><copy-me template="ngFor let item of items">{{item.toString()}};</copy-me></div>';
it('should reflect initial elements',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
async.done();
});
}));
it('should reflect added elements',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
(<number[]>fixture.debugElement.componentInstance.items).push(3);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
async.done();
});
}));
it('should reflect removed elements',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 1);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;');
async.done();
});
}));
it('should reflect moved elements',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
(<number[]>fixture.debugElement.componentInstance.items).push(1);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('2;1;');
async.done();
});
}));
it('should reflect a mix of all changes (additions/removals/moves)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = [6, 2, 7, 0, 4, 8];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('6;2;7;0;4;8;');
async.done();
});
}));
it('should iterate over an array of objects',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
// INIT
fixture.debugElement.componentInstance.items =
[{'name': 'misko'}, {'name': 'shyam'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;');
// GROW
(<any[]>fixture.debugElement.componentInstance.items).push({'name': 'adam'});
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;adam;');
// SHRINK
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 2);
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('shyam;');
async.done();
});
}));
it('should gracefully handle nulls',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
it('should gracefully handle ref changing to null and back',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
fixture.debugElement.componentInstance.items = null;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.items = [1, 2, 3];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
async.done();
});
}));
if (!IS_DART) {
it('should throw on non-iterable ref and suggest using an array',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = 'whaaa';
try {
fixture.detectChanges()
} catch (e) {
expect(e.message).toContain(
`Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays.`);
async.done();
}
});
}));
}
it('should throw on ref changing to string',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
fixture.debugElement.componentInstance.items = 'whaaa';
expect(() => fixture.detectChanges()).toThrowError();
async.done();
});
}));
it('should works with duplicates',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
var a = new Foo();
fixture.debugElement.componentInstance.items = [a, a];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('foo;foo;');
async.done();
});
}));
it('should repeat over nested arrays',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<div template="ngFor let item of items">' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div>|' +
'</div>' +
'</div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
fixture.detectChanges();
fixture.detectChanges();
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;|c-1;|');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('e-1;|f-2;g-2;|');
async.done();
});
}));
it('should repeat over nested arrays with no intermediate element',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div><template ngFor let-item [ngForOf]="items">' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div></template></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;c-1;');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('e-1;f-2;g-2;');
async.done();
});
}));
it('should repeat over nested ngIf that are the last node in the ngFor temlate',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var el = fixture.debugElement.nativeElement;
var items = [1];
fixture.debugElement.componentInstance.items = items;
fixture.detectChanges();
expect(el).toHaveText('0|even|');
items.push(1);
fixture.detectChanges();
expect(el).toHaveText('0|even|1|');
items.push(1);
fixture.detectChanges();
expect(el).toHaveText('0|even|1|2|even|');
async.done();
});
}));
it('should display indices correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let i=index">{{i.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
fixture.debugElement.componentInstance.items = [1, 2, 6, 7, 4, 3, 5, 8, 9, 0];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
async.done();
});
}));
it('should display first item correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalsefalse');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
async.done();
});
}));
it('should display last item correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
async.done();
});
}));
it('should display even items correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
async.done();
});
}));
it('should display odd items correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetruefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
async.done();
});
}));
it('should allow to use a custom template',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(
TestComponent,
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>')
.overrideTemplate(
ComponentUsingTestComponent,
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
var testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
it('should use a default template if a custom one is null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`)
.overrideTemplate(ComponentUsingTestComponent, '<test-cmp></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
var testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
it('should use a custom template when both default and a custom one are present',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`)
.overrideTemplate(
ComponentUsingTestComponent,
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
var testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
describe('track by', function() {
it('should not replace tracked items',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
<p>{{items[i]}}</p>
</template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var buildItemList =
() => {
fixture.debugElement.componentInstance.items = [{'id': 'a'}];
fixture.detectChanges();
return fixture.debugElement.queryAll(By.css('p'))[0];
}
var firstP = buildItemList();
var finalP = buildItemList();
expect(finalP.nativeElement).toBe(firstP.nativeElement);
async.done();
});
}));
it('should update implicit local variable on view',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'blue'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blue');
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('red');
async.done();
});
}));
it('should move items around and keep them updated ',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items =
[{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blueyellow');
fixture.debugElement.componentInstance.items =
[{'id': 'b', 'color': 'orange'}, {'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('orangered');
async.done();
});
}));
it('should handle added and removed items properly when tracking by index',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = ['a', 'b', 'c', 'd'];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'g', 'h'];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'h'];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('efh');
async.done();
});
}));
});
});
}
class Foo {
toString() { return 'foo'; }
}
@Component({selector: 'test-cmp', directives: [NgFor, NgIf], template: ''})
class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
items: any;
constructor() { this.items = [1, 2]; }
trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; }
}
@Component({selector: 'outer-cmp', directives: [TestComponent], template: ''})
class ComponentUsingTestComponent {
items: any;
constructor() { this.items = [1, 2]; }
}

View File

@ -1,259 +0,0 @@
import {
beforeEach,
ddescribe,
describe,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {Component} from '@angular/core';
import {NgIf} from '@angular/common';
import {IS_DART} from '../../src/facade/lang';
export function main() {
describe('ngIf directive', () => {
it('should work in a template attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
async.done();
});
}));
it('should work in a template element',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html =
'<div><template [ngIf]="booleanCondition"><copy-me>hello2</copy-me></template></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello2');
async.done();
});
}));
it('should toggle node when condition changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.booleanCondition = true;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
it('should handle nested if correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html =
'<div><template [ngIf]="booleanCondition"><copy-me *ngIf="nestedBooleanCondition">hello</copy-me></template></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.booleanCondition = true;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.nestedBooleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.nestedBooleanCondition = true;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
it('should update several nodes with if',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html =
'<div>' +
'<copy-me template="ngIf numberCondition + 1 >= 2">helloNumber</copy-me>' +
'<copy-me template="ngIf stringCondition == \'foo\'">helloString</copy-me>' +
'<copy-me template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</copy-me>' +
'</div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(3);
expect(getDOM().getText(fixture.debugElement.nativeElement))
.toEqual('helloNumberhelloStringhelloFunction');
fixture.debugElement.componentInstance.numberCondition = 0;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('helloString');
fixture.debugElement.componentInstance.numberCondition = 1;
fixture.debugElement.componentInstance.stringCondition = "bar";
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('helloNumber');
async.done();
});
}));
if (!IS_DART) {
it('should not add the element twice if the condition goes from true to true (JS)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.numberCondition = 2;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
async.done();
});
}));
it('should not recreate the element if the condition goes from true to true (JS)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
getDOM().addClass(
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'), "foo");
fixture.debugElement.componentInstance.numberCondition = 2;
fixture.detectChanges();
expect(getDOM().hasClass(
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'),
"foo"))
.toBe(true);
async.done();
});
}));
}
if (IS_DART) {
it('should not create the element if the condition is not a boolean (DART)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
expect(() => fixture.detectChanges()).toThrowError();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
}
});
}
@Component({selector: 'test-cmp', directives: [NgIf], template: ''})
class TestComponent {
booleanCondition: boolean;
nestedBooleanCondition: boolean;
numberCondition: number;
stringCondition: string;
functionCondition: Function;
constructor() {
this.booleanCondition = true;
this.nestedBooleanCondition = true;
this.numberCondition = 1;
this.stringCondition = "foo";
this.functionCondition = function(s, n) { return s == "foo" && n == 1; };
}
}

View File

@ -1,135 +0,0 @@
import {
beforeEachProviders,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {Component, Injectable, provide} from '@angular/core';
import {NgPlural, NgPluralCase, NgLocalization} from '@angular/common';
export function main() {
describe('switch', () => {
beforeEachProviders(() => [provide(NgLocalization, {useClass: TestLocalizationMap})]);
it('should display the template according to the exact value',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 0;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
fixture.debugElement.componentInstance.switchValue = 1;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
async.done();
});
}));
it('should display the template according to the category',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.');
fixture.debugElement.componentInstance.switchValue = 8;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have many messages.');
async.done();
});
}));
it('should default to other when no matches are found',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 100;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('default message.');
async.done();
});
}));
it('should prioritize value matches over category matches',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have two messages.');
fixture.debugElement.componentInstance.switchValue = 3;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.');
async.done();
});
}));
});
}
@Injectable()
export class TestLocalizationMap extends NgLocalization {
getPluralCategory(value: number): string {
if (value > 1 && value < 4) {
return 'few';
} else if (value >= 4 && value < 10) {
return 'many';
} else {
return 'other';
}
}
}
@Component({selector: 'test-cmp', directives: [NgPlural, NgPluralCase], template: ''})
class TestComponent {
switchValue: number;
constructor() { this.switchValue = null; }
}

View File

@ -1,155 +0,0 @@
import {
beforeEach,
beforeEachProviders,
ddescribe,
xdescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {StringMapWrapper} from '../../src/facade/collection';
import {Component} from '@angular/core';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {NgStyle} from '@angular/common/src/directives/ng_style';
export function main() {
describe('binding to CSS styles', () => {
it('should add styles specified in an object literal',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
async.done();
});
}));
it('should add and change styles specified in an object expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var expr: Map<string, any>;
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
expr = fixture.debugElement.componentInstance.expr;
expr['max-width'] = '30%';
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('30%');
async.done();
});
}));
it('should remove styles when deleting a key in an object expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('');
async.done();
});
}));
it('should co-operate with the style attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div style="font-size: 12px" [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
.toEqual('12px');
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('');
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
.toEqual('12px');
async.done();
});
}));
it('should co-operate with the style.[styleName]="expr" special-case in the compiler',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [style.font-size.px]="12" [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
.toEqual('12px');
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
.toEqual('12px');
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('');
async.done();
});
}));
})
}
@Component({selector: 'test-cmp', directives: [NgStyle], template: ''})
class TestComponent {
expr;
}

View File

@ -1,158 +0,0 @@
import {
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {Component} from '@angular/core';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from '@angular/common';
export function main() {
describe('switch', () => {
describe('switch value changes', () => {
it('should switch amongst when values',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchWhen="a"><li>when a</li></template>' +
'<template ngSwitchWhen="b"><li>when b</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when b');
async.done();
});
}));
it('should switch amongst when values with fallback to default',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<li template="ngSwitchWhen \'a\'">when a</li>' +
'<li template="ngSwitchDefault">when default</li>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default');
async.done();
});
}));
it('should support multiple whens with the same value',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchWhen="a"><li>when a1;</li></template>' +
'<template ngSwitchWhen="b"><li>when b1;</li></template>' +
'<template ngSwitchWhen="a"><li>when a2;</li></template>' +
'<template ngSwitchWhen="b"><li>when b2;</li></template>' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('when default1;when default2;');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a1;when a2;');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when b1;when b2;');
async.done();
});
}));
});
describe('when values changes', () => {
it('should switch amongst when values',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template [ngSwitchWhen]="when1"><li>when 1;</li></template>' +
'<template [ngSwitchWhen]="when2"><li>when 2;</li></template>' +
'<template ngSwitchDefault><li>when default;</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.when1 = 'a';
fixture.debugElement.componentInstance.when2 = 'b';
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 2;');
fixture.debugElement.componentInstance.switchValue = 'c';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
fixture.debugElement.componentInstance.when1 = 'c';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
fixture.debugElement.componentInstance.when1 = 'd';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
async.done();
});
}));
});
});
}
@Component(
{selector: 'test-cmp', directives: [NgSwitch, NgSwitchWhen, NgSwitchDefault], template: ''})
class TestComponent {
switchValue: any;
when1: any;
when2: any;
constructor() {
this.switchValue = null;
this.when1 = null;
this.when2 = null;
}
}

View File

@ -1,110 +0,0 @@
import {
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {Component, Directive, TemplateRef, ContentChildren, QueryList} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
export function main() {
describe('insert', () => {
it('should do nothing if templateRef is null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<template [ngTemplateOutlet]="null"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
async.done();
});
}));
it('should insert content specified by TemplateRef',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
async.done();
});
}));
it('should clear content if TemplateRef becomes null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = null;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
async.done();
});
}));
it('should swap content if TemplateRef changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.last;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('bar');
async.done();
});
}));
});
}
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
class CaptureTplRefs {
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
}
@Component({selector: 'test-cmp', directives: [NgTemplateOutlet, CaptureTplRefs], template: ''})
class TestComponent {
currentTplRef: TemplateRef<any>;
}

View File

@ -1,71 +0,0 @@
import {
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {Component, Directive} from '@angular/core';
import {ElementRef} from '@angular/core/src/linker/element_ref';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
export function main() {
describe('non-bindable', () => {
it('should not interpolate children',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>{{text}}<span ngNonBindable>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('foo{{text}}');
async.done();
});
}));
it('should ignore directives on child nodes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div ngNonBindable><span id=child test-dec>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
// We must use getDOM().querySelector instead of fixture.query here
// since the elements inside are not compiled.
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
expect(getDOM().hasClass(span, 'compiled')).toBeFalsy();
async.done();
});
}));
it('should trigger directives on the same node',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div><span id=child ngNonBindable test-dec>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
expect(getDOM().hasClass(span, 'compiled')).toBeTruthy();
async.done();
});
}));
})
}
@Directive({selector: '[test-dec]'})
class TestDirective {
constructor(el: ElementRef) { getDOM().addClass(el.nativeElement, 'compiled'); }
}
@Component({selector: 'test-cmp', directives: [TestDirective], template: ''})
class TestComponent {
text: string;
constructor() { this.text = 'foo'; }
}

View File

@ -1,71 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from '@angular/core/testing/testing_internal';
import {Control, FormBuilder} from '@angular/common';
import {PromiseWrapper} from '../../src/facade/promise';
export function main() {
function syncValidator(_) { return null; }
function asyncValidator(_) { return PromiseWrapper.resolve(null); }
describe("Form Builder", () => {
var b;
beforeEach(() => { b = new FormBuilder(); });
it("should create controls from a value", () => {
var g = b.group({"login": "some value"});
expect(g.controls["login"].value).toEqual("some value");
});
it("should create controls from an array", () => {
var g = b.group(
{"login": ["some value"], "password": ["some value", syncValidator, asyncValidator]});
expect(g.controls["login"].value).toEqual("some value");
expect(g.controls["password"].value).toEqual("some value");
expect(g.controls["password"].validator).toEqual(syncValidator);
expect(g.controls["password"].asyncValidator).toEqual(asyncValidator);
});
it("should use controls", () => {
var g = b.group({"login": b.control("some value", syncValidator, asyncValidator)});
expect(g.controls["login"].value).toEqual("some value");
expect(g.controls["login"].validator).toBe(syncValidator);
expect(g.controls["login"].asyncValidator).toBe(asyncValidator);
});
it("should create groups with optional controls", () => {
var g = b.group({"login": "some value"}, {"optionals": {"login": false}});
expect(g.contains("login")).toEqual(false);
});
it("should create groups with a custom validator", () => {
var g = b.group({"login": "some value"},
{"validator": syncValidator, "asyncValidator": asyncValidator});
expect(g.validator).toBe(syncValidator);
expect(g.asyncValidator).toBe(asyncValidator);
});
it("should create control arrays", () => {
var c = b.control("three");
var a = b.array(["one", ["two", syncValidator], c, b.array(['four'])], syncValidator,
asyncValidator);
expect(a.value).toEqual(['one', 'two', 'three', ['four']]);
expect(a.validator).toBe(syncValidator);
expect(a.asyncValidator).toBe(asyncValidator);
});
});
}

File diff suppressed because it is too large Load Diff

View File

@ -1,170 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from '@angular/core/testing/testing_internal';
import {fakeAsync, flushMicrotasks, Log, tick} from '@angular/core/testing';
import {ControlGroup, Control, Validators, AbstractControl, ControlArray} from '@angular/common';
import {PromiseWrapper} from '../../src/facade/promise';
import {EventEmitter, ObservableWrapper, TimerWrapper} from '../../src/facade/async';
export function main() {
function validator(key: string, error: any) {
return function(c: AbstractControl) {
var r = {};
r[key] = error;
return r;
}
}
describe("Validators", () => {
describe("required", () => {
it("should error on an empty string",
() => { expect(Validators.required(new Control(""))).toEqual({"required": true}); });
it("should error on null",
() => { expect(Validators.required(new Control(null))).toEqual({"required": true}); });
it("should not error on a non-empty string",
() => { expect(Validators.required(new Control("not empty"))).toEqual(null); });
it("should accept zero as valid",
() => { expect(Validators.required(new Control(0))).toEqual(null); });
});
describe("minLength", () => {
it("should not error on an empty string",
() => { expect(Validators.minLength(2)(new Control(""))).toEqual(null); });
it("should not error on null",
() => { expect(Validators.minLength(2)(new Control(null))).toEqual(null); });
it("should not error on valid strings",
() => { expect(Validators.minLength(2)(new Control("aa"))).toEqual(null); });
it("should error on short strings", () => {
expect(Validators.minLength(2)(new Control("a")))
.toEqual({"minlength": {"requiredLength": 2, "actualLength": 1}});
});
});
describe("maxLength", () => {
it("should not error on an empty string",
() => { expect(Validators.maxLength(2)(new Control(""))).toEqual(null); });
it("should not error on null",
() => { expect(Validators.maxLength(2)(new Control(null))).toEqual(null); });
it("should not error on valid strings",
() => { expect(Validators.maxLength(2)(new Control("aa"))).toEqual(null); });
it("should error on long strings", () => {
expect(Validators.maxLength(2)(new Control("aaa")))
.toEqual({"maxlength": {"requiredLength": 2, "actualLength": 3}});
});
});
describe("pattern", () => {
it("should not error on an empty string",
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control(""))).toEqual(null); });
it("should not error on null",
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control(null))).toEqual(null); });
it("should not error on valid strings",
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control("aaAA"))).toEqual(null); });
it("should error on failure to match string", () => {
expect(Validators.pattern("[a-zA-Z ]*")(new Control("aaa0")))
.toEqual({"pattern": {"requiredPattern": "^[a-zA-Z ]*$", "actualValue": "aaa0"}});
});
});
describe("compose", () => {
it("should return null when given null",
() => { expect(Validators.compose(null)).toBe(null); });
it("should collect errors from all the validators", () => {
var c = Validators.compose([validator("a", true), validator("b", true)]);
expect(c(new Control(""))).toEqual({"a": true, "b": true});
});
it("should run validators left to right", () => {
var c = Validators.compose([validator("a", 1), validator("a", 2)]);
expect(c(new Control(""))).toEqual({"a": 2});
});
it("should return null when no errors", () => {
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
expect(c(new Control(""))).toEqual(null);
});
it("should ignore nulls", () => {
var c = Validators.compose([null, Validators.required]);
expect(c(new Control(""))).toEqual({"required": true});
});
});
describe("composeAsync", () => {
function asyncValidator(expected, response) {
return (c) => {
var emitter = new EventEmitter();
var res = c.value != expected ? response : null;
PromiseWrapper.scheduleMicrotask(() => {
ObservableWrapper.callEmit(emitter, res);
// this is required because of a bug in ObservableWrapper
// where callComplete can fire before callEmit
// remove this one the bug is fixed
TimerWrapper.setTimeout(() => { ObservableWrapper.callComplete(emitter); }, 0);
});
return emitter;
};
}
it("should return null when given null",
() => { expect(Validators.composeAsync(null)).toEqual(null); });
it("should collect errors from all the validators", fakeAsync(() => {
var c = Validators.composeAsync([
asyncValidator("expected", {"one": true}),
asyncValidator("expected", {"two": true})
]);
var value = null;
(<Promise<any>>c(new Control("invalid"))).then(v => value = v);
tick(1);
expect(value).toEqual({"one": true, "two": true});
}));
it("should return null when no errors", fakeAsync(() => {
var c = Validators.composeAsync([asyncValidator("expected", {"one": true})]);
var value = null;
(<Promise<any>>c(new Control("expected"))).then(v => value = v);
tick(1);
expect(value).toEqual(null);
}));
it("should ignore nulls", fakeAsync(() => {
var c = Validators.composeAsync([asyncValidator("expected", {"one": true}), null]);
var value = null;
(<Promise<any>>c(new Control("invalid"))).then(v => value = v);
tick(1);
expect(value).toEqual({"one": true});
}));
});
});
}

View File

@ -1,82 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from '@angular/core/testing/testing_internal';
import {browserDetection} from '@angular/platform-browser/testing';
import {DatePipe} from '@angular/common';
import {DateWrapper} from '../../src/facade/lang';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
export function main() {
describe("DatePipe", () => {
var date;
var pipe;
beforeEach(() => {
date = DateWrapper.create(2015, 6, 15, 21, 43, 11);
pipe = new DatePipe();
});
it('should be marked as pure',
() => { expect(new PipeResolver().resolve(DatePipe).pure).toEqual(true); });
describe("supports", () => {
it("should support date", () => { expect(pipe.supports(date)).toBe(true); });
it("should support int", () => { expect(pipe.supports(123456789)).toBe(true); });
it("should not support other objects", () => {
expect(pipe.supports(new Object())).toBe(false);
expect(pipe.supports(null)).toBe(false);
});
});
// TODO(mlaval): enable tests when Intl API is no longer used, see
// https://github.com/angular/angular/issues/3333
if (browserDetection.supportsIntlApi) {
describe("transform", () => {
it('should format each component correctly', () => {
expect(pipe.transform(date, 'y')).toEqual('2015');
expect(pipe.transform(date, 'yy')).toEqual('15');
expect(pipe.transform(date, 'M')).toEqual('6');
expect(pipe.transform(date, 'MM')).toEqual('06');
expect(pipe.transform(date, 'MMM')).toEqual('Jun');
expect(pipe.transform(date, 'MMMM')).toEqual('June');
expect(pipe.transform(date, 'd')).toEqual('15');
expect(pipe.transform(date, 'E')).toEqual('Mon');
expect(pipe.transform(date, 'EEEE')).toEqual('Monday');
expect(pipe.transform(date, 'H')).toEqual('21');
expect(pipe.transform(date, 'j')).toEqual('9 PM');
expect(pipe.transform(date, 'm')).toEqual('43');
expect(pipe.transform(date, 's')).toEqual('11');
});
it('should format common multi component patterns', () => {
expect(pipe.transform(date, 'yMEd')).toEqual('Mon, 6/15/2015');
expect(pipe.transform(date, 'MEd')).toEqual('Mon, 6/15');
expect(pipe.transform(date, 'MMMd')).toEqual('Jun 15');
expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('Monday, June 15, 2015');
expect(pipe.transform(date, 'jms')).toEqual('9:43:11 PM');
expect(pipe.transform(date, 'ms')).toEqual('43:11');
});
it('should format with pattern aliases', () => {
expect(pipe.transform(date, 'medium')).toEqual('Jun 15, 2015, 9:43:11 PM');
expect(pipe.transform(date, 'short')).toEqual('6/15/2015, 9:43 PM');
expect(pipe.transform(date, 'fullDate')).toEqual('Monday, June 15, 2015');
expect(pipe.transform(date, 'longDate')).toEqual('June 15, 2015');
expect(pipe.transform(date, 'mediumDate')).toEqual('Jun 15, 2015');
expect(pipe.transform(date, 'shortDate')).toEqual('6/15/2015');
expect(pipe.transform(date, 'mediumTime')).toEqual('9:43:11 PM');
expect(pipe.transform(date, 'shortTime')).toEqual('9:43 PM');
});
});
}
});
}

View File

@ -1,59 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from '@angular/core/testing/testing_internal';
import {I18nPluralPipe} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
export function main() {
describe("I18nPluralPipe", () => {
var pipe;
var mapping = {'=0': 'No messages.', '=1': 'One message.', 'other': 'There are some messages.'};
var interpolatedMapping =
{'=0': 'No messages.', '=1': 'One message.', 'other': 'There are # messages, that is #.'};
beforeEach(() => { pipe = new I18nPluralPipe(); });
it('should be marked as pure',
() => { expect(new PipeResolver().resolve(I18nPluralPipe).pure).toEqual(true); });
describe("transform", () => {
it("should return 0 text if value is 0", () => {
var val = pipe.transform(0, mapping);
expect(val).toEqual('No messages.');
});
it("should return 1 text if value is 1", () => {
var val = pipe.transform(1, mapping);
expect(val).toEqual('One message.');
});
it("should return other text if value is anything other than 0 or 1", () => {
var val = pipe.transform(6, mapping);
expect(val).toEqual('There are some messages.');
});
it("should interpolate the value into the text where indicated", () => {
var val = pipe.transform(6, interpolatedMapping);
expect(val).toEqual('There are 6 messages, that is 6.');
});
it("should use 'other' if value is undefined", () => {
var messageLength;
var val = pipe.transform(messageLength, interpolatedMapping);
expect(val).toEqual('There are messages, that is .');
});
it("should not support bad arguments",
() => { expect(() => pipe.transform(0, 'hey')).toThrowError(); });
});
});
}

Some files were not shown because too many files have changed in this diff Show More