mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
331 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7001ef6429 | |||
| 3d19645208 | |||
| 518fbeac9b | |||
| a25ec0f07d | |||
| 425e3297f8 | |||
| 4c55f793bc | |||
| 75e22abe74 | |||
| 24c0897839 | |||
| 288a0a668a | |||
| f7908baa5c | |||
| 20ab5d89a0 | |||
| b11a303f40 | |||
| d7c11deaf9 | |||
| e821184333 | |||
| 340f1ff89b | |||
| 01c1bfdbdd | |||
| 95ed2b0090 | |||
| 4cfa2098d0 | |||
| c20c76aff8 | |||
| 2bfa8eec7a | |||
| e95cd2a17a | |||
| 6dc74fd5de | |||
| 9dc0a4edf6 | |||
| f5fa8b8606 | |||
| 0163881c3b | |||
| 93dfab9b2a | |||
| 6730493b46 | |||
| 75b53690f6 | |||
| 25d7823022 | |||
| 9cb12ca62c | |||
| 8634611e37 | |||
| dc486c97ea | |||
| 5cb711244f | |||
| f5d7ca6e90 | |||
| 9f197611e2 | |||
| 7bf73d7442 | |||
| 7e52be372a | |||
| b980bf9649 | |||
| 6ed1576eb8 | |||
| 7c9b84f528 | |||
| ed4acb93e9 | |||
| 4b89f8de31 | |||
| e32fe1aa0a | |||
| 7dcc212170 | |||
| acdacd5883 | |||
| 3e6456c76c | |||
| 3f13d95edd | |||
| 3114b0aa12 | |||
| 93a2347348 | |||
| e25d66173e | |||
| 50c19a2dde | |||
| 989d4d4aac | |||
| 8ec71565b2 | |||
| d172882474 | |||
| f57911589f | |||
| cb1d77a9e3 | |||
| df231ad572 | |||
| babbf8a626 | |||
| 2b2d572a46 | |||
| 9db12994f2 | |||
| 93c706f814 | |||
| 27152e2a22 | |||
| d909b44eaf | |||
| 77416103be | |||
| 7a814eb941 | |||
| d884c227b6 | |||
| 2d8b4f2244 | |||
| cff286c645 | |||
| 56926072c4 | |||
| 2506c024ca | |||
| aaa3111d2d | |||
| 29fdbf250b | |||
| a8ac287f00 | |||
| afe1768b39 | |||
| 7d3831c59b | |||
| a0a0179819 | |||
| 123656a727 | |||
| 26a133756b | |||
| 33639db911 | |||
| 2fe2104738 | |||
| 02378451a2 | |||
| 04e469ee16 | |||
| 8ab332b00b | |||
| 68be6f9896 | |||
| c39ed99bcc | |||
| ee14e43d26 | |||
| 9f2ed3096a | |||
| 0a43d090c6 | |||
| 1523ea59bd | |||
| a645573f97 | |||
| 2287cd6b1f | |||
| 3ea3128ac6 | |||
| ab45d5890a | |||
| 26533a0954 | |||
| 84377531cb | |||
| 91faaaccb7 | |||
| 153b42b01e | |||
| 163fdc35f6 | |||
| 9b867b39d6 | |||
| a76706d375 | |||
| fd0cb532dc | |||
| 413c238a05 | |||
| 9f987b635e | |||
| 158581b366 | |||
| 5d8b5eb97b | |||
| db29176075 | |||
| 403d889edf | |||
| 46edc48056 | |||
| 724dc93080 | |||
| 3fcfc5fcd1 | |||
| 10e4ac05b4 | |||
| a88bc643fc | |||
| 752af2ea48 | |||
| 882ff8fc37 | |||
| 1891455784 | |||
| 9e87fc88ff | |||
| 19b77b8a3b | |||
| db62b5f979 | |||
| 05b22a956e | |||
| 53ea4bbc93 | |||
| 271be8aaec | |||
| 9ab3fe732b | |||
| 8a425cc29a | |||
| 9d1131c58c | |||
| 1c3226d45d | |||
| 5080a16933 | |||
| e05cfdd367 | |||
| b363d96724 | |||
| b8f9aa3b5b | |||
| f2d88955bd | |||
| 7f3314a1e8 | |||
| 83a6ab1edc | |||
| 8d317dfa50 | |||
| f1d8717a88 | |||
| 323609d9a7 | |||
| 47a1af6ce3 | |||
| 06de803963 | |||
| a7adcbda54 | |||
| 7ab31c9929 | |||
| e02bea8e53 | |||
| 5fae8b3aa9 | |||
| 005e3c6458 | |||
| 651ba4e331 | |||
| 20db90a41d | |||
| 050cea9252 | |||
| 978f0810d9 | |||
| 888f0e4913 | |||
| 7ddda8a5b0 | |||
| 4dc861fa5a | |||
| 92900d561d | |||
| faf5205a66 | |||
| 6e45f2bee9 | |||
| 28112ca55c | |||
| a17ae55445 | |||
| 3cc9bee1f7 | |||
| 4552b4b554 | |||
| c68ca49d0b | |||
| dd28855455 | |||
| ff925af908 | |||
| ce7d5c4829 | |||
| 62457ca88b | |||
| 0319fc7656 | |||
| 7cb9e19297 | |||
| f51cf295b7 | |||
| bd5c7dd2df | |||
| 2da9a46e70 | |||
| b8e9b3d601 | |||
| 94d001396a | |||
| 92f833bde9 | |||
| 63f9e16309 | |||
| d34e36cc95 | |||
| de4f913662 | |||
| 96ad439d48 | |||
| 6959620002 | |||
| 43b4940c9d | |||
| e9889da457 | |||
| 450830cf1a | |||
| 7ef72d192b | |||
| 6817377590 | |||
| e8654f943c | |||
| b0bbc1f232 | |||
| b6064c2782 | |||
| e34c87e114 | |||
| a0b6548a94 | |||
| 40f38ce7df | |||
| d386fb3e86 | |||
| f868dcb9e8 | |||
| ad4f0fd85c | |||
| 8d0c1bdabf | |||
| 47244dbd00 | |||
| e5caf1189a | |||
| 37128ca1f7 | |||
| ce14f37a58 | |||
| 5c13c67d66 | |||
| 83565229fa | |||
| 1ac05001a1 | |||
| 67ea7b6098 | |||
| b2f3952946 | |||
| 3abf95dca9 | |||
| eb625b993e | |||
| a8e0db73ba | |||
| feda78e1c8 | |||
| 3091534af2 | |||
| 669c4f7ced | |||
| 3b6497b981 | |||
| 0e60dc5597 | |||
| e45da28ed5 | |||
| 5bb5069836 | |||
| ec416d0210 | |||
| e7f262d691 | |||
| 8ea0ee918d | |||
| 6f2b69d94e | |||
| 89835fe8fa | |||
| b0e114e51d | |||
| eccf9d68b8 | |||
| b8c71c45de | |||
| daab797f7d | |||
| 7c52fc690d | |||
| bf05c979a7 | |||
| ddc070e778 | |||
| d746804985 | |||
| 17fe000879 | |||
| 3870d5e629 | |||
| 112f7e9b58 | |||
| 2bc8b807c4 | |||
| 65d1ed38e0 | |||
| 75110f7487 | |||
| 2632510f87 | |||
| f61ac8a60e | |||
| 366be11150 | |||
| 899218de1f | |||
| 399f491894 | |||
| 910fa51e76 | |||
| f184578e2e | |||
| d76beda439 | |||
| 72e0c6b8be | |||
| bf1caa7ad1 | |||
| 4b26a05dbc | |||
| 70daf10b86 | |||
| 1bfc44d48a | |||
| 2a0a287121 | |||
| 523a451a68 | |||
| 13063c45d5 | |||
| 89c6d109e1 | |||
| 6c79598eb6 | |||
| 65abf7effa | |||
| d1840eed36 | |||
| cc329325ac | |||
| 32163ef355 | |||
| 97b4653cab | |||
| 291322fd53 | |||
| a94852c97d | |||
| 758c52b3bf | |||
| a4ea0bbe67 | |||
| 3fbf32534c | |||
| f5710c6902 | |||
| e8e3771002 | |||
| 8bc295ffc3 | |||
| f1a1a205d6 | |||
| b73d59e241 | |||
| 6669571884 | |||
| 84e09a06e3 | |||
| ef28295bf7 | |||
| fc2e3cc84d | |||
| 084b082f5c | |||
| dbc9547643 | |||
| 8144ea5cc9 | |||
| 91e2851f5d | |||
| 6c7e16ab08 | |||
| 54366c3590 | |||
| 252ef62e41 | |||
| 6b7ca5956a | |||
| 1e91958b69 | |||
| 9309a51368 | |||
| 44f245a0c3 | |||
| 298c9822f0 | |||
| 13f9120b92 | |||
| a92b5317bf | |||
| 61e98f8352 | |||
| f2fea8c94f | |||
| 3fcbdc5c25 | |||
| cd2bf969f4 | |||
| 279de9f0ff | |||
| 7d43ca9dfe | |||
| 4e29200dfc | |||
| 0992b678f0 | |||
| c83a389aea | |||
| f875b0d9b6 | |||
| 7f79e77c66 | |||
| 08a4c5b5df | |||
| b4e8399144 | |||
| d8c9f42689 | |||
| 8b36487cbc | |||
| 63b8c04372 | |||
| e30c6519d3 | |||
| ac53101fc6 | |||
| 161b278b6f | |||
| 3738233f16 | |||
| 38a3c31ada | |||
| 595062e415 | |||
| f341da8a43 | |||
| 44913944a9 | |||
| 5167eead45 | |||
| 736e0644b0 | |||
| e0322282e6 | |||
| 51301b9f4b | |||
| d3087be237 | |||
| aba1cb4672 | |||
| 726c5ef679 | |||
| d30911c4eb | |||
| e7f9e6fdac | |||
| 8a744d5246 | |||
| 6c9c70a0e4 | |||
| d2ea9b05d8 | |||
| 60b4135795 | |||
| a0fde29f80 | |||
| 09de939694 | |||
| 13847816d3 | |||
| 63a915b023 | |||
| fdbc61111d | |||
| f541e5f39d | |||
| 20b801e707 | |||
| bed8c1c814 | |||
| 6acddb9518 | |||
| 5e1c7e69e6 | |||
| 61cd1b685a | |||
| b05af6dd53 | |||
| cd4388e523 | |||
| 9269ad9626 | |||
| c1e0776d39 | |||
| 14f15ecd38 |
+1
-1
@@ -1,3 +1,3 @@
|
||||
3.5.1
|
||||
3.6.0
|
||||
# [NB: this comment has to be after the first line, see https://github.com/bazelbuild/bazelisk/issues/117]
|
||||
# When updating the Bazel version you also need to update the RBE toolchains version in package.bzl
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
# https://docs.bazel.build/versions/master/guide.html#bazelrc-syntax-and-semantics
|
||||
try-import %workspace%/.circleci/bazel.common.rc
|
||||
|
||||
# Save downloaded repositories in a location that can be cached by CircleCI. This helps us
|
||||
# speeding up the analysis time significantly with Bazel managed node dependencies on the CI.
|
||||
build --repository_cache=C:/Users/circleci/bazel_repository_cache
|
||||
|
||||
# Manually set the local resources used in windows CI runs
|
||||
build --local_ram_resources=120000
|
||||
build --local_cpu_resources=32
|
||||
|
||||
+22
-15
@@ -3,9 +3,9 @@
|
||||
# Note: YAML anchors allow an object to be re-used, reducing duplication.
|
||||
# The ampersand declares an alias for an object, then later the `<<: *name`
|
||||
# syntax dereferences it.
|
||||
# See http://blog.daemonl.com/2016/02/yaml.html
|
||||
# See https://blog.daemonl.com/2016/02/yaml.html
|
||||
# To validate changes, use an online parser, eg.
|
||||
# http://yaml-online-parser.appspot.com/
|
||||
# https://yaml-online-parser.appspot.com/
|
||||
|
||||
# CircleCI configuration version
|
||||
# Version 2.1 allows for extra config reuse features
|
||||
@@ -22,16 +22,16 @@ version: 2.1
|
||||
# **NOTE 1 **: If you change the cache key prefix, also sync the cache_key_fallback to match.
|
||||
# **NOTE 2 **: Keep the static part of the cache key as prefix to enable correct fallbacks.
|
||||
# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI.
|
||||
var_3: &cache_key v7-angular-node-12-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
|
||||
var_3: &cache_key v1-angular-node-12-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
|
||||
# We invalidate the cache if the Bazel version changes because otherwise the `bazelisk` cache
|
||||
# folder will contain all previously used versions and ultimately cause the cache restoring to
|
||||
# be slower due to its growing size.
|
||||
var_4: &cache_key_fallback v7-angular-node-12-{{ checksum ".bazelversion" }}
|
||||
var_4: &cache_key_fallback v1-angular-node-12-{{ checksum ".bazelversion" }}
|
||||
|
||||
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
|
||||
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
|
||||
var_5: &components_repo_unit_tests_cache_key v9-angular-components-09e68db8ed5b1253f2fe38ff954ef0df019fc25a
|
||||
var_6: &components_repo_unit_tests_cache_key_fallback v9-angular-components-
|
||||
var_5: &components_repo_unit_tests_cache_key v1-angular-components-09e68db8ed5b1253f2fe38ff954ef0df019fc25a
|
||||
var_6: &components_repo_unit_tests_cache_key_fallback v1-angular-components-
|
||||
|
||||
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
|
||||
# `build-ivy-npm-packages`.
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
git config user.name "angular-ci"
|
||||
git config user.email "angular-ci"
|
||||
# Rebase PR on top of target branch.
|
||||
node tools/rebase-pr.js
|
||||
node .circleci/rebase-pr.js
|
||||
else
|
||||
echo "This build is not over a PR, nothing to do."
|
||||
fi
|
||||
@@ -447,7 +447,7 @@ jobs:
|
||||
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
|
||||
# Since the parallelism is set to "5", there will be five parallel CircleCI containers.
|
||||
# with either "0", "1", etc as node index. This can be passed to the "--shard" argument.
|
||||
- run: yarn --cwd aio example-e2e --setup --local <<# parameters.viewengine >>--viewengine<</ parameters.viewengine >> --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} --retry 2
|
||||
- run: yarn --cwd aio example-e2e --setup --local <<# parameters.viewengine >>--viewengine<</ parameters.viewengine >> --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL}
|
||||
|
||||
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
||||
aio_preview:
|
||||
@@ -748,19 +748,26 @@ jobs:
|
||||
steps:
|
||||
- setup_win
|
||||
- run:
|
||||
# Ran into a command parsing problem where `-browser:chromium-local` was converted to
|
||||
# `-browser: chromium-local` (a space was added) in https://circleci.com/gh/angular/angular/357511.
|
||||
# Probably a powershell command parsing thing. There's no problem using a yarn script though.
|
||||
command: yarn circleci-win-ve
|
||||
no_output_timeout: 45m
|
||||
name: Build all windows CI targets
|
||||
command: yarn bazel build --build_tag_filters=-ivy-only //packages/compiler-cli/... //tools/ts-api-guardian/...
|
||||
no_output_timeout: 15m
|
||||
- run:
|
||||
name: Test all windows CI targets
|
||||
command: yarn bazel test --test_tag_filters="-ivy-only,-browser:chromium-local" //packages/compiler-cli/... //tools/ts-api-guardian/...
|
||||
no_output_timeout: 15m
|
||||
|
||||
test_ivy_aot_win:
|
||||
executor: windows-executor
|
||||
steps:
|
||||
- setup_win
|
||||
- run:
|
||||
command: yarn circleci-win-ivy
|
||||
no_output_timeout: 45m
|
||||
name: Build all windows CI targets
|
||||
command: yarn bazel build --config=ivy --build_tag_filters=-no-ivy-aot,-fixme-ivy-aot //packages/compiler-cli/... //tools/ts-api-guardian/...
|
||||
no_output_timeout: 15m
|
||||
- run:
|
||||
name: Test all windows CI targets
|
||||
command: yarn bazel test --config=ivy --test_tag_filters="-no-ivy-aot,-fixme-ivy-aot,-browser:chromium-local" //packages/compiler-cli/... //tools/ts-api-guardian/...
|
||||
no_output_timeout: 15m
|
||||
|
||||
|
||||
workflows:
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rebases the current branch on top of the GitHub PR target branch.
|
||||
*
|
||||
* **Context:**
|
||||
* Since a GitHub PR is not necessarily up to date with its target branch, it is useful to rebase
|
||||
* prior to testing it on CI to ensure more up to date test results.
|
||||
*
|
||||
* **NOTE:**
|
||||
* This script cannot use external dependencies or be compiled because it needs to run before the
|
||||
* environment is setup.
|
||||
* Use only features supported by the NodeJS versions used in the environment.
|
||||
*/
|
||||
// tslint:disable:no-console
|
||||
const {execSync} = require('child_process');
|
||||
|
||||
|
||||
/** A regex to select a ref that matches our semver refs. */
|
||||
const semverRegex = /^(\d+)\.(\d+)\.x$/;
|
||||
|
||||
// Run
|
||||
_main().catch(err => {
|
||||
console.log('Failed to rebase on top of target branch.\n');
|
||||
console.error(err);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
// Helpers
|
||||
async function _main() {
|
||||
const refs = await getRefsAndShasForChange();
|
||||
|
||||
// Log known refs and shas
|
||||
console.log(`--------------------------------`);
|
||||
console.log(` Target Branch: ${refs.base.ref}`);
|
||||
console.log(` Latest Commit for Target Branch: ${refs.target.latestSha}`);
|
||||
console.log(` Latest Commit for PR: ${refs.base.latestSha}`);
|
||||
console.log(` First Common Ancestor SHA: ${refs.commonAncestorSha}`);
|
||||
console.log(`--------------------------------`);
|
||||
console.log();
|
||||
|
||||
// Get the count of commits between the latest commit from origin and the common ancestor SHA.
|
||||
const commitCount =
|
||||
exec(`git rev-list --count origin/${refs.base.ref}...${refs.commonAncestorSha}`);
|
||||
console.log(`Checking ${commitCount} commits for changes in the CircleCI config file.`);
|
||||
|
||||
// Check if the files changed between the latest commit from origin and the common ancestor SHA
|
||||
// includes the CircleCI config.
|
||||
const circleCIConfigChanged = exec(`git diff --name-only origin/${refs.base.ref} ${
|
||||
refs.commonAncestorSha} -- .circleci/config.yml`);
|
||||
|
||||
if (!!circleCIConfigChanged) {
|
||||
throw Error(`
|
||||
CircleCI config on ${refs.base.ref} has been modified since commit
|
||||
${refs.commonAncestorSha.slice(0, 7)}, which this PR is based on.
|
||||
|
||||
Please rebase the PR on ${refs.base.ref} after fetching from upstream.
|
||||
|
||||
Rebase instructions for PR Author, please run the following commands:
|
||||
|
||||
git fetch upstream ${refs.base.ref};
|
||||
git checkout ${refs.target.ref};
|
||||
git rebase upstream/${refs.base.ref};
|
||||
git push --force-with-lease;
|
||||
`);
|
||||
} else {
|
||||
console.log('No change found in the CircleCI config file, continuing.');
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Rebase the PR.
|
||||
exec(`git rebase origin/${refs.base.ref}`);
|
||||
console.log(`Rebased current branch onto ${refs.base.ref}.`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sort a list of fullpath refs into a list and then provide the first entry.
|
||||
*
|
||||
* The sort order will first find master ref, and then any semver ref, followed
|
||||
* by the rest of the refs in the order provided.
|
||||
*
|
||||
* Branches are sorted in this order as work is primarily done on master, and
|
||||
* otherwise on a semver branch. If neither of those were to match, the most
|
||||
* likely correct branch will be the first one encountered in the list.
|
||||
*/
|
||||
function getRefFromBranchList(gitOutput) {
|
||||
const branches = gitOutput.split('\n').map(b => b.split('/').slice(1).join('').trim());
|
||||
return branches.sort((a, b) => {
|
||||
if (a === 'master') {
|
||||
return -1;
|
||||
}
|
||||
if (b === 'master') {
|
||||
return 1;
|
||||
}
|
||||
const aIsSemver = semverRegex.test(a);
|
||||
const bIsSemver = semverRegex.test(b);
|
||||
if (aIsSemver && bIsSemver) {
|
||||
const [, aMajor, aMinor] = a.match(semverRegex);
|
||||
const [, bMajor, bMinor] = b.match(semverRegex);
|
||||
return parseInt(bMajor, 10) - parseInt(aMajor, 10) ||
|
||||
parseInt(aMinor, 10) - parseInt(bMinor, 10) || 0;
|
||||
}
|
||||
if (aIsSemver) {
|
||||
return -1;
|
||||
}
|
||||
if (bIsSemver) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
})[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full sha of the ref provided.
|
||||
*
|
||||
* example: 1bc0c1a6c01ede7168f22fa9b3508ba51f1f464e
|
||||
*/
|
||||
function getShaFromRef(ref) {
|
||||
return exec(`git rev-parse ${ref}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of branches which contain the provided sha, sorted in descending order
|
||||
* by committerdate.
|
||||
*
|
||||
* example:
|
||||
* upstream/master
|
||||
* upstream/9.0.x
|
||||
* upstream/test
|
||||
* upstream/1.1.x
|
||||
*/
|
||||
function getBranchListForSha(sha, remote) {
|
||||
return exec(`git branch -r '${remote}/*' --sort=-committerdate --contains ${sha}`);
|
||||
}
|
||||
|
||||
/** Get the common ancestor sha of the two provided shas. */
|
||||
function getCommonAncestorSha(sha1, sha2) {
|
||||
return exec(`git merge-base ${sha1} ${sha2}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the remote to git, if it doesn't already exist. Returns a boolean indicating
|
||||
* whether the remote was added by the command.
|
||||
*/
|
||||
function addAndFetchRemote(owner, name) {
|
||||
const remoteName = `${owner}_${name}`;
|
||||
exec(`git remote add ${remoteName} https://github.com/${owner}/${name}.git`, false);
|
||||
exec(`git fetch ${remoteName}`);
|
||||
return remoteName;
|
||||
}
|
||||
|
||||
|
||||
/** Get the ref and latest shas for the provided sha on a specific remote. */
|
||||
function getRefAndShas(sha, owner, name) {
|
||||
const remoteName = addAndFetchRemote(owner, name);
|
||||
|
||||
// Get the ref on the remote for the sha provided.
|
||||
const branches = getBranchListForSha(sha, remoteName);
|
||||
const ref = getRefFromBranchList(branches);
|
||||
|
||||
// Get the latest sha on the discovered remote ref.
|
||||
const latestSha = getShaFromRef(`${remoteName}/${ref}`);
|
||||
|
||||
return {remote: remoteName, ref, latestSha, sha};
|
||||
}
|
||||
|
||||
|
||||
/** Gets the refs and shas for the base and target of the current environment. */
|
||||
function getRefsAndShasForChange() {
|
||||
const base = getRefAndShas(
|
||||
process.env['CI_GIT_BASE_REVISION'], process.env['CI_REPO_OWNER'],
|
||||
process.env['CI_REPO_NAME']);
|
||||
const target = getRefAndShas(
|
||||
process.env['CI_GIT_REVISION'], process.env['CI_PR_USERNAME'], process.env['CI_PR_REPONAME']);
|
||||
const commonAncestorSha = getCommonAncestorSha(base.sha, target.sha);
|
||||
return {
|
||||
base,
|
||||
target,
|
||||
commonAncestorSha,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Synchronously executes the command.
|
||||
*
|
||||
* Return the trimmed stdout as a string, with an added attribute of the exit code.
|
||||
*/
|
||||
function exec(command, allowStderr = true) {
|
||||
let output = new String();
|
||||
output.code = 0;
|
||||
try {
|
||||
output += execSync(command, {stdio: ['pipe', 'pipe', 'pipe']}).toString().trim();
|
||||
} catch (err) {
|
||||
allowStderr && console.error(err.stderr.toString());
|
||||
output.code = err.status;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@@ -165,6 +165,12 @@ triage:
|
||||
-
|
||||
- "discussion"
|
||||
- "comp: *"
|
||||
-
|
||||
- "needs clarification"
|
||||
- "comp: *"
|
||||
-
|
||||
- "needs reproduction"
|
||||
- "comp: *"
|
||||
|
||||
# options for the triage PR plugin
|
||||
triagePR:
|
||||
@@ -181,7 +187,6 @@ triagePR:
|
||||
# arrays of labels that determine if a PR has been fully triaged
|
||||
l2TriageLabels:
|
||||
-
|
||||
- "type: *"
|
||||
- "effort*"
|
||||
- "risk*"
|
||||
- "comp: *"
|
||||
|
||||
@@ -10,6 +10,6 @@ jobs:
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/lock-closed@414834b2b24dd2df37c6ed00808387ee6fd91b66
|
||||
- uses: angular/dev-infra/github-actions/lock-closed@0fc6f4d839e93312ed0dd9a2be88d4c11e947a0b
|
||||
with:
|
||||
lock-bot-key: ${{ secrets.LOCK_BOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -48,3 +48,9 @@ baseline.json
|
||||
|
||||
# Ignore .history for the xyz.local-history VSCode extension
|
||||
.history
|
||||
|
||||
# CLDR data
|
||||
tools/gulp-tasks/cldr/cldr-data/
|
||||
|
||||
# Husky
|
||||
.husky/_
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
_
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname $0)/_/husky.sh"
|
||||
|
||||
yarn -s ng-dev commit-message pre-commit-validate --file $1;
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname $0)/_/husky.sh"
|
||||
|
||||
yarn -s ng-dev format staged;
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname $0)/_/husky.sh"
|
||||
|
||||
yarn -s ng-dev commit-message restore-commit-message-draft $1 $2;
|
||||
@@ -16,6 +16,8 @@ export const format: FormatConfig = {
|
||||
'!.yarn/**',
|
||||
// Do not format d.ts files as they are generated
|
||||
'!**/*.d.ts',
|
||||
// Do not format generated ng-dev script
|
||||
'!dev-infra/ng-dev.js',
|
||||
]
|
||||
},
|
||||
'buildifier': true
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ export const merge: DevInfraMergeConfig['merge'] = async api => {
|
||||
githubApiMerge: false,
|
||||
claSignedLabel: 'cla: yes',
|
||||
mergeReadyLabel: /^action: merge(-assistance)?/,
|
||||
caretakerNoteLabel: 'action: merge-assistance',
|
||||
caretakerNoteLabel: /^(action: merge-assistance)|(PullApprove: disable)/,
|
||||
commitMessageFixupLabel: 'commit message fixup',
|
||||
// We can pick any of the NPM packages as we are in a monorepo where all packages are
|
||||
// published together with the same version and branching.
|
||||
|
||||
+6
-4
@@ -18,14 +18,16 @@ export const release: ReleaseConfig = {
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
'@angular/platform-server',
|
||||
'@angular/platform-webworker',
|
||||
'@angular/platform-webworker-dynamic',
|
||||
'@angular/router',
|
||||
'@angular/service-worker',
|
||||
'@angular/upgrade',
|
||||
],
|
||||
// TODO: Implement release package building here.
|
||||
buildPackages: async () => [],
|
||||
buildPackages: async () => {
|
||||
// The buildTargetPackages function is loaded at runtime as the loading the script causes an
|
||||
// invocation of bazel.
|
||||
const {buildTargetPackages} = require(join(__dirname, '../scripts/build/package-builder'));
|
||||
return buildTargetPackages('dist/release-output', false, 'Release');
|
||||
},
|
||||
// TODO: This can be removed once there is an org-wide tool for changelog generation.
|
||||
generateReleaseNotesForHead: async () => {
|
||||
exec('yarn -s gulp changelog', {cwd: join(__dirname, '../')});
|
||||
|
||||
+19
-2
@@ -103,6 +103,8 @@ meta:
|
||||
github_api_version: "shadow-cat-preview"
|
||||
|
||||
pullapprove_conditions:
|
||||
# For PRs which are still being worked on, either still in draft mode or indicated through WIP in
|
||||
# title or label, PullApprove stays in a pending state until its ready for review.
|
||||
- condition: "'WIP' not in title"
|
||||
unmet_status: pending
|
||||
explanation: "Waiting to send reviews as PR is WIP"
|
||||
@@ -112,6 +114,10 @@ pullapprove_conditions:
|
||||
- condition: "not draft"
|
||||
unmet_status: pending
|
||||
explanation: "Waiting to send reviews as PR is in draft"
|
||||
# Disable PullApprove on specific PRs by adding the `PullApprove: disable` label
|
||||
- condition: "'PullApprove: disable' not in labels"
|
||||
unmet_status: success
|
||||
explanation: "PullApprove skipped because of 'PullApprove: disable' label"
|
||||
|
||||
|
||||
groups:
|
||||
@@ -186,6 +192,7 @@ groups:
|
||||
- IgorMinar # Igor Minar
|
||||
- jbogarthyde # Judy Bogart
|
||||
- jelbourn # Jeremy Elbourn
|
||||
- jessicajaniuk # Jessica Janiuk
|
||||
- JiaLiPassion # Jia Li
|
||||
- JoostK # Joost Koehoorn
|
||||
- josephperrott # Joey Perrott
|
||||
@@ -323,6 +330,8 @@ groups:
|
||||
'aio/content/guide/component-interaction.md',
|
||||
'aio/content/examples/component-interaction/**',
|
||||
'aio/content/images/guide/component-interaction/**',
|
||||
'aio/content/guide/component-overview.md',
|
||||
'aio/content/examples/component-overview/**',
|
||||
'aio/content/guide/component-styles.md',
|
||||
'aio/content/guide/view-encapsulation.md',
|
||||
'aio/content/examples/component-styles/**',
|
||||
@@ -341,6 +350,7 @@ groups:
|
||||
'aio/content/guide/dynamic-component-loader.md',
|
||||
'aio/content/examples/dynamic-component-loader/**',
|
||||
'aio/content/images/guide/dynamic-component-loader/**',
|
||||
'aio/content/guide/example-apps-list.md',
|
||||
'aio/content/guide/entry-components.md',
|
||||
'aio/content/guide/feature-modules.md',
|
||||
'aio/content/examples/feature-modules/**',
|
||||
@@ -368,6 +378,7 @@ groups:
|
||||
'aio/content/guide/built-in-template-functions.md',
|
||||
'aio/content/examples/built-in-template-functions/**',
|
||||
'aio/content/guide/event-binding.md',
|
||||
'aio/content/guide/event-binding-concepts.md',
|
||||
'aio/content/examples/event-binding/**',
|
||||
'aio/content/guide/interpolation.md',
|
||||
'aio/content/examples/interpolation/**',
|
||||
@@ -377,6 +388,7 @@ groups:
|
||||
'aio/content/examples/binding-syntax/**',
|
||||
'aio/content/guide/property-binding.md',
|
||||
'aio/content/examples/property-binding/**',
|
||||
'aio/content/guide/property-binding-best-practices.md',
|
||||
'aio/content/guide/attribute-binding.md',
|
||||
'aio/content/examples/attribute-binding/**',
|
||||
'aio/content/guide/two-way-binding.md',
|
||||
@@ -405,6 +417,7 @@ groups:
|
||||
'aio/content/guide/structural-directives.md',
|
||||
'aio/content/examples/structural-directives/**',
|
||||
'aio/content/guide/svg-in-templates.md',
|
||||
'aio/content/guide/style-precedence.md',
|
||||
'aio/content/images/guide/structural-directives/**',
|
||||
'aio/content/guide/template-statements.md',
|
||||
'aio/content/guide/user-input.md',
|
||||
@@ -418,6 +431,7 @@ groups:
|
||||
- atscott
|
||||
- ~kara # do not request reviews from Kara, but allow her to approve PRs
|
||||
- mhevery
|
||||
- jessicajaniuk
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
@@ -644,7 +658,7 @@ groups:
|
||||
- *can-be-global-docs-approved
|
||||
- >
|
||||
contains_any_globs(files.exclude('packages/compiler-cli/**'), [
|
||||
'**/testing/**',
|
||||
'packages/**/testing/**',
|
||||
'aio/content/guide/testing.md',
|
||||
'aio/content/guide/test-debugging.md',
|
||||
'aio/content/guide/testing-attribute-directives.md',
|
||||
@@ -945,7 +959,7 @@ groups:
|
||||
'aio/content/guide/migration-localize.md',
|
||||
'aio/content/guide/migration-module-with-providers.md',
|
||||
'aio/content/guide/static-query-migration.md',
|
||||
'aio/content/guide/updating-to-version-10.md',
|
||||
'aio/content/guide/updating-to-version-11.md',
|
||||
'aio/content/guide/ivy-compatibility.md',
|
||||
'aio/content/guide/ivy-compatibility-examples.md'
|
||||
])
|
||||
@@ -1011,6 +1025,7 @@ groups:
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- alan-agius4
|
||||
- clydin
|
||||
- IgorMinar
|
||||
- mgechev
|
||||
@@ -1103,6 +1118,7 @@ groups:
|
||||
'.circleci/**',
|
||||
'.devcontainer/**',
|
||||
'.github/**',
|
||||
'.husky/**',
|
||||
'.ng-dev/**',
|
||||
'.vscode/**',
|
||||
'.yarn/**',
|
||||
@@ -1120,6 +1136,7 @@ groups:
|
||||
'docs/SAVED_REPLIES.md',
|
||||
'docs/TOOLS.md',
|
||||
'docs/TRIAGE_AND_LABELS.md',
|
||||
'docs/images/**',
|
||||
'goldens/*',
|
||||
'modules/*',
|
||||
'packages/*',
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
|
||||
+264
-284
@@ -1,53 +1,282 @@
|
||||
<a name="11.0.0-next.6"></a>
|
||||
# 11.0.0-next.6 (2020-10-14)
|
||||
<a name="11.0.3"></a>
|
||||
## 11.0.3 (2020-12-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** getAnimationStyle causes exceptions in older browsers ([#29709](https://github.com/angular/angular/issues/29709)) ([cb1d77a](https://github.com/angular/angular/commit/cb1d77a))
|
||||
* **animations:** replace copy of query selector node-list from "spread" to "for" ([#39646](https://github.com/angular/angular/issues/39646)) ([e95cd2a](https://github.com/angular/angular/commit/e95cd2a)), closes [#38551](https://github.com/angular/angular/issues/38551)
|
||||
* **common:** Prefer to use pageXOffset / pageYOffset instance of scrollX / scrollY ([#28262](https://github.com/angular/angular/issues/28262)) ([5692607](https://github.com/angular/angular/commit/5692607))
|
||||
* **compiler:** ensure that placeholders have the correct sourceSpan ([#39717](https://github.com/angular/angular/issues/39717)) ([8ec7156](https://github.com/angular/angular/commit/8ec7156)), closes [#39671](https://github.com/angular/angular/issues/39671)
|
||||
* **compiler:** report better error on interpolation in an expression ([#30300](https://github.com/angular/angular/issues/30300)) ([6dc74fd](https://github.com/angular/angular/commit/6dc74fd))
|
||||
* **compiler-cli:** report error when a reference target is missing instead of crashing ([#39805](https://github.com/angular/angular/issues/39805)) ([8634611](https://github.com/angular/angular/commit/8634611)), closes [#38618](https://github.com/angular/angular/issues/38618) [#39744](https://github.com/angular/angular/issues/39744)
|
||||
* **core:** Ensure OnPush ancestors are marked dirty when events occur ([#39833](https://github.com/angular/angular/issues/39833)) ([01c1bfd](https://github.com/angular/angular/commit/01c1bfd)), closes [#39832](https://github.com/angular/angular/issues/39832)
|
||||
* **core:** meta addTag() adds incorrect attribute for httpEquiv ([#32531](https://github.com/angular/angular/issues/32531)) ([3114b0a](https://github.com/angular/angular/commit/3114b0a))
|
||||
* **core:** migration error if program contains files outside of the project ([#39790](https://github.com/angular/angular/issues/39790)) ([7dcc212](https://github.com/angular/angular/commit/7dcc212)), closes [#39778](https://github.com/angular/angular/issues/39778)
|
||||
* **core:** not invoking object's toString when rendering to the DOM ([#39843](https://github.com/angular/angular/issues/39843)) ([75e22ab](https://github.com/angular/angular/commit/75e22ab)), closes [#38839](https://github.com/angular/angular/issues/38839)
|
||||
* **core:** remove duplicated noop function ([#39761](https://github.com/angular/angular/issues/39761)) ([26a1337](https://github.com/angular/angular/commit/26a1337))
|
||||
* **core:** support `Attribute` DI decorator in `deps` section of a token ([#37085](https://github.com/angular/angular/issues/37085)) ([aaa3111](https://github.com/angular/angular/commit/aaa3111)), closes [#36479](https://github.com/angular/angular/issues/36479)
|
||||
* **router:** correctly handle string command in outlets ([#39728](https://github.com/angular/angular/issues/39728)) ([50c19a2](https://github.com/angular/angular/commit/50c19a2)), closes [#18928](https://github.com/angular/angular/issues/18928)
|
||||
* **router:** remove duplicated getOutlet function ([#39764](https://github.com/angular/angular/issues/39764)) ([df231ad](https://github.com/angular/angular/commit/df231ad))
|
||||
* **service-worker:** correctly handle failed cache-busted request ([#39786](https://github.com/angular/angular/issues/39786)) ([7bf73d7](https://github.com/angular/angular/commit/7bf73d7)), closes [#39775](https://github.com/angular/angular/issues/39775) [#39775](https://github.com/angular/angular/issues/39775)
|
||||
|
||||
### DEPRECATIONS
|
||||
|
||||
* **forms:** Mark the {[key: string]: any} type for the options property of the FormBuilder.group method as deprecated. Using AbstractControlOptions gives the same functionality and is type-safe.
|
||||
|
||||
|
||||
<a name="11.0.2"></a>
|
||||
## 11.0.2 (2020-11-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **router:** migration incorrectly replacing deprecated key ([#39763](https://github.com/angular/angular/issues/39763)) ([0237845](https://github.com/angular/angular/commit/0237845)), closes [#38762](https://github.com/angular/angular/issues/38762) [#39755](https://github.com/angular/angular/issues/39755)
|
||||
|
||||
|
||||
|
||||
<a name="11.0.1"></a>
|
||||
## 11.0.1 (2020-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** incorrectly type checking calls to implicit template variables ([#39686](https://github.com/angular/angular/issues/39686)) ([e05cfdd](https://github.com/angular/angular/commit/e05cfdd)), closes [#39634](https://github.com/angular/angular/issues/39634)
|
||||
* **compiler-cli:** setComponentScope should only list used components/pipes ([#39662](https://github.com/angular/angular/issues/39662)) ([8d317df](https://github.com/angular/angular/commit/8d317df))
|
||||
* **core:** handle !important in style property value ([#39603](https://github.com/angular/angular/issues/39603)) ([978f081](https://github.com/angular/angular/commit/978f081)), closes [#35323](https://github.com/angular/angular/issues/35323)
|
||||
* **core:** not inserting ViewContainerRef nodes when inside root of a component ([#39599](https://github.com/angular/angular/issues/39599)) ([20db90a](https://github.com/angular/angular/commit/20db90a)), closes [#39556](https://github.com/angular/angular/issues/39556)
|
||||
* **core:** remove deprecated wtfZoneSpec from NgZone ([#37864](https://github.com/angular/angular/issues/37864)) ([e02bea8](https://github.com/angular/angular/commit/e02bea8)), closes [#33949](https://github.com/angular/angular/issues/33949)
|
||||
* **forms:** more precise control cleanup ([#39623](https://github.com/angular/angular/issues/39623)) ([050cea9](https://github.com/angular/angular/commit/050cea9))
|
||||
* **http:** queue jsonp <script> tag onLoad event handler in microtask ([#39512](https://github.com/angular/angular/issues/39512)) ([10e4ac0](https://github.com/angular/angular/commit/10e4ac0)), closes [#39496](https://github.com/angular/angular/issues/39496)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler:** optimize computation of i18n message ids ([#39694](https://github.com/angular/angular/issues/39694)) ([1891455](https://github.com/angular/angular/commit/1891455))
|
||||
* **compiler:** use raw bytes to represent utf-8 encoded strings ([#39694](https://github.com/angular/angular/issues/39694)) ([882ff8f](https://github.com/angular/angular/commit/882ff8f))
|
||||
* **compiler-cli:** reduce filesystem hits during resource resolution ([#39604](https://github.com/angular/angular/issues/39604)) ([a7adcbd](https://github.com/angular/angular/commit/a7adcbd))
|
||||
|
||||
|
||||
|
||||
<a name="11.0.0"></a>
|
||||
# 11.0.0 (2020-11-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** only providing stamping information if the `--stamp` flag is used ([#39392](https://github.com/angular/angular/issues/39392)) ([84e09a0](https://github.com/angular/angular/commit/84e09a0))
|
||||
* **common:** do not round up fractions of a millisecond in `DatePipe` ([#38009](https://github.com/angular/angular/issues/38009)) ([26f2820](https://github.com/angular/angular/commit/26f2820)), closes [#37989](https://github.com/angular/angular/issues/37989)
|
||||
* **common:** mark locale data arrays as readonly ([#30397](https://github.com/angular/angular/issues/30397)) ([6acea54](https://github.com/angular/angular/commit/6acea54)), closes [#27003](https://github.com/angular/angular/issues/27003)
|
||||
* **common:** add `params` and `reportProgress` options to `HttpClient.put()` overload ([#37873](https://github.com/angular/angular/issues/37873)) ([dd8d8c8](https://github.com/angular/angular/commit/dd8d8c8)), closes [#23600](https://github.com/angular/angular/issues/23600)
|
||||
* **common:** correct and simplify typing of `KeyValuePipe` ([#37447](https://github.com/angular/angular/issues/37447)) ([4dfe0fa](https://github.com/angular/angular/commit/4dfe0fa))
|
||||
* **common:** correct and simplify typing of `AsyncPipe` ([#37447](https://github.com/angular/angular/issues/37447)) ([5f815c0](https://github.com/angular/angular/commit/5f815c0))
|
||||
* **common:** correct and simplify typing of `I18nPluralPipe` ([#37447](https://github.com/angular/angular/issues/37447)) ([3b919ef](https://github.com/angular/angular/commit/3b919ef))
|
||||
* **common:** correct typing and implementation of `SlicePipe` ([#37447](https://github.com/angular/angular/issues/37447)) ([4744c22](https://github.com/angular/angular/commit/4744c22))
|
||||
* **common:** let case conversion pipes accept type unions with `null` ([#37447](https://github.com/angular/angular/issues/37447)) ([c7d5555](https://github.com/angular/angular/commit/c7d5555)), closes [#36259](https://github.com/angular/angular/issues/36259)
|
||||
* **common:** add boolean to valid json for testing ([#37893](https://github.com/angular/angular/issues/37893)) ([3c474ec](https://github.com/angular/angular/commit/3c474ec)), closes [#20690](https://github.com/angular/angular/issues/20690)
|
||||
* **common:** update locales using new CLDR data ([#39343](https://github.com/angular/angular/issues/39343)) ([3738233](https://github.com/angular/angular/commit/3738233))
|
||||
* **common:** change the week-numbering year format from `r` -> `Y` ([#39495](https://github.com/angular/angular/issues/39495)) ([feda78e](https://github.com/angular/angular/commit/feda78e))
|
||||
* **compiler:** source span for microsyntax text att should be key span ([#38766](https://github.com/angular/angular/issues/38766)) ([8f349b2](https://github.com/angular/angular/commit/8f349b2))
|
||||
* **compiler:** promote constants in templates to Trusted Types ([#39211](https://github.com/angular/angular/issues/39211)) ([6e18d2d](https://github.com/angular/angular/commit/6e18d2d))
|
||||
* **compiler:** do not throw away render3 AST on errors ([#39413](https://github.com/angular/angular/issues/39413)) ([d76beda](https://github.com/angular/angular/commit/d76beda))
|
||||
* **compiler:** treat i18n attributes with no bindings as static attributes ([#39408](https://github.com/angular/angular/issues/39408)) ([bf1caa7](https://github.com/angular/angular/commit/bf1caa7)), closes [#38231](https://github.com/angular/angular/issues/38231)
|
||||
* **compiler:** preserve `this.$event` and `this.$any` accesses in expressions ([#39323](https://github.com/angular/angular/issues/39323)) ([a8e0db7](https://github.com/angular/angular/commit/a8e0db7)), closes [#30278](https://github.com/angular/angular/issues/30278)
|
||||
* **compiler:** ensure that i18n message-parts have the correct source-span ([#39486](https://github.com/angular/angular/issues/39486)) ([63f9e16](https://github.com/angular/angular/commit/63f9e16))
|
||||
* **compiler:** skipping leading whitespace should not break placeholder source-spans ([#39486](https://github.com/angular/angular/issues/39486)) ([b8e9b3d](https://github.com/angular/angular/commit/b8e9b3d)), closes [#39195](https://github.com/angular/angular/issues/39195)
|
||||
* **compiler-cli:** compute source-mappings for localized strings ([#38645](https://github.com/angular/angular/issues/38645)) ([7e0b3fd](https://github.com/angular/angular/commit/7e0b3fd))
|
||||
* **compiler-cli:** generate `let` statements in ES2015+ mode ([#38775](https://github.com/angular/angular/issues/38775)) ([123bff7](https://github.com/angular/angular/commit/123bff7))
|
||||
* **compiler-cli:** perform DOM schema checks even in basic mode in g3 ([#38943](https://github.com/angular/angular/issues/38943)) ([40975e0](https://github.com/angular/angular/commit/40975e0))
|
||||
* **compiler-cli:** type checking of expressions within ICUs ([#39072](https://github.com/angular/angular/issues/39072)) ([0a16e60](https://github.com/angular/angular/commit/0a16e60)), closes [#39064](https://github.com/angular/angular/issues/39064)
|
||||
* **core:** migrate relative link resolution with single quotes ([#39102](https://github.com/angular/angular/issues/39102)) ([049b453](https://github.com/angular/angular/commit/049b453)), closes [#39082](https://github.com/angular/angular/issues/39082)
|
||||
* **compiler-cli:** generating invalid `setClassMetadata` call in ES5 for class with custom decorator ([#39527](https://github.com/angular/angular/issues/39527)) ([b0bbc1f](https://github.com/angular/angular/commit/b0bbc1f)), closes [#39509](https://github.com/angular/angular/issues/39509)
|
||||
* **compiler-cli:** report missing pipes when `fullTemplateTypeCheck` is disabled ([#39320](https://github.com/angular/angular/issues/39320)) ([67ea7b6](https://github.com/angular/angular/commit/67ea7b6)), closes [#38195](https://github.com/angular/angular/issues/38195)
|
||||
* **compiler-cli:** avoid duplicate diagnostics about unknown pipes ([#39517](https://github.com/angular/angular/issues/39517)) ([c68ca49](https://github.com/angular/angular/commit/c68ca49))
|
||||
* **compiler-cli:** do not drop non-Angular decorators when downleveling ([#39577](https://github.com/angular/angular/issues/39577)) ([f51cf29](https://github.com/angular/angular/commit/f51cf29)), closes [#39574](https://github.com/angular/angular/issues/39574)
|
||||
* **core:** remove `CollectionChangeRecord` symbol ([#38668](https://github.com/angular/angular/issues/38668)) ([fdea180](https://github.com/angular/angular/commit/fdea180))
|
||||
* **core:** ensure `TestBed` is not instantiated before override provider ([#38717](https://github.com/angular/angular/issues/38717)) ([c8f056b](https://github.com/angular/angular/commit/c8f056b))
|
||||
* **core:** use single quotes for relative link resolution migration to align with style guide ([#39070](https://github.com/angular/angular/issues/39070)) ([8894706](https://github.com/angular/angular/commit/8894706))
|
||||
* **core:** migrate relative link resolution with single quotes ([#39102](https://github.com/angular/angular/issues/39102)) ([049b453](https://github.com/angular/angular/commit/049b453))
|
||||
* **core:** use Trusted Types policy in inert DOM builder ([#39208](https://github.com/angular/angular/issues/39208)) ([7d49299](https://github.com/angular/angular/commit/7d49299))
|
||||
* **core:** use Trusted Types policy in named_array_type ([#39209](https://github.com/angular/angular/issues/39209)) ([f6d5cdf](https://github.com/angular/angular/commit/f6d5cdf))
|
||||
* **router:** Allow undefined inputs on routerLink ([#39151](https://github.com/angular/angular/issues/39151)) ([b0b4953](https://github.com/angular/angular/commit/b0b4953))
|
||||
* **router:** create schematic for preserveQueryParams ([#38762](https://github.com/angular/angular/issues/38762)) ([93ee05d](https://github.com/angular/angular/commit/93ee05d))
|
||||
* **router:** remove preserveQueryParams symbol ([#38762](https://github.com/angular/angular/issues/38762)) ([783a5bd](https://github.com/angular/angular/commit/783a5bd))
|
||||
* **core:** use Trusted Types policy in `createNamedArrayType()` ([#39209](https://github.com/angular/angular/issues/39209)) ([f6d5cdf](https://github.com/angular/angular/commit/f6d5cdf))
|
||||
* **core:** guard reading of global `ngDevMode` for undefined. ([#36055](https://github.com/angular/angular/issues/36055)) ([f541e5f](https://github.com/angular/angular/commit/f541e5f))
|
||||
* **core:** do not error when `ngDevMode` is undeclared ([#39415](https://github.com/angular/angular/issues/39415)) ([cc32932](https://github.com/angular/angular/commit/cc32932))
|
||||
* **core:** store ICU state in `LView` rather than in `TView` ([#39233](https://github.com/angular/angular/issues/39233)) ([0992b67](https://github.com/angular/angular/commit/0992b67)), closes [#37021](https://github.com/angular/angular/issues/37021) [#38144](https://github.com/angular/angular/issues/38144) [#38073](https://github.com/angular/angular/issues/38073)
|
||||
* **core:** `markDirty()` should only mark flags when really scheduling tick. ([#39316](https://github.com/angular/angular/issues/39316)) ([3b6497b](https://github.com/angular/angular/commit/3b6497b)), closes [#39296](https://github.com/angular/angular/issues/39296)
|
||||
* **core:** access injected parent values using `SkipSelf` ([#39464](https://github.com/angular/angular/issues/39464)) ([7cb9e19](https://github.com/angular/angular/commit/7cb9e19))
|
||||
* **elements:** update the view of an `OnPush` component when inputs change ([#39452](https://github.com/angular/angular/issues/39452)) ([dd28855](https://github.com/angular/angular/commit/dd28855)), closes [#38948](https://github.com/angular/angular/issues/38948)
|
||||
* **forms:** ensure to emit `statusChanges` on subsequent value update/validations ([#38354](https://github.com/angular/angular/issues/38354)) ([d9fea85](https://github.com/angular/angular/commit/d9fea85)), closes [#20424](https://github.com/angular/angular/issues/20424) [#14542](https://github.com/angular/angular/issues/14542)
|
||||
* **forms:** type `NG_VALUE_ACCESSOR` injection token as array ([#29723](https://github.com/angular/angular/issues/29723)) ([2b1b718](https://github.com/angular/angular/commit/2b1b718)), closes [#29351](https://github.com/angular/angular/issues/29351)
|
||||
* **forms:** improve types of directive constructor arguments ([#38944](https://github.com/angular/angular/issues/38944)) ([246de9a](https://github.com/angular/angular/commit/246de9a))
|
||||
* **forms:** include `null` in `.parent` of abstract control ([#32671](https://github.com/angular/angular/issues/32671)) ([f4f1bcc](https://github.com/angular/angular/commit/f4f1bcc)), closes [#16999](https://github.com/angular/angular/issues/16999)
|
||||
* **forms:** remove validators while cleaning up a control ([#39234](https://github.com/angular/angular/issues/39234)) ([43b4940](https://github.com/angular/angular/commit/43b4940))
|
||||
* **language-service:** hybrid visitor returns parent node of `BoundAttribute` ([#38995](https://github.com/angular/angular/issues/38995)) ([323be39](https://github.com/angular/angular/commit/323be39))
|
||||
* **language-service:** [Ivy] hybrid visitor should not locate let keyword ([#39061](https://github.com/angular/angular/issues/39061)) ([70e13dc](https://github.com/angular/angular/commit/70e13dc))
|
||||
* **language-service:** [Ivy] create compiler only when program changes ([#39231](https://github.com/angular/angular/issues/39231)) ([8f1317f](https://github.com/angular/angular/commit/8f1317f))
|
||||
* **localize:** render placeholder types in extracted XLIFF files ([#39398](https://github.com/angular/angular/issues/39398)) ([32163ef](https://github.com/angular/angular/commit/32163ef)), closes [#38791](https://github.com/angular/angular/issues/38791)
|
||||
* **localize:** serialize all the message locations to XLIFF ([#39411](https://github.com/angular/angular/issues/39411)) ([f5710c6](https://github.com/angular/angular/commit/f5710c6)), closes [#39330](https://github.com/angular/angular/issues/39330)
|
||||
* **ngcc:** ensure that "inline exports" can be interpreted correctly ([#39267](https://github.com/angular/angular/issues/39267)) ([822b838](https://github.com/angular/angular/commit/822b838))
|
||||
* **ngcc:** capture UMD/CommonJS inner class implementation node correctly ([#39346](https://github.com/angular/angular/issues/39346)) ([fc2e3cc](https://github.com/angular/angular/commit/fc2e3cc))
|
||||
* **platform-server:** resolve absolute URL from `baseUrl` ([#39334](https://github.com/angular/angular/issues/39334)) ([b4e8399](https://github.com/angular/angular/commit/b4e8399))
|
||||
* **platform-webworker:** remove @angular/platform-webworker and @angular/platform-webworker-dynamic ([#38846](https://github.com/angular/angular/issues/38846)) ([93c3d8f](https://github.com/angular/angular/commit/93c3d8f))
|
||||
* **router:** fix arguments order for call to `shouldReuseRoute` ([#26949](https://github.com/angular/angular/issues/26949)) ([3817e5f](https://github.com/angular/angular/commit/3817e5f)), closes [#16192](https://github.com/angular/angular/issues/16192)
|
||||
* **router:** support lazy loading for empty path named outlets ([#38379](https://github.com/angular/angular/issues/38379)) ([926ffcd](https://github.com/angular/angular/commit/926ffcd)), closes [#12842](https://github.com/angular/angular/issues/12842)
|
||||
* **router:** set `relativeLinkResolution` to `corrected` by default ([#25609](https://github.com/angular/angular/issues/25609)) ([837889f](https://github.com/angular/angular/commit/837889f))
|
||||
* **router:** properly assign `ExtraOptions` to `Router` in `RouterTestingModule` ([#39096](https://github.com/angular/angular/issues/39096)) ([d8c0534](https://github.com/angular/angular/commit/d8c0534)), closes [#23347](https://github.com/angular/angular/issues/23347)
|
||||
* **router:** allow undefined inputs on `routerLink` ([#39151](https://github.com/angular/angular/issues/39151)) ([b0b4953](https://github.com/angular/angular/commit/b0b4953))
|
||||
* **router:** create schematic for `preserveQueryParams` ([#38762](https://github.com/angular/angular/issues/38762)) ([93ee05d](https://github.com/angular/angular/commit/93ee05d))
|
||||
* **router:** remove `preserveQueryParams` symbol ([#38762](https://github.com/angular/angular/issues/38762)) ([783a5bd](https://github.com/angular/angular/commit/783a5bd))
|
||||
* **router:** fix incorrect type signature for `createUrlTree` ([#39347](https://github.com/angular/angular/issues/39347)) ([161b278](https://github.com/angular/angular/commit/161b278))
|
||||
* **router:** ensure all outlets are used when commands have a prefix ([#39456](https://github.com/angular/angular/issues/39456)) ([b2f3952](https://github.com/angular/angular/commit/b2f3952))
|
||||
* **service-worker:** fix condition to check for a cache-busted request ([#36847](https://github.com/angular/angular/issues/36847)) ([5be4edf](https://github.com/angular/angular/commit/5be4edf))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** add automated migration to replace async with waitForAsync ([#39212](https://github.com/angular/angular/issues/39212)) ([5ce71e0](https://github.com/angular/angular/commit/5ce71e0))
|
||||
* **core:** add automated migration to replace ViewEncapsulation.Native ([#38882](https://github.com/angular/angular/issues/38882)) ([0e733f3](https://github.com/angular/angular/commit/0e733f3))
|
||||
* **core:** add initialNavigation schematic ([#36926](https://github.com/angular/angular/issues/36926)) ([0ec7043](https://github.com/angular/angular/commit/0ec7043))
|
||||
* **core:** add Trusted Types workaround for Function constructor ([#39209](https://github.com/angular/angular/issues/39209)) ([5913e5c](https://github.com/angular/angular/commit/5913e5c))
|
||||
* **common:** add ISO week-numbering year formats support to `formatDate` ([#38828](https://github.com/angular/angular/issues/38828)) ([984ed39](https://github.com/angular/angular/commit/984ed39))
|
||||
* **common:** stricter types for `DatePipe` ([#37447](https://github.com/angular/angular/issues/37447)) ([daf8b7f](https://github.com/angular/angular/commit/daf8b7f))
|
||||
* **common:** stricter types for number pipes ([#37447](https://github.com/angular/angular/issues/37447)) ([7b2aac9](https://github.com/angular/angular/commit/7b2aac9))
|
||||
* **compiler:** parse and recover on incomplete opening HTML tags ([#38681](https://github.com/angular/angular/issues/38681)) ([6ae3b68](https://github.com/angular/angular/commit/6ae3b68)), closes [#38596](https://github.com/angular/angular/issues/38596)
|
||||
* **compiler:** add `keySpan` to `Variable` node ([#38965](https://github.com/angular/angular/issues/38965)) ([239968d](https://github.com/angular/angular/commit/239968d))
|
||||
* **compiler-cli:** `TemplateTypeChecker` operation to get `Symbol` from a template node ([#38618](https://github.com/angular/angular/issues/38618)) ([c4556db](https://github.com/angular/angular/commit/c4556db))
|
||||
* **compiler-cli:** add ability to get `Symbol` of `Template`s and `Element`s in component template ([#38618](https://github.com/angular/angular/issues/38618)) ([cf2e8b9](https://github.com/angular/angular/commit/cf2e8b9))
|
||||
* **compiler-cli:** add ability to get `Symbol` of AST expression in component template ([#38618](https://github.com/angular/angular/issues/38618)) ([f56ece4](https://github.com/angular/angular/commit/f56ece4))
|
||||
* **compiler-cli:** add ability to get symbol of reference or variable ([#38618](https://github.com/angular/angular/issues/38618)) ([19598b4](https://github.com/angular/angular/commit/19598b4))
|
||||
* **compiler-cli:** define interfaces to be used for `TemplateTypeChecker` ([#38618](https://github.com/angular/angular/issues/38618)) ([9e77bd3](https://github.com/angular/angular/commit/9e77bd3))
|
||||
* **compiler-cli:** support getting resource dependencies for a source file ([#38048](https://github.com/angular/angular/issues/38048)) ([5dbf357](https://github.com/angular/angular/commit/5dbf357))
|
||||
* **core:** add automated migration to replace `async` with `waitForAsync` ([#39212](https://github.com/angular/angular/issues/39212)) ([5ce71e0](https://github.com/angular/angular/commit/5ce71e0))
|
||||
* **core:** add automated migration to replace `ViewEncapsulation.Native` ([#38882](https://github.com/angular/angular/issues/38882)) ([0e733f3](https://github.com/angular/angular/commit/0e733f3))
|
||||
* **core:** add `initialNavigation` schematic ([#36926](https://github.com/angular/angular/issues/36926)) ([0ec7043](https://github.com/angular/angular/commit/0ec7043))
|
||||
* **core:** add Trusted Types workaround for `Function` constructor ([#39209](https://github.com/angular/angular/issues/39209)) ([5913e5c](https://github.com/angular/angular/commit/5913e5c))
|
||||
* **core:** create internal Trusted Types module ([#39207](https://github.com/angular/angular/issues/39207)) ([0875fd2](https://github.com/angular/angular/commit/0875fd2))
|
||||
* **core:** depend on type definitions for Trusted Types ([#39207](https://github.com/angular/angular/issues/39207)) ([c4266fb](https://github.com/angular/angular/commit/c4266fb))
|
||||
* **core:** remove ViewEncapsulation.Native ([#38882](https://github.com/angular/angular/issues/38882)) ([4a1c12c](https://github.com/angular/angular/commit/4a1c12c))
|
||||
* **language-service:** [Ivy] getSemanticDiagnostics for external templates ([#39065](https://github.com/angular/angular/issues/39065)) ([63624a2](https://github.com/angular/angular/commit/63624a2))
|
||||
* **language-service:** Add getTypeDefinitionAtPosition (go to type definition) ([#39145](https://github.com/angular/angular/issues/39145)) ([a84976f](https://github.com/angular/angular/commit/a84976f))
|
||||
* **language-service:** Add module name to directive quick info ([#39121](https://github.com/angular/angular/issues/39121)) ([4604fe9](https://github.com/angular/angular/commit/4604fe9))
|
||||
* **router:** add new initialNavigation options to replace legacy ([#37480](https://github.com/angular/angular/issues/37480)) ([c4becca](https://github.com/angular/angular/commit/c4becca))
|
||||
* **core:** remove `ViewEncapsulation.Native` ([#38882](https://github.com/angular/angular/issues/38882)) ([4a1c12c](https://github.com/angular/angular/commit/4a1c12c))
|
||||
* **forms:** add migration for `AbstractControl.parent` accesses ([#39009](https://github.com/angular/angular/issues/39009)) ([aeec223](https://github.com/angular/angular/commit/aeec223)), closes [#32671](https://github.com/angular/angular/issues/32671)
|
||||
* **language-service:** add `getDefinitionAndBoundSpan` (go to definition) ([#39101](https://github.com/angular/angular/issues/39101)) ([3975dd9](https://github.com/angular/angular/commit/3975dd9))
|
||||
* **language-service:** add quick info for inline templates in ivy ([#39060](https://github.com/angular/angular/issues/39060)) ([904adb7](https://github.com/angular/angular/commit/904adb7))
|
||||
* **language-service:** [Ivy] `getSemanticDiagnostics` for external templates ([#39065](https://github.com/angular/angular/issues/39065)) ([63624a2](https://github.com/angular/angular/commit/63624a2))
|
||||
* **language-service:** add `getTypeDefinitionAtPosition` (go to type definition) ([#39145](https://github.com/angular/angular/issues/39145)) ([a84976f](https://github.com/angular/angular/commit/a84976f))
|
||||
* **language-service:** add module name to directive quick info ([#39121](https://github.com/angular/angular/issues/39121)) ([4604fe9](https://github.com/angular/angular/commit/4604fe9))
|
||||
* **router:** add migration to update calls to `navigateByUrl` and `createUrlTree` with invalid parameters ([#38825](https://github.com/angular/angular/issues/38825)) ([7849fdd](https://github.com/angular/angular/commit/7849fdd))
|
||||
* **router:** add `relativeLinkResolution` migration to update default value ([#38698](https://github.com/angular/angular/issues/38698)) ([15ea811](https://github.com/angular/angular/commit/15ea811))
|
||||
* **router:** add new `initialNavigation` options to replace legacy ([#37480](https://github.com/angular/angular/issues/37480)) ([c4becca](https://github.com/angular/angular/commit/c4becca))
|
||||
* **service-worker:** add `UnrecoverableStateError` ([#36847](https://github.com/angular/angular/issues/36847)) ([036a2fa](https://github.com/angular/angular/commit/036a2fa)), closes [#36539](https://github.com/angular/angular/issues/36539)
|
||||
* **service-worker:** add the option to prefer network for navigation requests ([#38565](https://github.com/angular/angular/issues/38565)) ([a206852](https://github.com/angular/angular/commit/a206852)), closes [#38194](https://github.com/angular/angular/issues/38194)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler-cli:** only emit directive/pipe references that are used ([#38539](https://github.com/angular/angular/issues/38539)) ([077f516](https://github.com/angular/angular/commit/077f516))
|
||||
* **compiler-cli:** optimize computation of type-check scope information ([#38539](https://github.com/angular/angular/issues/38539)) ([297c060](https://github.com/angular/angular/commit/297c060))
|
||||
* **compiler-cli:** only generate template context declaration when used ([#39321](https://github.com/angular/angular/issues/39321)) ([1ac0500](https://github.com/angular/angular/commit/1ac0500))
|
||||
* **core:** do not recurse into modules that have already been registered ([#39514](https://github.com/angular/angular/issues/39514)) ([5c13c67](https://github.com/angular/angular/commit/5c13c67)), closes [#39487](https://github.com/angular/angular/issues/39487)
|
||||
* **router:** use `ngDevMode` to tree-shake error messages in router ([#38674](https://github.com/angular/angular/issues/38674)) ([db21c4f](https://github.com/angular/angular/commit/db21c4f))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **router:** * The `initialNavigation` property for the options in
|
||||
`RouterModule.forRoot` no longer supports `legacy_disabled`,
|
||||
`legacy_enabled`, `true`, or `false` as valid values.
|
||||
`legacy_enabled` (the old default) is instead `enabledNonBlocking`
|
||||
* `enabled` is deprecated as a valid value for the
|
||||
`RouterModule.forRoot` `initialNavigation` option. `enabledBlocking`
|
||||
has been introduced to replace it
|
||||
* **router:** preserveQueryParams has been removed, use
|
||||
queryParamsHandling="preserve" instead
|
||||
* **router:** If you were accessing the `RouterLink` values of `queryParams`,
|
||||
`fragment` or `queryParamsHandling` you might need to relax the typing to also
|
||||
accept `undefined` and `null`. ([#39151](https://github.com/angular/angular/issues/39151))
|
||||
* **core:** * `ViewEncapsulation.Native` has been removed. Use `ViewEncapsulation.ShadowDom` instead. Existing
|
||||
usages will be updated automatically by `ng update`.
|
||||
* **compiler-cli:** Expressions within ICUs are now type-checked again, fixing a regression
|
||||
in Ivy. This may cause compilation failures if errors are found in
|
||||
expressions that appear within an ICU. Please correct these expressions
|
||||
to resolve the type-check errors.
|
||||
* **common:**
|
||||
- **[6acea54](https://github.com/angular/angular/commit/6acea54f62fe7e62e9b36458b30d98501277236f):**
|
||||
The locale data API has been marked as returning readonly arrays, rather than mutable arrays, since these arrays are shared across calls to the API.
|
||||
If you were mutating them (e.g. calling `sort()`, `push()`, `splice()`, etc.) then your code will no longer compile.
|
||||
If you need to mutate the array, you should now make a copy (e.g. by calling `slice()`) and mutate the copy.
|
||||
- **[26f2820](https://github.com/angular/angular/commit/26f28200bf3a909024184c61111f4cd20ac41edd):**
|
||||
When passing a date-time formatted string to the `DatePipe` in a format that contains fractions of a millisecond, the milliseconds will now always be rounded down rather than to the nearest millisecond.
|
||||
Most applications will not be affected by this change.
|
||||
If this is not the desired behaviour then consider pre-processing the string to round the millisecond part before passing it to the `DatePipe`.
|
||||
- **[4744c22](https://github.com/angular/angular/commit/4744c229dbd372746a38ebbcdae2245f945ebcc4):**
|
||||
The `slice` pipe now returns `null` for the `undefined` input value, which is consistent with the behavior of most pipes.
|
||||
If you rely on `undefined` being the result in that case, you now need to check for it explicitly.
|
||||
- **[4dfe0fa](https://github.com/angular/angular/commit/4dfe0fa068007c28ca3b4a1476ed4ba1d2d86064):**
|
||||
The typing of the `keyvalue` pipe has been fixed to report that for input objects that have `number` keys, the result will contain the string representation of the keys.
|
||||
This was already the case and the types have simply been updated to reflect this.
|
||||
Please update the consumers of the pipe output if they were relying on the incorrect types.
|
||||
Note that this does not affect use cases where the input values are `Map`s, so if you need to preserve `number`s, this is an effective way.
|
||||
- **[7b2aac9](https://github.com/angular/angular/commit/7b2aac97df63717fd2dfbb81903d12221ae29ba1):**
|
||||
The signatures of the number pipes now explicitly state which types are accepted.
|
||||
This should only cause issues in corner cases, as any other values would result in runtime exceptions.
|
||||
- **[daf8b7f](https://github.com/angular/angular/commit/daf8b7f10038c7ecf7ea78bc6fe39766c85224a6):**
|
||||
The signature of the `date` pipe now explicitly states which types are accepted.
|
||||
This should only cause issues in corner cases, as any other values would result in runtime exceptions.
|
||||
- **[5f815c0](https://github.com/angular/angular/commit/5f815c05650c15e414fe72cddaa2b690a7962273):**
|
||||
The `async` pipe no longer claims to return `undefined` for an input that was typed as `undefined`.
|
||||
Note that the code actually returned `null` on `undefined` inputs.
|
||||
In the unlikely case you were relying on this, please fix the typing of the consumers of the pipe output.
|
||||
- **[c7d5555](https://github.com/angular/angular/commit/c7d5555dfb14f5deca4a2d947fe82a68453e5f09):**
|
||||
The case conversion pipes no longer let falsy values through.
|
||||
They now map both `null` and `undefined` to `null` and raise an exception on invalid input (`0`, `false`, `NaN`) just like most "common pipes".
|
||||
If your code required falsy values to pass through, you need to handle them explicitly.
|
||||
* **compiler:**
|
||||
- **[736e064](https://github.com/angular/angular/commit/736e0644b02bc4606a7ae0c974d1b06e993708f6):**
|
||||
TypeScript 3.9 is no longer supported, please upgrade to TypeScript 4.0.
|
||||
* **compiler-cli:**
|
||||
- **[0a16e60](https://github.com/angular/angular/commit/0a16e60afae27c7af023cf617de05297dddf5135):**
|
||||
Expressions within ICUs are now type-checked again, fixing a regression in Ivy.
|
||||
This may cause compilation failures if errors are found in expressions that appear within an ICU.
|
||||
Please correct these expressions to resolve the type-check errors.
|
||||
* **core:**
|
||||
- **[fdea180](https://github.com/angular/angular/commit/fdea1804d6d46d33c71640972ff45a8d5cc82f8b):**
|
||||
`CollectionChangeRecord` has been removed, use `IterableChangeRecord` instead.
|
||||
- **[c8f056b](https://github.com/angular/angular/commit/c8f056beb696d176530ee4e86f64964c3b834c9a):**
|
||||
If you call `TestBed.overrideProvider` after TestBed initialization, provider overrides are not applied.
|
||||
This behavior is consistent with other override methods (such as `TestBed.overrideDirective`, etc) but they throw an error to indicate that, when the check was missing in the `TestBed.overrideProvider` function.
|
||||
Now calling `TestBed.overrideProvider` after `TestBed` initialization also triggers an error, thus there is a chance that some tests (where `TestBed.overrideProvider` is called after `TestBed` initialization) will start to fail and require updates to move `TestBed.overrideProvider` calls before `TestBed` initialization is completed.
|
||||
- **[4ca1c73](https://github.com/angular/angular/commit/4ca1c736bb020de166b6a1fbfb9fe264602ccd5c):**
|
||||
In v10, IE 9, 10, and IE mobile support was deprecated.
|
||||
In v11, Angular framework removes IE 9, 10, and IE mobile support completely.
|
||||
Supporting outdated browsers like these increases bundle size, code complexity, and test load, and also requires time and effort that could be spent on improvements to the framework.
|
||||
For example, fixing issues can be more difficult, as a straightforward fix for modern browsers could break old ones that have quirks due to not receiving updates from vendors.
|
||||
- **[4a1c12c](https://github.com/angular/angular/commit/4a1c12c773f14dbfc5db61087f415d56f60127cf):**
|
||||
`ViewEncapsulation.Native` has been removed. Use `ViewEncapsulation.ShadowDom` instead.
|
||||
Existing usages will be updated automatically by `ng update`.
|
||||
* **forms:**
|
||||
- **[d9fea85](https://github.com/angular/angular/commit/d9fea857db0638149f200ca86c7852c01c9787f4):**
|
||||
Previously if `FormControl`, `FormGroup` and `FormArray` class instances had async validators defined at initialization time, the status change event was not emitted once async validators completed.
|
||||
After this change the status event is emitted into the `statusChanges` observable.
|
||||
If your code relies on the old behavior, you can filter/ignore this additional status change event.
|
||||
- **[246de9a](https://github.com/angular/angular/commit/246de9aaad2a85c39f4e624ba25e444d716fe486):**
|
||||
Directives in the `@angular/forms` package used to have `any[]` as a type of `validators` and `asyncValidators` arguments in constructors.
|
||||
Now these arguments are properly typed, so if your code relies on directive constructor types it may require some updates to improve type safety.
|
||||
- **[f4f1bcc](https://github.com/angular/angular/commit/f4f1bcc99711be277205dcd8ef6fc5a1873cb227):**
|
||||
Type of `AbstractFormControl.parent` now includes `null`.
|
||||
`null` is now included in the types of `.parent`.
|
||||
If you don't already have a check for this case, the TypeScript compiler might complain.
|
||||
A v11 migration exists which adds the non-null assertion operator where necessary.
|
||||
In an unlikely case your code was testing the parent against `undefined` with strict equality, you'll need to change this to `=== null` instead, since the parent is now explicitly initialized with `null` instead of being left `undefined`.
|
||||
* **platform-server:**
|
||||
- **[b4e8399](https://github.com/angular/angular/commit/b4e8399144ae4b6d4f0da9ff2a8e1fa73643f766):**
|
||||
If you use `useAbsoluteUrl` to setup `platform-server`, you now need to also specify `baseUrl`.
|
||||
We are intentionally making this a breaking change in a minor release, because if `useAbsoluteUrl` is set to `true` then the behavior of the application could be unpredictable, resulting in issues that are hard to discover but could be affecting production environments.
|
||||
* **platform-webworker:**
|
||||
- **[93c3d8f](https://github.com/angular/angular/commit/93c3d8f9fd79014bf700a1447503f5ee6f170bf9):**
|
||||
@angular/platform-webworker and @angular/platform-webworker-dynamic have been removed as they were deprecated in v8.
|
||||
* **router:**
|
||||
- **[3817e5f](https://github.com/angular/angular/commit/3817e5f1dfeb9de26fa2ea4068a37565f435214f):**
|
||||
This change corrects the argument order when calling `RouteReuseStrategy#shouldReuseRoute`.
|
||||
Previously, when evaluating child routes, they would be called with the future and current arguments would be swapped.
|
||||
If your `RouteReuseStrategy` relies specifically on only the future or current snapshot state, you may need to update the `shouldReuseRoute` implementation's use of "future" and "current" `ActivatedRouteSnapshot`s.
|
||||
- **[e4f4d18](https://github.com/angular/angular/commit/e4f4d18e7e73a7c755d3b79cf599e0e04270000d):**
|
||||
While the new parameter types allow a variable of type `NavigationExtras` to be passed in, they will not allow object literals, as they may only specify known properties.
|
||||
They will also not accept types that do not have properties in common with the ones in the `Pick`.
|
||||
To fix this error, only specify properties from the `NavigationExtras` which are actually used in the respective function calls or use a type assertion on the object or variable: `as NavigationExtras`.
|
||||
- **[837889f](https://github.com/angular/angular/commit/837889f0a47521d3de5c445ac3ffeedfe47fc109):**
|
||||
This commit changes the default value of `relativeLinkResolution` from `'legacy'` to `'default'`.
|
||||
If your application previously used the default by not specifying a value in the `ExtraOptions` and uses relative links when navigating from children of empty path routes, you will need to update your `RouterModule` to specifically specify `'legacy'` for `relativeLinkResolution`.
|
||||
See https://angular.io/api/router/ExtraOptions#relativeLinkResolution for more details.
|
||||
- **[c4becca](https://github.com/angular/angular/commit/c4becca0e4238640461c43567a6d3e16b8c5d3f3):**
|
||||
The `initialNavigation` property for the options in `RouterModule.forRoot` no longer supports `legacy_disabled`, `legacy_enabled`, `true`, or `false` as valid values.
|
||||
`legacy_enabled` (the old default) is instead `enabledNonBlocking`.
|
||||
- **[c4becca](https://github.com/angular/angular/commit/c4becca0e4238640461c43567a6d3e16b8c5d3f3):**
|
||||
`enabled` is deprecated as a valid value for the `RouterModule.forRoot` `initialNavigation` option.
|
||||
`enabledBlocking` has been introduced to replace it.
|
||||
- **[783a5bd](https://github.com/angular/angular/commit/783a5bd7bbae35562e21aaa27771c54091fb9812):**
|
||||
`preserveQueryParams` has been removed, use `queryParamsHandling: "preserve"` instead.
|
||||
- **[b0b4953](https://github.com/angular/angular/commit/b0b4953fd654aeda2f358b0af1ab32d20b368930):**
|
||||
If you were accessing the `RouterLink` values of `queryParams`, `fragment` or `queryParamsHandling` you might need to relax the typing to also accept `undefined` and `null`.
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **compiler:** remove support for TypeScript 3.9 ([#39313](https://github.com/angular/angular/issues/39313)) ([736e064](https://github.com/angular/angular/commit/736e0644b02bc4606a7ae0c974d1b06e993708f6))
|
||||
* **router:** adjust type of parameter in `navigateByUrl` and `createUrlTree` to be more accurate ([#38227](https://github.com/angular/angular/issues/38227)) ([e4f4d18](https://github.com/angular/angular/commit/e4f4d18)), closes [#18798](https://github.com/angular/angular/issues/18798)
|
||||
|
||||
|
||||
|
||||
<a name="10.1.6"></a>
|
||||
@@ -75,44 +304,6 @@ to resolve the type-check errors.
|
||||
* **ngcc:** do not rescan program source files when referenced from multiple root files ([#39254](https://github.com/angular/angular/issues/39254)) ([5221df8](https://github.com/angular/angular/commit/5221df8)), closes [#39240](https://github.com/angular/angular/issues/39240)
|
||||
|
||||
|
||||
<a name="11.0.0-next.5"></a>
|
||||
# 11.0.0-next.5 (2020-10-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** add boolean to valid json for testing ([#37893](https://github.com/angular/angular/issues/37893)) ([3c474ec](https://github.com/angular/angular/commit/3c474ec)), closes [#20690](https://github.com/angular/angular/issues/20690)
|
||||
* **core:** use single quotes for relative link resolution migration to align with style guide ([#39070](https://github.com/angular/angular/issues/39070)) ([8894706](https://github.com/angular/angular/commit/8894706))
|
||||
* **forms:** improve types of directive constructor arguments ([#38944](https://github.com/angular/angular/issues/38944)) ([246de9a](https://github.com/angular/angular/commit/246de9a))
|
||||
* **forms:** include null in .parent of abstract control ([#32671](https://github.com/angular/angular/issues/32671)) ([f4f1bcc](https://github.com/angular/angular/commit/f4f1bcc)), closes [#16999](https://github.com/angular/angular/issues/16999)
|
||||
* **language-service:** [Ivy] hybrid visitor should not locate let keyword ([#39061](https://github.com/angular/angular/issues/39061)) ([70e13dc](https://github.com/angular/angular/commit/70e13dc))
|
||||
* **router:** properly assign ExtraOptions to Router in RouterTestingModule ([#39096](https://github.com/angular/angular/issues/39096)) ([d8c0534](https://github.com/angular/angular/commit/d8c0534)), closes [#23347](https://github.com/angular/angular/issues/23347)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-cli:** support getting resource dependencies for a source file ([#38048](https://github.com/angular/angular/issues/38048)) ([5dbf357](https://github.com/angular/angular/commit/5dbf357))
|
||||
* **forms:** add migration for AbstractControl.parent accesses ([#39009](https://github.com/angular/angular/issues/39009)) ([aeec223](https://github.com/angular/angular/commit/aeec223)), closes [#32671](https://github.com/angular/angular/issues/32671)
|
||||
* **language-service:** Add getDefinitionAndBoundSpan (go to definition) ([#39101](https://github.com/angular/angular/issues/39101)) ([3975dd9](https://github.com/angular/angular/commit/3975dd9))
|
||||
* **language-service:** add quick info for inline templates in ivy ([#39060](https://github.com/angular/angular/issues/39060)) ([904adb7](https://github.com/angular/angular/commit/904adb7))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **forms:** Directives in the `@angular/forms` package used to have `any[]` as a type of `validators` and
|
||||
`asyncValidators` arguments in constructors. Now these arguments are properly typed, so if your
|
||||
code relies on directive constructor types it may require some updates to improve type safety.
|
||||
* **forms:** Type of AbstractFormControl.parent now includes null
|
||||
|
||||
`null` is now included in the types of .parent. If you don't already have a check for this case,
|
||||
the TypeScript compiler might compain. A v11 migration exists which adds the not-null assertion
|
||||
operator where necessary.
|
||||
|
||||
In an unlikely case your code was testing the parnet against undefined with sitrct equality,
|
||||
you'll need to change this to `=== null` instead, since the parent is not explicily initialized
|
||||
with `null` instead of being left `undefined`.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.5"></a>
|
||||
## 10.1.5 (2020-10-07)
|
||||
@@ -125,92 +316,6 @@ with `null` instead of being left `undefined`.
|
||||
|
||||
|
||||
|
||||
<a name="11.0.0-next.4"></a>
|
||||
# 11.0.0-next.4 (2020-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** correct and simplify typing of `KeyValuePipe` ([#37447](https://github.com/angular/angular/issues/37447)) ([4dfe0fa](https://github.com/angular/angular/commit/4dfe0fa))
|
||||
* **common:** correct and simplify typing of AsyncPipe ([#37447](https://github.com/angular/angular/issues/37447)) ([5f815c0](https://github.com/angular/angular/commit/5f815c0))
|
||||
* **common:** correct and simplify typing of I18nPluralPipe ([#37447](https://github.com/angular/angular/issues/37447)) ([3b919ef](https://github.com/angular/angular/commit/3b919ef))
|
||||
* **common:** correct typing and implementation of `SlicePipe` ([#37447](https://github.com/angular/angular/issues/37447)) ([4744c22](https://github.com/angular/angular/commit/4744c22))
|
||||
* **common:** let case conversion pipes accept type unions with `null` ([#36259](https://github.com/angular/angular/issues/36259)) ([#37447](https://github.com/angular/angular/issues/37447)) ([c7d5555](https://github.com/angular/angular/commit/c7d5555))
|
||||
* **compiler-cli:** perform DOM schema checks even in basic mode in g3 ([#38943](https://github.com/angular/angular/issues/38943)) ([40975e0](https://github.com/angular/angular/commit/40975e0))
|
||||
* **language-service:** hybrid visitor returns parent node of BoundAttribute ([#38995](https://github.com/angular/angular/issues/38995)) ([323be39](https://github.com/angular/angular/commit/323be39))
|
||||
* **packaging:** remove polyfills needed to run tests on IE9 and IE 10 ([#38931](https://github.com/angular/angular/issues/38931)) ([4ca1c73](https://github.com/angular/angular/commit/4ca1c73))
|
||||
* **platform-webworker:** remove platform-webworker and platform-webworker-dynamic ([#38846](https://github.com/angular/angular/issues/38846)) ([93c3d8f](https://github.com/angular/angular/commit/93c3d8f))
|
||||
* **router:** make relativeLinkResolution corrected by default ([#25609](https://github.com/angular/angular/issues/25609)) ([837889f](https://github.com/angular/angular/commit/837889f)), closes [#22394](https://github.com/angular/angular/issues/22394)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **router:** Adjust type of parameter in navigateByUrl and createUrlTree to be more accurate ([#38227](https://github.com/angular/angular/issues/38227)) ([e4f4d18](https://github.com/angular/angular/commit/e4f4d18)), closes [#18798](https://github.com/angular/angular/issues/18798)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** stricter types for DatePipe ([#37447](https://github.com/angular/angular/issues/37447)) ([daf8b7f](https://github.com/angular/angular/commit/daf8b7f))
|
||||
* **common:** stricter types for number pipes ([#37447](https://github.com/angular/angular/issues/37447)) ([7b2aac9](https://github.com/angular/angular/commit/7b2aac9))
|
||||
* **compiler:** Add keySpan to Variable Node ([#38965](https://github.com/angular/angular/issues/38965)) ([239968d](https://github.com/angular/angular/commit/239968d))
|
||||
* **router:** Add `relativeLinkResolution` migration to update default value ([#38698](https://github.com/angular/angular/issues/38698)) ([15ea811](https://github.com/angular/angular/commit/15ea811))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **packaging:** In v10, IE 9, 10, and IE mobile support was deprecated. In v11, Angular framework removes IE 9,
|
||||
10, and IE mobile support completely.
|
||||
Supporting outdated browsers like these increases bundle size, code complexity, and test load,
|
||||
and also requires time and effort that could be spent on improvements to the framework.
|
||||
For example, fixing issues can be more difficult, as a straightforward fix for modern browsers
|
||||
could break old ones that have quirks due to not receiving updates from vendors.
|
||||
* **platform-webworker:** @angular/platform-webworker and @angular/platform-webworker-dynamic
|
||||
have been removed as they were deprecated in v8
|
||||
* **common:** The `slice` pipe now returns `null` for the `undefined` input value,
|
||||
which is consistent with the behavior of most pipes. If you rely on
|
||||
`undefined` being the result in that case, you now need to check for it
|
||||
explicitly.
|
||||
* **common:** The typing of the `keyvalue` pipe has been fixed to report that for
|
||||
input objects that have `number` keys, the result will contain the
|
||||
string representation of the keys. This was already the case and the
|
||||
code has simply been updated to reflect this. Please update the
|
||||
consumers of the pipe output if they were relying on the incorrect
|
||||
types. Note that this does not affect use cases where the input values
|
||||
are `Map`s, so if you need to preserve `number`s, this is an effective
|
||||
way.
|
||||
* **common:** The signatures of the number pipes now explicitly state which types are
|
||||
accepted. This should only cause issues in corner cases, as any other
|
||||
values would result in runtime exceptions.
|
||||
* **common:** The signature of the `date` pipe now explicitly states which types are
|
||||
accepted. This should only cause issues in corner cases, as any other
|
||||
values would result in runtime exceptions.
|
||||
* **common:** The async pipe no longer claims to return `undefined` for an input that
|
||||
was typed as `undefined`. Note that the code actually returned `null` on
|
||||
`undefined` inputs. In the unlikely case you were relying on this,
|
||||
please fix the typing of the consumers of the pipe output.
|
||||
* **common:** The case conversion pipes no longer let falsy values through. They now
|
||||
map both `null` and `undefined` to `null` and raise an exception on
|
||||
invalid input (`0`, `false`, `NaN`) just like most "common pipes". If
|
||||
your code required falsy values to pass through, you need to handle them
|
||||
explicitly.
|
||||
* **router:** While the new parameter types allow a variable of type
|
||||
`NavigationExtras` to be passed in, they will not allow object literals,
|
||||
as they may only specify known properties. They will also not accept
|
||||
types that do not have properties in common with the ones in the `Pick`.
|
||||
To fix this error, only specify properties from the `NavigationExtras` which are
|
||||
actually used in the respective function calls or use a type assertion
|
||||
on the object or variable: `as NavigationExtras`.
|
||||
* **router:** This commit changes the default value of
|
||||
`relativeLinkResolution` from `'legacy'` to `'default'`. If your
|
||||
application previously used the default by not specifying a value in the
|
||||
`ExtraOptions` and uses relative links when navigating from children of
|
||||
empty path routes, you will need to update your `RouterModule` to
|
||||
specifically specify `'legacy'` for `relativeLinkResolution`.
|
||||
See https://angular.io/api/router/ExtraOptions#relativeLinkResolution
|
||||
for more details.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.4"></a>
|
||||
## 10.1.4 (2020-09-30)
|
||||
|
||||
@@ -221,38 +326,6 @@ for more details.
|
||||
|
||||
|
||||
|
||||
<a name="11.0.0-next.3"></a>
|
||||
# 11.0.0-next.3 (2020-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** add `params` and `reportProgress` options to `HttpClient.put()` overload ([#37873](https://github.com/angular/angular/issues/37873)) ([dd8d8c8](https://github.com/angular/angular/commit/dd8d8c8)), closes [#23600](https://github.com/angular/angular/issues/23600)
|
||||
* **compiler-cli:** generate `let` statements in ES2015+ mode ([#38775](https://github.com/angular/angular/issues/38775)) ([123bff7](https://github.com/angular/angular/commit/123bff7))
|
||||
* **core:** ensure TestBed is not instantiated before override provider ([#38717](https://github.com/angular/angular/issues/38717)) ([c8f056b](https://github.com/angular/angular/commit/c8f056b))
|
||||
* **forms:** type NG_VALUE_ACCESSOR injection token as array ([#29723](https://github.com/angular/angular/issues/29723)) ([2b1b718](https://github.com/angular/angular/commit/2b1b718)), closes [#29351](https://github.com/angular/angular/issues/29351)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** Add ISO week-numbering year formats support to formatDate ([#38828](https://github.com/angular/angular/issues/38828)) ([984ed39](https://github.com/angular/angular/commit/984ed39))
|
||||
* **compiler:** Parse and recover on incomplete opening HTML tags ([#38681](https://github.com/angular/angular/issues/38681)) ([6ae3b68](https://github.com/angular/angular/commit/6ae3b68)), closes [#38596](https://github.com/angular/angular/issues/38596)
|
||||
* **router:** add migration to update calls to navigateByUrl and createUrlTree with invalid parameters ([#38825](https://github.com/angular/angular/issues/38825)) ([7849fdd](https://github.com/angular/angular/commit/7849fdd)), closes [#38227](https://github.com/angular/angular/issues/38227)
|
||||
* **service-worker:** add the option to prefer network for navigation requests ([#38565](https://github.com/angular/angular/issues/38565)) ([a206852](https://github.com/angular/angular/commit/a206852)), closes [#38194](https://github.com/angular/angular/issues/38194)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **core:** If you call `TestBed.overrideProvider` after TestBed initialization, provider overrides are not applied. This
|
||||
behavior is consistent with other override methods (such as `TestBed.overrideDirective`, etc) but they
|
||||
throw an error to indicate that, when the check was missing in the `TestBed.overrideProvider` function.
|
||||
Now calling `TestBed.overrideProvider` after TestBed initialization also triggers an
|
||||
error, thus there is a chance that some tests (where `TestBed.overrideProvider` is
|
||||
called after TestBed initialization) will start to fail and require updates to move `TestBed.overrideProvider` calls
|
||||
before TestBed initialization is completed.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.3"></a>
|
||||
## 10.1.3 (2020-09-23)
|
||||
|
||||
@@ -269,56 +342,6 @@ before TestBed initialization is completed.
|
||||
|
||||
|
||||
|
||||
<a name="11.0.0-next.2"></a>
|
||||
# 11.0.0-next.2 (2020-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** do not round up fractions of a millisecond in `DatePipe` ([#38009](https://github.com/angular/angular/issues/38009)) ([26f2820](https://github.com/angular/angular/commit/26f2820)), closes [/www.ecma-international.org/ecma-262/5.1/#sec-15](https://github.com//www.ecma-international.org/ecma-262/5.1//issues/sec-15) [#37989](https://github.com/angular/angular/issues/37989)
|
||||
* **common:** mark locale data arrays as readonly ([#30397](https://github.com/angular/angular/issues/30397)) ([6acea54](https://github.com/angular/angular/commit/6acea54)), closes [#27003](https://github.com/angular/angular/issues/27003)
|
||||
* **compiler:** source span for microsyntax text att should be key span ([#38766](https://github.com/angular/angular/issues/38766)) ([8f349b2](https://github.com/angular/angular/commit/8f349b2))
|
||||
* **router:** Fix arguments order for call to shouldReuseRoute ([#26949](https://github.com/angular/angular/issues/26949)) ([3817e5f](https://github.com/angular/angular/commit/3817e5f)), closes [#16192](https://github.com/angular/angular/issues/16192) [#16192](https://github.com/angular/angular/issues/16192)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-cli:** `TemplateTypeChecker` operation to get `Symbol` from a template node ([#38618](https://github.com/angular/angular/issues/38618)) ([c4556db](https://github.com/angular/angular/commit/c4556db))
|
||||
* **compiler-cli:** Add ability to get `Symbol` of `Template`s and `Element`s in component template ([#38618](https://github.com/angular/angular/issues/38618)) ([cf2e8b9](https://github.com/angular/angular/commit/cf2e8b9))
|
||||
* **compiler-cli:** Add ability to get `Symbol` of AST expression in component template ([#38618](https://github.com/angular/angular/issues/38618)) ([f56ece4](https://github.com/angular/angular/commit/f56ece4))
|
||||
* **compiler-cli:** add ability to get symbol of reference or variable ([#38618](https://github.com/angular/angular/issues/38618)) ([19598b4](https://github.com/angular/angular/commit/19598b4))
|
||||
* **compiler-cli:** define interfaces to be used for TemplateTypeChecker ([#38618](https://github.com/angular/angular/issues/38618)) ([9e77bd3](https://github.com/angular/angular/commit/9e77bd3))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler-cli:** only emit directive/pipe references that are used ([#38539](https://github.com/angular/angular/issues/38539)) ([077f516](https://github.com/angular/angular/commit/077f516))
|
||||
* **compiler-cli:** optimize computation of type-check scope information ([#38539](https://github.com/angular/angular/issues/38539)) ([297c060](https://github.com/angular/angular/commit/297c060))
|
||||
* **router:** use `ngDevMode` to tree-shake error messages in router ([#38674](https://github.com/angular/angular/issues/38674)) ([db21c4f](https://github.com/angular/angular/commit/db21c4f))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **router:** This change corrects the argument order when calling
|
||||
RouteReuseStrategy#shouldReuseRoute. Previously, when evaluating child
|
||||
routes, they would be called with the future and current arguments would
|
||||
be swapped. If your RouteReuseStrategy relies specifically on only the future
|
||||
or current snapshot state, you may need to update the shouldReuseRoute
|
||||
implementation's use of "future" and "current" ActivateRouteSnapshots.
|
||||
* **common:** The locale data API has been marked as returning readonly arrays, rather
|
||||
than mutable arrays, since these arrays are shared across calls to the
|
||||
API. If you were mutating them (e.g. calling `sort()`, `push()`, `splice()`, etc)
|
||||
then your code will not longer compile. If you need to mutate the array, you
|
||||
should now take a copy (e.g. by calling `slice()`) and mutate the copy.
|
||||
* **common:** When passing a date-time formatted string to the `DatePipe` in a format that contains
|
||||
fractions of a millisecond, the milliseconds will now always be rounded down rather than
|
||||
to the nearest millisecond.
|
||||
|
||||
Most applications will not be affected by this change. If this is not the desired behaviour
|
||||
then consider pre-processing the string to round the millisecond part before passing
|
||||
it to the `DatePipe`.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.2"></a>
|
||||
## 10.1.2 (2020-09-16)
|
||||
@@ -342,24 +365,6 @@ it to the `DatePipe`.
|
||||
|
||||
|
||||
|
||||
<a name="11.0.0-next.1"></a>
|
||||
# 11.0.0-next.1 (2020-09-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** compute source-mappings for localized strings ([#38645](https://github.com/angular/angular/issues/38645)) ([7e0b3fd](https://github.com/angular/angular/commit/7e0b3fd)), closes [#38588](https://github.com/angular/angular/issues/38588)
|
||||
* **core:** remove CollectionChangeRecord symbol ([#38668](https://github.com/angular/angular/issues/38668)) ([fdea180](https://github.com/angular/angular/commit/fdea180))
|
||||
* **router:** support lazy loading for empty path named outlets ([#38379](https://github.com/angular/angular/issues/38379)) ([926ffcd](https://github.com/angular/angular/commit/926ffcd)), closes [#12842](https://github.com/angular/angular/issues/12842)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **core:** CollectionChangeRecord has been removed, use IterableChangeRecord
|
||||
instead
|
||||
|
||||
|
||||
|
||||
<a name="10.1.1"></a>
|
||||
## 10.1.1 (2020-09-09)
|
||||
|
||||
@@ -385,31 +390,6 @@ instead
|
||||
|
||||
|
||||
|
||||
<a name="11.0.0-next.0"></a>
|
||||
# 11.0.0-next.0 (2020-09-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **forms:** ensure to emit `statusChanges` on subsequent value update/validations ([#38354](https://github.com/angular/angular/issues/38354)) ([d9fea85](https://github.com/angular/angular/commit/d9fea85)), closes [#20424](https://github.com/angular/angular/issues/20424) [#14542](https://github.com/angular/angular/issues/14542)
|
||||
* **service-worker:** fix condition to check for a cache-busted request ([#36847](https://github.com/angular/angular/issues/36847)) ([5be4edf](https://github.com/angular/angular/commit/5be4edf))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **service-worker:** add `UnrecoverableStateError` ([#36847](https://github.com/angular/angular/issues/36847)) ([036a2fa](https://github.com/angular/angular/commit/036a2fa)), closes [#36539](https://github.com/angular/angular/issues/36539)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **forms:** Previously if FormControl, FormGroup and FormArray class instances had async validators
|
||||
defined at initialization time, the status change event was not emitted once async validator
|
||||
completed. After this change the status event is emitted into the `statusChanges` observable.
|
||||
If your code relies on the old behavior, you can filter/ignore this additional status change
|
||||
event.
|
||||
|
||||
|
||||
|
||||
<a name="10.1.0"></a>
|
||||
# 10.1.0 (2020-09-02)
|
||||
|
||||
@@ -5279,7 +5259,7 @@ Note: 4.1.0-beta.0 release also contains all the changes present in the 4.0.1 re
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
From 4.0.0 @angular/core uses a [`WeakMap`](https://github.com/angular/angular/commit/52b21275f4c2c26c46627f5648b41a33bb5c8283), a polyfill needs to be included for [browsers that do not support it natively](http://kangax.github.io/compat-table/es6/#test-WeakMap).
|
||||
From 4.0.0 @angular/core uses a [`WeakMap`](https://github.com/angular/angular/commit/52b21275f4c2c26c46627f5648b41a33bb5c8283), a polyfill needs to be included for [browsers that do not support it natively](https://kangax.github.io/compat-table/es6/#test-WeakMap).
|
||||
|
||||
<a name="4.0.0-rc.6"></a>
|
||||
# [4.0.0-rc.6](https://github.com/angular/angular/compare/4.0.0-rc.5...4.0.0-rc.6) (2017-03-23)
|
||||
|
||||
+18
-15
@@ -119,6 +119,9 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
|
||||
11. In GitHub, send a pull request to `angular:master`.
|
||||
|
||||
### Reviewing a Pull Request
|
||||
|
||||
The Angular team reserves the right not to accept pull requests from community members who haven't been good citizens of the community. Such behavior includes not following the [Angular code of conduct](https://github.com/angular/code-of-conduct) and applies within or outside of Angular managed channels.
|
||||
|
||||
#### Addressing review feedback
|
||||
|
||||
@@ -209,7 +212,7 @@ To ensure consistency throughout the source code, keep these rules in mind as yo
|
||||
|
||||
## <a name="commit"></a> Commit Message Format
|
||||
|
||||
*This specification is inspired and supersedes the [AngularJS commit message format][commit-message-format].*
|
||||
*This specification is inspired by and supersedes the [AngularJS commit message format][commit-message-format].*
|
||||
|
||||
We have very precise rules over how our Git commit messages must be formatted.
|
||||
This format leads to **easier to read commit history**.
|
||||
@@ -227,15 +230,15 @@ Each commit message consists of a **header**, a **body**, and a **footer**.
|
||||
|
||||
The `header` is mandatory and must conform to the [Commit Message Header](#commit-header) format.
|
||||
|
||||
The `body` is mandatory for all commits except for those of scope "docs".
|
||||
When the body is required it must be at least 20 characters long.
|
||||
The `body` is mandatory for all commits except for those of type "docs".
|
||||
When the body is present it must be at least 20 characters long and must conform to the [Commit Message Body](#commit-body) format.
|
||||
|
||||
The `footer` is optional.
|
||||
The `footer` is optional. The [Commit Message Footer](#commit-footer) format describes what the footer is used for and the structure it must have.
|
||||
|
||||
Any line of the commit message cannot be longer than 100 characters.
|
||||
|
||||
|
||||
#### <a href="commit-header"></a>Commit Message Header
|
||||
#### <a name="commit-header"></a>Commit Message Header
|
||||
|
||||
```
|
||||
<type>(<scope>): <short summary>
|
||||
@@ -248,7 +251,7 @@ Any line of the commit message cannot be longer than 100 characters.
|
||||
│ upgrade|zone.js|packaging|changelog|dev-infra|docs-infra|migrations|
|
||||
│ ngcc|ve
|
||||
│
|
||||
└─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|style|test
|
||||
└─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
|
||||
```
|
||||
|
||||
The `<type>` and `<summary>` fields are mandatory, the `(<scope>)` field is optional.
|
||||
@@ -309,7 +312,7 @@ There are currently a few exceptions to the "use package name" rule:
|
||||
|
||||
* `ve`: used for changes specific to ViewEngine (legacy compiler/renderer).
|
||||
|
||||
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`) and for docs changes that are not related to a specific package (e.g. `docs: fix typo in tutorial`).
|
||||
* none/empty string: useful for `test` and `refactor` changes that are done across all packages (e.g. `test: add missing unit tests`) and for docs changes that are not related to a specific package (e.g. `docs: fix typo in tutorial`).
|
||||
|
||||
|
||||
##### Summary
|
||||
@@ -321,7 +324,7 @@ Use the summary field to provide a succinct description of the change:
|
||||
* no dot (.) at the end
|
||||
|
||||
|
||||
#### Commit Message Body
|
||||
#### <a name="commit-body"></a>Commit Message Body
|
||||
|
||||
Just as in the summary, use the imperative, present tense: "fix" not "fixed" nor "fixes".
|
||||
|
||||
@@ -329,7 +332,7 @@ Explain the motivation for the change in the commit message body. This commit me
|
||||
You can include a comparison of the previous behavior with the new behavior in order to illustrate the impact of the change.
|
||||
|
||||
|
||||
#### Commit Message Footer
|
||||
#### <a name="commit-footer"></a>Commit Message Footer
|
||||
|
||||
The footer can contain information about breaking changes and is also the place to reference GitHub issues, Jira tickets, and other PRs that this commit closes or is related to.
|
||||
|
||||
@@ -379,13 +382,13 @@ The following documents can help you sort out issues with GitHub accounts and mu
|
||||
[angular-group]: https://groups.google.com/forum/#!forum/angular
|
||||
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
|
||||
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
[corporate-cla]: https://cla.developers.google.com/about/google-corporate
|
||||
[dev-doc]: https://github.com/angular/angular/blob/master/docs/DEVELOPER.md
|
||||
[github]: https://github.com/angular/angular
|
||||
[discord]: https://discord.gg/angular
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[individual-cla]: https://cla.developers.google.com/about/google-individual
|
||||
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
|
||||
[jsfiddle]: http://jsfiddle.net
|
||||
[plunker]: http://plnkr.co/edit
|
||||
[runnable]: http://runnable.com
|
||||
[stackoverflow]: http://stackoverflow.com/questions/tagged/angular
|
||||
[jsfiddle]: https://jsfiddle.net/
|
||||
[plunker]: https://plnkr.co/edit
|
||||
[runnable]: https://runnable.com/
|
||||
[stackoverflow]: https://stackoverflow.com/questions/tagged/angular
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2020 Google LLC. http://angular.io/license
|
||||
Copyright (c) 2010-2020 Google LLC. https://angular.io/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,27 +1,151 @@
|
||||
[](https://circleci.com/gh/angular/workflows/angular/tree/master)
|
||||
[](https://discord.gg/angular)
|
||||
[](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://www.npmjs.com/@angular/core)
|
||||
<h1 align="center">Angular - One framework. Mobile & desktop.</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="aio/src/assets/images/logos/angular/angular.png" alt="angular-logo" width="120px" height="120px"/>
|
||||
<br>
|
||||
<i>Angular is a development platform for building mobile and desktop web applications
|
||||
<br> using Typescript/JavaScript and other languages.</i>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
# Angular
|
||||
<p align="center">
|
||||
<a href="https://www.angular.io"><strong>www.angular.io</strong></a>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages.
|
||||
<p align="center">
|
||||
<a href="CONTRIBUTING.md">Contributing Guidelines</a>
|
||||
·
|
||||
<a href="https://github.com/angular/angular/issues">Submit an Issue</a>
|
||||
·
|
||||
<a href="https://blog.angular.io/">Blog</a>
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://circleci.com/gh/angular/workflows/angular/tree/master">
|
||||
<img src="https://img.shields.io/circleci/build/github/angular/angular/master.svg?logo=circleci&logoColor=fff&label=CircleCI" alt="CI status" />
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/@angular/core">
|
||||
<img src="https://img.shields.io/npm/v/@angular/core.svg?logo=npm&logoColor=fff&label=NPM+package&color=limegreen" alt="Angular on npm" />
|
||||
</a>
|
||||
<a href="https://discord.gg/angular">
|
||||
<img src="https://img.shields.io/discord/463752820026376202.svg?logo=discord&logoColor=fff&label=Discord&color=7389d8" alt="Discord conversation" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
## Documentation
|
||||
|
||||
Get started with Angular, learn the fundamentals and explore advanced topics on our documentation website.
|
||||
|
||||
- [Getting Started][quickstart]
|
||||
- [Architecture][architecture]
|
||||
- [Components and Templates][componentstemplates]
|
||||
- [Forms][forms]
|
||||
- [API][api]
|
||||
|
||||
### Advanced
|
||||
|
||||
- [Angular Elements][angularelements]
|
||||
- [Server Side Rendering][ssr]
|
||||
- [Schematics][schematics]
|
||||
- [Lazy Loading][lazyloading]
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Install [Node.js] which includes [Node Package Manager][npm]
|
||||
|
||||
### Setting Up a Project
|
||||
|
||||
Install the Angular CLI globally:
|
||||
|
||||
```
|
||||
npm install -g @angular/cli
|
||||
```
|
||||
|
||||
Create workspace:
|
||||
|
||||
```
|
||||
ng new [PROJECT NAME]
|
||||
```
|
||||
|
||||
Run the application:
|
||||
|
||||
```
|
||||
cd [PROJECT NAME]
|
||||
ng serve
|
||||
```
|
||||
|
||||
## Quickstart
|
||||
|
||||
[Get started in 5 minutes][quickstart].
|
||||
|
||||
## Ecosystem
|
||||
|
||||
<p>
|
||||
<img src="/docs/images/angular-ecosystem-logos.png" alt="angular ecosystem logos" width="500px" height="auto">
|
||||
</p>
|
||||
|
||||
- [Angular Command Line (CLI)][cli]
|
||||
- [Angular Material][angularmaterial]
|
||||
|
||||
## Changelog
|
||||
|
||||
[Learn about the latest improvements][changelog].
|
||||
|
||||
## Want to help?
|
||||
## Upgrading
|
||||
|
||||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
|
||||
guidelines for [contributing][contributing] and then check out one of our issues in the [hotlist: community-help](https://github.com/angular/angular/labels/hotlist%3A%20community-help).
|
||||
Check out our [upgrade guide](https://update.angular.io/) to find out the best way to upgrade your project.
|
||||
|
||||
[contributing]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md
|
||||
## Contributing
|
||||
|
||||
### Contributing Guidelines
|
||||
|
||||
Read through our [contributing guidelines][contributing] to learn about our submission process, coding rules and more.
|
||||
|
||||
### Want to Help?
|
||||
|
||||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our guidelines for [contributing][contributing] and then check out one of our issues in the [hotlist: community-help](https://github.com/angular/angular/labels/hotlist%3A%20community-help).
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
Help us keep Angular open and inclusive. Please read and follow our [Code of Conduct][codeofconduct].
|
||||
|
||||
## Community
|
||||
|
||||
Join the conversation and help the community.
|
||||
|
||||
- [Twitter][twitter]
|
||||
- [Gitter][gitter]
|
||||
- Find a Local [Meetup][meetup]
|
||||
|
||||
[](https://www.github.com/angular/angular)
|
||||
|
||||
**Love Angular? Give our repo a star :star: :arrow_up:.**
|
||||
|
||||
[contributing]: CONTRIBUTING.md
|
||||
[quickstart]: https://angular.io/start
|
||||
[changelog]: https://github.com/angular/angular/blob/master/CHANGELOG.md
|
||||
[changelog]: CHANGELOG.md
|
||||
[ng]: https://angular.io
|
||||
[documentation]: https://angular.io/docs
|
||||
[angularmaterial]: https://material.angular.io/
|
||||
[cli]: https://cli.angular.io/
|
||||
[architecture]: https://angular.io/guide/architecture
|
||||
[componentstemplates]: https://angular.io/guide/displaying-data
|
||||
[forms]: https://angular.io/guide/forms-overview
|
||||
[api]: https://angular.io/api
|
||||
[angularelements]: https://angular.io/guide/elements
|
||||
[ssr]: https://angular.io/guide/universal
|
||||
[schematics]: https://angular.io/guide/schematics
|
||||
[lazyloading]: https://angular.io/guide/lazy-loading-ngmodules
|
||||
[node.js]: https://nodejs.org/
|
||||
[npm]: https://www.npmjs.com/get-npm
|
||||
[codeofconduct]: CODE_OF_CONDUCT.md
|
||||
[twitter]: https://www.twitter.com/angular
|
||||
[gitter]: https://gitter.im/angular/angular
|
||||
[meetup]: https://www.meetup.com/find/?keywords=angular"
|
||||
|
||||
@@ -81,14 +81,14 @@ rbe_autoconfig(
|
||||
# Need to specify a base container digest in order to ensure that we can use the checked-in
|
||||
# platform configurations for the "ubuntu16_04" image. Otherwise the autoconfig rule would
|
||||
# need to pull the image and run it in order determine the toolchain configuration. See:
|
||||
# https://github.com/bazelbuild/bazel-toolchains/blob/3.5.1/configs/ubuntu16_04_clang/versions.bzl
|
||||
# https://github.com/bazelbuild/bazel-toolchains/blob/3.6.0/configs/ubuntu16_04_clang/versions.bzl
|
||||
base_container_digest = "sha256:f6568d8168b14aafd1b707019927a63c2d37113a03bcee188218f99bd0327ea1",
|
||||
# Note that if you change the `digest`, you might also need to update the
|
||||
# `base_container_digest` to make sure marketplace.gcr.io/google/rbe-ubuntu16-04-webtest:<digest>
|
||||
# and marketplace.gcr.io/google/rbe-ubuntu16-04:<base_container_digest> have
|
||||
# the same Clang and JDK installed. Clang is needed because of the dependency on
|
||||
# @com_google_protobuf. Java is needed for the Bazel's test executor Java tool.
|
||||
digest = "sha256:f743114235a43355bf8324e2ba0fa6a597236fe06f7bc99aaa9ac703631c306b",
|
||||
digest = "sha256:dddaaddbe07a61c2517f9b08c4977fc23c4968fcb6c0b8b5971e955d2de7a961",
|
||||
env = clang_env(),
|
||||
registry = "marketplace.gcr.io",
|
||||
# We can't use the default "ubuntu16_04" RBE image provided by the autoconfig because we need
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
|
||||
+2
-1
@@ -36,8 +36,9 @@ Here are the most important tasks you might need to use:
|
||||
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
|
||||
* `yarn boilerplate:add:viewengine` - same as `boilerplate:add` but also turns on `ViewEngine` (pre-Ivy) mode.
|
||||
|
||||
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
|
||||
* `yarn create-example` - create a new example directory containing initial source files.
|
||||
|
||||
* `yarn generate-stackblitz` - generate the stackblitz files that are used by the `live-example` tags in the docs.
|
||||
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
||||
|
||||
|
||||
@@ -6,4 +6,4 @@ TODO (gkalpak): Add docs. Mention:
|
||||
Relevant files: `aio/aio-builds-setup/scripts/test.sh`
|
||||
- Deploying from CI.
|
||||
Relevant files: `.circleci/config.yml`, `scripts/ci/deploy.sh`, `aio/scripts/build-artifacts.sh`,
|
||||
`aio/scripts/deploy-to-firebase.sh`
|
||||
`aio/scripts/deploy-to-firebase.js`
|
||||
|
||||
@@ -59,4 +59,4 @@ log its output to `update-preview-server.log` (assuming the user has the necessa
|
||||
Keep in mind that cron jobs run in non-interactive, non-login shells. This means that the execution
|
||||
context might be different compared to when running the same commands from an interactive, login
|
||||
shell. For example, `.bashrc` files are normally _not_ sourced automatically in cron jobs. See
|
||||
[here](http://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html) for more info.
|
||||
[here](https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html) for more info.
|
||||
|
||||
+12
-1
@@ -37,7 +37,6 @@
|
||||
"outputHashing": "all",
|
||||
"sourceMap": true,
|
||||
"statsJson": true,
|
||||
"extractCss": true,
|
||||
"extractLicenses": true,
|
||||
"namedChunks": true,
|
||||
"vendorChunk": false,
|
||||
@@ -81,6 +80,15 @@
|
||||
],
|
||||
"serviceWorker": true
|
||||
},
|
||||
"rc": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.rc.ts"
|
||||
}
|
||||
],
|
||||
"serviceWorker": true
|
||||
},
|
||||
"stable": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
@@ -116,6 +124,9 @@
|
||||
"next": {
|
||||
"browserTarget": "site:build:next"
|
||||
},
|
||||
"rc": {
|
||||
"browserTarget": "site:build:rc"
|
||||
},
|
||||
"stable": {
|
||||
"browserTarget": "site:build:stable"
|
||||
},
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
**/karma-test-shim.js
|
||||
**/browser-test-shim.js
|
||||
**/node_modules
|
||||
**/yarn.lock
|
||||
**/package-lock.json
|
||||
|
||||
# built files
|
||||
*.map
|
||||
|
||||
@@ -2,18 +2,16 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Accessibility example e2e tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
it('should display Accessibility Example', async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual('Accessibility Example');
|
||||
});
|
||||
|
||||
it('should display Accessibility Example', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Accessibility Example');
|
||||
});
|
||||
|
||||
it('should take a number and change progressbar width', () => {
|
||||
element(by.css('input')).sendKeys('16');
|
||||
expect(element(by.css('input')).getAttribute('value')).toEqual('016');
|
||||
expect(element(by.css('app-example-progressbar div')).getCssValue('width')).toBe('48px');
|
||||
it('should take a number and change progressbar width', async () => {
|
||||
await element(by.css('input')).sendKeys('16');
|
||||
expect(await element(by.css('input')).getAttribute('value')).toEqual('16');
|
||||
expect(await element(by.css('app-example-progressbar div')).getCssValue('width')).toBe('48px');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<label>
|
||||
Enter an example progress value
|
||||
<input type="number" min="0" max="100"
|
||||
[value]="progress" (input)="progress = $event.target.value">
|
||||
[value]="progress" (input)="setProgress($event)">
|
||||
</label>
|
||||
|
||||
<!-- The user of the progressbar sets an aria-label to communicate what the progress means. -->
|
||||
|
||||
@@ -7,4 +7,8 @@ import { Component } from '@angular/core';
|
||||
})
|
||||
export class AppComponent {
|
||||
progress = 0;
|
||||
|
||||
setProgress($event: Event) {
|
||||
this.progress = +($event.target as HTMLInputElement).value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,13 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('AngularJS to Angular Quick Reference Tests', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should display no poster images after bootstrap', async () => {
|
||||
await testImagesAreDisplayed(false);
|
||||
});
|
||||
|
||||
it('should display no poster images after bootstrap', () => {
|
||||
testImagesAreDisplayed(false);
|
||||
});
|
||||
|
||||
it('should display proper movie data', () => {
|
||||
it('should display proper movie data', async () => {
|
||||
// We check only a few samples
|
||||
const expectedSamples: any[] = [
|
||||
{row: 0, column: 0, element: 'img', attr: 'src', value: 'images/hero.png', contains: true},
|
||||
@@ -34,8 +32,8 @@ describe('AngularJS to Angular Quick Reference Tests', () => {
|
||||
|
||||
// Check element attribute or text
|
||||
const valueToCheck = sample.attr
|
||||
? elementToCheck.getAttribute(sample.attr)
|
||||
: elementToCheck.getText();
|
||||
? await elementToCheck.getAttribute(sample.attr)
|
||||
: await elementToCheck.getText();
|
||||
|
||||
// Test for equals/contains/match
|
||||
if (sample.contains) {
|
||||
@@ -48,65 +46,64 @@ describe('AngularJS to Angular Quick Reference Tests', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should display images after Show Poster', () => {
|
||||
testPosterButtonClick('Show Poster', true);
|
||||
it('should display images after Show Poster', async () => {
|
||||
await testPosterButtonClick('Show Poster', true);
|
||||
});
|
||||
|
||||
it('should hide images after Hide Poster', () => {
|
||||
testPosterButtonClick('Hide Poster', false);
|
||||
it('should hide images after Hide Poster', async () => {
|
||||
await testPosterButtonClick('Hide Poster', false);
|
||||
});
|
||||
|
||||
it('should display no movie when no favorite hero is specified', () => {
|
||||
testFavoriteHero(null, 'Please enter your favorite hero.');
|
||||
it('should display no movie when no favorite hero is specified', async () => {
|
||||
await testFavoriteHero(null, 'Please enter your favorite hero.');
|
||||
});
|
||||
|
||||
it('should display no movie for Magneta', () => {
|
||||
testFavoriteHero('Magneta', 'No movie, sorry!');
|
||||
it('should display no movie for Magneta', async () => {
|
||||
await testFavoriteHero('Magneta', 'No movie, sorry!');
|
||||
});
|
||||
|
||||
it('should display a movie for Dr Nice', () => {
|
||||
testFavoriteHero('Dr Nice', 'Excellent choice!');
|
||||
it('should display a movie for Dr Nice', async () => {
|
||||
await testFavoriteHero('Dr Nice', 'Excellent choice!');
|
||||
});
|
||||
|
||||
function testImagesAreDisplayed(isDisplayed: boolean) {
|
||||
async function testImagesAreDisplayed(isDisplayed: boolean) {
|
||||
const expectedMovieCount = 3;
|
||||
|
||||
const movieRows = getMovieRows();
|
||||
expect(movieRows.count()).toBe(expectedMovieCount);
|
||||
expect(await movieRows.count()).toBe(expectedMovieCount);
|
||||
for (let i = 0; i < expectedMovieCount; i++) {
|
||||
const movieImage = movieRows.get(i).element(by.css('td > img'));
|
||||
expect(movieImage.isDisplayed()).toBe(isDisplayed);
|
||||
expect(await movieImage.isDisplayed()).toBe(isDisplayed);
|
||||
}
|
||||
}
|
||||
|
||||
function testPosterButtonClick(expectedButtonText: string, isDisplayed: boolean) {
|
||||
async function testPosterButtonClick(expectedButtonText: string, isDisplayed: boolean) {
|
||||
const posterButton = element(by.css('app-movie-list tr > th > button'));
|
||||
expect(posterButton.getText()).toBe(expectedButtonText);
|
||||
expect(await posterButton.getText()).toBe(expectedButtonText);
|
||||
|
||||
posterButton.click().then(() => {
|
||||
testImagesAreDisplayed(isDisplayed);
|
||||
});
|
||||
await posterButton.click();
|
||||
await testImagesAreDisplayed(isDisplayed);
|
||||
}
|
||||
|
||||
function getMovieRows() {
|
||||
return element.all(by.css('app-movie-list tbody > tr'));
|
||||
}
|
||||
|
||||
function testFavoriteHero(heroName: string, expectedLabel: string) {
|
||||
async function testFavoriteHero(heroName: string, expectedLabel: string) {
|
||||
const movieListComp = element(by.tagName('app-movie-list'));
|
||||
const heroInput = movieListComp.element(by.tagName('input'));
|
||||
const favoriteHeroLabel = movieListComp.element(by.tagName('h3'));
|
||||
const resultLabel = movieListComp.element(by.css('span > p'));
|
||||
|
||||
heroInput.clear().then(() => {
|
||||
heroInput.sendKeys(heroName || '');
|
||||
expect(resultLabel.getText()).toBe(expectedLabel);
|
||||
if (heroName) {
|
||||
expect(favoriteHeroLabel.isDisplayed()).toBe(true);
|
||||
expect(favoriteHeroLabel.getText()).toContain(heroName);
|
||||
} else {
|
||||
expect(favoriteHeroLabel.isDisplayed()).toBe(false);
|
||||
}
|
||||
});
|
||||
await heroInput.clear();
|
||||
await heroInput.sendKeys(heroName || '');
|
||||
|
||||
expect(await resultLabel.getText()).toBe(expectedLabel);
|
||||
if (heroName) {
|
||||
expect(await favoriteHeroLabel.isDisplayed()).toBe(true);
|
||||
expect(await favoriteHeroLabel.getText()).toContain(heroName);
|
||||
} else {
|
||||
expect(await favoriteHeroLabel.isDisplayed()).toBe(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { browser, ExpectedConditions as EC } from 'protractor';
|
||||
import { browser } from 'protractor';
|
||||
import { logging } from 'selenium-webdriver';
|
||||
import * as openClose from './open-close.po';
|
||||
import * as statusSlider from './status-slider.po';
|
||||
@@ -18,9 +18,7 @@ describe('Animation Tests', () => {
|
||||
const filterHref = getLinkById('heroes');
|
||||
const heroGroupsHref = getLinkById('hero-groups');
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
describe('Open/Close Component', () => {
|
||||
const closedHeight = '100px';
|
||||
@@ -28,7 +26,7 @@ describe('Animation Tests', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await openCloseHref.click();
|
||||
sleepFor();
|
||||
await sleepFor();
|
||||
});
|
||||
|
||||
it('should be open', async () => {
|
||||
@@ -84,7 +82,7 @@ describe('Animation Tests', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await statusSliderHref.click();
|
||||
sleepFor(2000);
|
||||
await sleepFor(2000);
|
||||
});
|
||||
|
||||
it('should be inactive with a blue background', async () => {
|
||||
@@ -125,7 +123,7 @@ describe('Animation Tests', () => {
|
||||
describe('Toggle Animations Component', () => {
|
||||
beforeAll(async () => {
|
||||
await toggleHref.click();
|
||||
sleepFor();
|
||||
await sleepFor();
|
||||
});
|
||||
|
||||
it('should disabled animations on the child element', async () => {
|
||||
@@ -143,7 +141,7 @@ describe('Animation Tests', () => {
|
||||
describe('Enter/Leave Component', () => {
|
||||
beforeAll(async () => {
|
||||
await enterLeaveHref.click();
|
||||
sleepFor(100);
|
||||
await sleepFor(100);
|
||||
});
|
||||
|
||||
it('should attach a flyInOut trigger to the list of items', async () => {
|
||||
@@ -169,7 +167,7 @@ describe('Animation Tests', () => {
|
||||
describe('Auto Calculation Component', () => {
|
||||
beforeAll(async () => {
|
||||
await autoHref.click();
|
||||
sleepFor(0);
|
||||
await sleepFor(0);
|
||||
});
|
||||
|
||||
it('should attach a shrinkOut trigger to the list of items', async () => {
|
||||
@@ -193,7 +191,7 @@ describe('Animation Tests', () => {
|
||||
describe('Filter/Stagger Component', () => {
|
||||
beforeAll(async () => {
|
||||
await filterHref.click();
|
||||
sleepFor();
|
||||
await sleepFor();
|
||||
});
|
||||
|
||||
it('should attach a filterAnimations trigger to the list container', async () => {
|
||||
@@ -220,7 +218,7 @@ describe('Animation Tests', () => {
|
||||
describe('Hero Groups Component', () => {
|
||||
beforeAll(async () => {
|
||||
await heroGroupsHref.click();
|
||||
sleepFor(300);
|
||||
await sleepFor(300);
|
||||
});
|
||||
|
||||
it('should attach a flyInOut trigger to the list of items', async () => {
|
||||
@@ -245,5 +243,3 @@ describe('Animation Tests', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -14,12 +14,12 @@ describe('Architecture', () => {
|
||||
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it(`has title '${expectedTitle}'`, () => {
|
||||
expect(browser.getTitle()).toEqual(expectedTitle);
|
||||
it(`has title '${expectedTitle}'`, async () => {
|
||||
expect(await browser.getTitle()).toEqual(expectedTitle);
|
||||
});
|
||||
|
||||
it(`has h2 '${expectedH2}'`, () => {
|
||||
const h2 = element.all(by.css('h2')).map((elt: any) => elt.getText());
|
||||
it(`has h2 '${expectedH2}'`, async () => {
|
||||
const h2 = await element.all(by.css('h2')).map((elt: any) => elt.getText());
|
||||
expect(h2).toEqual(expectedH2);
|
||||
});
|
||||
|
||||
@@ -31,14 +31,14 @@ function heroTests() {
|
||||
|
||||
const targetHero: Hero = { id: 2, name: 'Dr Nice' };
|
||||
|
||||
it('has the right number of heroes', () => {
|
||||
it('has the right number of heroes', async () => {
|
||||
const page = getPageElts();
|
||||
expect(page.heroes.count()).toEqual(3);
|
||||
expect(await page.heroes.count()).toEqual(3);
|
||||
});
|
||||
|
||||
it('has no hero details initially', () => {
|
||||
it('has no hero details initially', async () => {
|
||||
const page = getPageElts();
|
||||
expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail');
|
||||
expect(await page.heroDetail.isPresent()).toBeFalsy('no hero detail');
|
||||
});
|
||||
|
||||
it('shows selected hero details', async () => {
|
||||
@@ -51,7 +51,7 @@ function heroTests() {
|
||||
|
||||
it(`shows updated hero name in details`, async () => {
|
||||
const input = element.all(by.css('input')).first();
|
||||
input.sendKeys(nameSuffix);
|
||||
await input.sendKeys(nameSuffix);
|
||||
const page = getPageElts();
|
||||
const hero = await heroFromDetail(page.heroDetail);
|
||||
const newName = targetHero.name + nameSuffix;
|
||||
@@ -61,15 +61,15 @@ function heroTests() {
|
||||
}
|
||||
|
||||
function salesTaxTests() {
|
||||
it('has no sales tax initially', () => {
|
||||
it('has no sales tax initially', async () => {
|
||||
const page = getPageElts();
|
||||
expect(page.salesTaxDetail.isPresent()).toBeFalsy('no sales tax info');
|
||||
expect(await page.salesTaxDetail.isPresent()).toBeFalsy('no sales tax info');
|
||||
});
|
||||
|
||||
it('shows sales tax', async () => {
|
||||
const page = getPageElts();
|
||||
page.salesTaxAmountInput.sendKeys('10', protractor.Key.ENTER);
|
||||
expect(page.salesTaxDetail.getText()).toEqual('The sales tax is $1.00');
|
||||
await page.salesTaxAmountInput.sendKeys('10', protractor.Key.ENTER);
|
||||
expect(await page.salesTaxDetail.getText()).toEqual('The sales tax is $1.00');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,33 +2,34 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Attribute binding example', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
it('should display Property Binding with Angular', async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual('Attribute, class, and style bindings');
|
||||
});
|
||||
|
||||
it('should display Property Binding with Angular', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Attribute, class, and style bindings');
|
||||
it('should display a table', async () => {
|
||||
expect(await element.all(by.css('table')).isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should display a table', () => {
|
||||
expect(element.all(by.css('table')).isPresent()).toBe(true);
|
||||
it('should display an Aria button', async () => {
|
||||
expect(await element.all(by.css('button')).get(0).getText()).toBe('Go for it with Aria');
|
||||
});
|
||||
|
||||
it('should display an Aria button', () => {
|
||||
expect(element.all(by.css('button')).get(0).getText()).toBe('Go for it with Aria');
|
||||
it('should display a blue background on div', async () => {
|
||||
const div = element.all(by.css('div')).get(1);
|
||||
expect(await div.getCssValue('background-color')).toEqual('rgba(25, 118, 210, 1)');
|
||||
});
|
||||
|
||||
it('should display a blue background on div', () => {
|
||||
expect(element.all(by.css('div')).get(1).getCssValue('background-color')).toEqual('rgba(25, 118, 210, 1)');
|
||||
it('should display a blue div with a red border', async () => {
|
||||
const div = element.all(by.css('div')).get(1);
|
||||
expect(await div.getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)');
|
||||
});
|
||||
|
||||
it('should display a blue div with a red border', () => {
|
||||
expect(element.all(by.css('div')).get(1).getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)');
|
||||
});
|
||||
|
||||
it('should display a div with many classes', () => {
|
||||
expect(element.all(by.css('div')).get(1).getAttribute('class')).toContain('special');
|
||||
expect(element.all(by.css('div')).get(1).getAttribute('class')).toContain('clearance');
|
||||
it('should display a div with many classes', async () => {
|
||||
const div = element.all(by.css('div')).get(1);
|
||||
expect(await div.getAttribute('class')).toContain('special');
|
||||
expect(await div.getAttribute('class')).toContain('clearance');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
<h2>Attribute binding</h2>
|
||||
<!-- #docregion attrib-binding-colspan -->
|
||||
<table border=1>
|
||||
<!-- #docregion colspan -->
|
||||
<!-- expression calculates colspan=2 -->
|
||||
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
|
||||
<!-- #enddocregion colspan -->
|
||||
|
||||
<!-- ERROR: There is no `colspan` property to set!
|
||||
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
||||
@@ -32,31 +34,31 @@
|
||||
<!-- #docregion basic-specificity -->
|
||||
<h3>Basic specificity</h3>
|
||||
|
||||
<!-- The `class.special` binding will override any value for the `special` class in `classExpr`. -->
|
||||
<div [class.special]="isSpecial" [class]="classExpr">Some text.</div>
|
||||
<!-- The `class.special` binding overrides any value for the `special` class in `classExpression`. -->
|
||||
<div [class.special]="isSpecial" [class]="classExpression">Some text.</div>
|
||||
|
||||
<!-- The `style.color` binding will override any value for the `color` property in `styleExpr`. -->
|
||||
<div [style.color]="color" [style]="styleExpr">Some text.</div>
|
||||
<!-- The `style.color` binding overrides any value for the `color` property in `styleExpression`. -->
|
||||
<div [style.color]="color" [style]="styleExpression">Some text.</div>
|
||||
<!-- #enddocregion basic-specificity -->
|
||||
|
||||
<!-- #docregion source-specificity -->
|
||||
<h3>Source specificity</h3>
|
||||
|
||||
<!-- The `class.special` template binding will override any host binding to the `special` class set by `dirWithClassBinding` or `comp-with-host-binding`.-->
|
||||
<!-- The `class.special` template binding overrides any host binding to the `special` class set by `dirWithClassBinding` or `comp-with-host-binding`.-->
|
||||
<comp-with-host-binding [class.special]="isSpecial" dirWithClassBinding>Some text.</comp-with-host-binding>
|
||||
|
||||
<!-- The `style.color` template binding will override any host binding to the `color` property set by `dirWithStyleBinding` or `comp-with-host-binding`. -->
|
||||
<!-- The `style.color` template binding overrides any host binding to the `color` property set by `dirWithStyleBinding` or `comp-with-host-binding`. -->
|
||||
<comp-with-host-binding [style.color]="color" dirWithStyleBinding>Some text.</comp-with-host-binding>
|
||||
<!-- #enddocregion source-specificity -->
|
||||
|
||||
<!-- #docregion dynamic-priority -->
|
||||
<h3>Dynamic vs static</h3>
|
||||
|
||||
<!-- If `classExpr` has a value for the `special` class, this value will override the `class="special"` below -->
|
||||
<div class="special" [class]="classExpr">Some text.</div>
|
||||
<!-- If `classExpression` has a value for the `special` class, this value overrides the `class="special"` below -->
|
||||
<div class="special" [class]="classExpression">Some text.</div>
|
||||
|
||||
<!-- If `styleExpr` has a value for the `color` property, this value will override the `style="color: blue"` below -->
|
||||
<div style="color: blue" [style]="styleExpr">Some text.</div>
|
||||
<!-- If `styleExpression` has a value for the `color` property, this value overrides the `style="color: blue"` below -->
|
||||
<div style="color: blue" [style]="styleExpression">Some text.</div>
|
||||
|
||||
<!-- #enddocregion dynamic-priority -->
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export class AppComponent {
|
||||
actionName = 'Go for it';
|
||||
isSpecial = true;
|
||||
canSave = true;
|
||||
classExpr = 'special clearance';
|
||||
styleExpr = 'color: red';
|
||||
classExpression = 'special clearance';
|
||||
styleExpression = 'color: red';
|
||||
color = 'blue';
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ export class CompWithHostBindingComponent {
|
||||
@HostBinding('style.color')
|
||||
color = 'green';
|
||||
|
||||
// #docregion hostbinding
|
||||
@HostBinding('style.width')
|
||||
width = '200px';
|
||||
// #enddocregion hostbinding
|
||||
|
||||
}
|
||||
|
||||
@@ -4,27 +4,25 @@ describe('Attribute directives', () => {
|
||||
|
||||
const title = 'My First Attribute Directive';
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it(`should display correct title: ${title}`, async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual(title);
|
||||
});
|
||||
|
||||
it(`should display correct title: ${title}`, () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual(title);
|
||||
});
|
||||
|
||||
it('should be able to select green highlight', () => {
|
||||
it('should be able to select green highlight', async () => {
|
||||
const highlightedEle = element(by.cssContainingText('p', 'Highlight me!'));
|
||||
const lightGreen = 'rgba(144, 238, 144, 1)';
|
||||
const getBgColor = () => highlightedEle.getCssValue('background-color');
|
||||
|
||||
expect(highlightedEle.getCssValue('background-color')).not.toEqual(lightGreen);
|
||||
expect(await highlightedEle.getCssValue('background-color')).not.toEqual(lightGreen);
|
||||
|
||||
const greenRb = element.all(by.css('input')).get(0);
|
||||
greenRb.click();
|
||||
browser.actions().mouseMove(highlightedEle).perform();
|
||||
await greenRb.click();
|
||||
await browser.actions().mouseMove(highlightedEle).perform();
|
||||
|
||||
// Wait for up to 4s for the background color to be updated,
|
||||
// to account for slow environments (e.g. CI).
|
||||
browser.wait(() => highlightedEle.getCssValue('background-color').then(c => c === lightGreen), 4000);
|
||||
await browser.wait(async () => await getBgColor() === lightGreen, 4000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- #docregion v2, -->
|
||||
<h1>My First Attribute Directive</h1>
|
||||
|
||||
<h4>Pick a highlight color</h4>
|
||||
<h2>Pick a highlight color</h2>
|
||||
<div>
|
||||
<input type="radio" name="colors" (click)="color='lightgreen'">Green
|
||||
<input type="radio" name="colors" (click)="color='yellow'">Yellow
|
||||
@@ -17,8 +17,22 @@
|
||||
</p>
|
||||
<!-- #enddocregion defaultColor, -->
|
||||
|
||||
<hr>
|
||||
<p><i>Mouse over the following lines to see fixed highlights</i></p>
|
||||
<hr />
|
||||
<h2>Mouse over the following lines to see fixed highlights</h2>
|
||||
|
||||
<p [appHighlight]="'yellow'">Highlighted in yellow</p>
|
||||
<p appHighlight="orange">Highlighted in orange</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>ngNonBindable</h2>
|
||||
<!-- #docregion ngNonBindable -->
|
||||
<p>Use ngNonBindable to stop evaluation.</p>
|
||||
<p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p>
|
||||
<!-- #enddocregion ngNonBindable -->
|
||||
|
||||
<!-- #docregion ngNonBindable-with-directive -->
|
||||
<h3>ngNonBindable with a directive</h3>
|
||||
<div ngNonBindable [appHighlight]="'yellow'">This should not evaluate: {{ 1 +1 }}, but will highlight yellow.
|
||||
</div>
|
||||
<!-- #enddocregion ngNonBindable-with-directive -->
|
||||
|
||||
@@ -7,31 +7,31 @@ describe('Binding syntax e2e tests', () => {
|
||||
|
||||
|
||||
// helper function used to test what's logged to the console
|
||||
async function logChecker(button, contents) {
|
||||
async function logChecker(contents) {
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
const messages = logs.filter(({ message }) => message.indexOf(contents) !== -1 ? true : false);
|
||||
expect(messages.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
|
||||
it('should display Binding syntax', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Binding syntax');
|
||||
it('should display Binding syntax', async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual('Binding syntax');
|
||||
});
|
||||
|
||||
it('should display Save button', () => {
|
||||
expect(element.all(by.css('button')).get(0).getText()).toBe('Save');
|
||||
it('should display Save button', async () => {
|
||||
expect(await element.all(by.css('button')).get(0).getText()).toBe('Save');
|
||||
});
|
||||
|
||||
it('should display HTML attributes and DOM properties', () => {
|
||||
expect(element.all(by.css('h2')).get(1).getText()).toBe('HTML attributes and DOM properties');
|
||||
it('should display HTML attributes and DOM properties', async () => {
|
||||
expect(await element.all(by.css('h2')).get(1).getText()).toBe('HTML attributes and DOM properties');
|
||||
});
|
||||
|
||||
it('should display 1. Use the inspector...', () => {
|
||||
expect(element.all(by.css('p')).get(0).getText()).toContain('1. Use the inspector');
|
||||
it('should display 1. Use the inspector...', async () => {
|
||||
expect(await element.all(by.css('p')).get(0).getText()).toContain('1. Use the inspector');
|
||||
});
|
||||
|
||||
it('should display Disabled property vs. attribute', () => {
|
||||
expect(element.all(by.css('h3')).get(0).getText()).toBe('Disabled property vs. attribute');
|
||||
it('should display Disabled property vs. attribute', async () => {
|
||||
expect(await element.all(by.css('h3')).get(0).getText()).toBe('Disabled property vs. attribute');
|
||||
});
|
||||
|
||||
|
||||
@@ -39,36 +39,36 @@ describe('Binding syntax e2e tests', () => {
|
||||
const attributeButton = element.all(by.css('button')).get(1);
|
||||
await attributeButton.click();
|
||||
const contents = 'Sarah';
|
||||
logChecker(attributeButton, contents);
|
||||
await logChecker(contents);
|
||||
});
|
||||
|
||||
it('should log a message including Sarah for DOM property', async () => {
|
||||
const DOMPropertyButton = element.all(by.css('button')).get(2);
|
||||
await DOMPropertyButton.click();
|
||||
const contents = 'Sarah';
|
||||
logChecker(DOMPropertyButton, contents);
|
||||
await logChecker(contents);
|
||||
});
|
||||
|
||||
it('should log a message including Sally for DOM property', async () => {
|
||||
const DOMPropertyButton = element.all(by.css('button')).get(2);
|
||||
const input = element(by.css('input'));
|
||||
input.sendKeys('Sally');
|
||||
await input.sendKeys('Sally');
|
||||
await DOMPropertyButton.click();
|
||||
const contents = 'Sally';
|
||||
logChecker(DOMPropertyButton, contents);
|
||||
await logChecker(contents);
|
||||
});
|
||||
|
||||
it('should log a message that Test Button works', async () => {
|
||||
const testButton = element.all(by.css('button')).get(3);
|
||||
await testButton.click();
|
||||
const contents = 'Test';
|
||||
logChecker(testButton, contents);
|
||||
await logChecker(contents);
|
||||
});
|
||||
|
||||
it('should toggle Test Button disabled', async () => {
|
||||
const toggleButton = element.all(by.css('button')).get(4);
|
||||
await toggleButton.click();
|
||||
const contents = 'true';
|
||||
logChecker(toggleButton, contents);
|
||||
await logChecker(contents);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,8 +7,8 @@ describe('feature-modules App', () => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display message saying app works', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('app works!');
|
||||
it('should display message saying app works', async () => {
|
||||
await page.navigateTo();
|
||||
expect(await page.getTitleText()).toEqual('app works!');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,75 +2,72 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Built-in Directives', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should have title Built-in Directives', () => {
|
||||
it('should have title Built-in Directives', async () => {
|
||||
const title = element.all(by.css('h1')).get(0);
|
||||
expect(title.getText()).toEqual('Built-in Directives');
|
||||
expect(await title.getText()).toEqual('Built-in Directives');
|
||||
});
|
||||
|
||||
it('should change first Teapot header', async () => {
|
||||
const firstLabel = element.all(by.css('p')).get(0);
|
||||
const firstInput = element.all(by.css('input')).get(0);
|
||||
|
||||
expect(firstLabel.getText()).toEqual('Current item name: Teapot');
|
||||
firstInput.sendKeys('abc');
|
||||
expect(firstLabel.getText()).toEqual('Current item name: Teapotabc');
|
||||
expect(await firstLabel.getText()).toEqual('Current item name: Teapot');
|
||||
await firstInput.sendKeys('abc');
|
||||
expect(await firstLabel.getText()).toEqual('Current item name: Teapotabc');
|
||||
});
|
||||
|
||||
|
||||
it('should modify sentence when modified checkbox checked', () => {
|
||||
it('should modify sentence when modified checkbox checked', async () => {
|
||||
const modifiedChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(1);
|
||||
const modifiedSentence = element.all(by.css('div')).get(1);
|
||||
|
||||
modifiedChkbxLabel.click();
|
||||
expect(modifiedSentence.getText()).toContain('modified');
|
||||
await modifiedChkbxLabel.click();
|
||||
expect(await modifiedSentence.getText()).toContain('modified');
|
||||
});
|
||||
|
||||
it('should modify sentence when normal checkbox checked', () => {
|
||||
it('should modify sentence when normal checkbox checked', async () => {
|
||||
const normalChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(4);
|
||||
const normalSentence = element.all(by.css('div')).get(7);
|
||||
|
||||
normalChkbxLabel.click();
|
||||
expect(normalSentence.getText()).toContain('normal weight and, extra large');
|
||||
await normalChkbxLabel.click();
|
||||
expect(await normalSentence.getText()).toContain('normal weight and, extra large');
|
||||
});
|
||||
|
||||
it('should toggle app-item-detail', () => {
|
||||
it('should toggle app-item-detail', async () => {
|
||||
const toggleButton = element.all(by.css('button')).get(3);
|
||||
const toggledDiv = element.all(by.css('app-item-detail')).get(0);
|
||||
|
||||
toggleButton.click();
|
||||
expect(toggledDiv.isDisplayed()).toBe(true);
|
||||
await toggleButton.click();
|
||||
expect(await toggledDiv.isDisplayed()).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide app-item-detail', () => {
|
||||
it('should hide app-item-detail', async () => {
|
||||
const hiddenMessage = element.all(by.css('p')).get(11);
|
||||
const hiddenDiv = element.all(by.css('app-item-detail')).get(2);
|
||||
|
||||
expect(hiddenMessage.getText()).toContain('in the DOM');
|
||||
expect(hiddenDiv.isDisplayed()).toBe(true);
|
||||
expect(await hiddenMessage.getText()).toContain('in the DOM');
|
||||
expect(await hiddenDiv.isDisplayed()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have 10 lists each containing the string Teapot', () => {
|
||||
it('should have 10 lists each containing the string Teapot', async () => {
|
||||
const listDiv = element.all(by.cssContainingText('.box', 'Teapot'));
|
||||
expect(listDiv.count()).toBe(10);
|
||||
expect(await listDiv.count()).toBe(10);
|
||||
});
|
||||
|
||||
it('should switch case', () => {
|
||||
it('should switch case', async () => {
|
||||
const tvRadioButton = element.all(by.css('input[type="radio"]')).get(3);
|
||||
const tvDiv = element(by.css('app-lost-item'));
|
||||
|
||||
const fishbowlRadioButton = element.all(by.css('input[type="radio"]')).get(4);
|
||||
const fishbowlDiv = element(by.css('app-unknown-item'));
|
||||
|
||||
tvRadioButton.click();
|
||||
expect(tvDiv.getText()).toContain('Television');
|
||||
fishbowlRadioButton.click();
|
||||
expect(fishbowlDiv.getText()).toContain('mysterious');
|
||||
await tvRadioButton.click();
|
||||
expect(await tvDiv.getText()).toContain('Television');
|
||||
await fishbowlRadioButton.click();
|
||||
expect(await fishbowlDiv.getText()).toContain('mysterious');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Built Template Functions Example', () => {
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should have title Built-in Template Functions', () => {
|
||||
it('should have title Built-in Template Functions', async () => {
|
||||
const title = element.all(by.css('h1')).get(0);
|
||||
expect(title.getText()).toEqual('Built-in Template Functions');
|
||||
expect(await title.getText()).toEqual('Built-in Template Functions');
|
||||
});
|
||||
|
||||
it('should display $any( ) in h2', () => {
|
||||
it('should display $any( ) in h2', async () => {
|
||||
const header = element(by.css('h2'));
|
||||
expect(header.getText()).toContain('$any( )');
|
||||
expect(await header.getText()).toContain('$any( )');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -10,13 +10,13 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
const heroNames = ['Dr IQ', 'Magneta', 'Bombasto'];
|
||||
const masterName = 'Master';
|
||||
|
||||
it('should pass properties to children properly', () => {
|
||||
it('should pass properties to children properly', async () => {
|
||||
const parent = element(by.tagName('app-hero-parent'));
|
||||
const heroes = parent.all(by.tagName('app-hero-child'));
|
||||
|
||||
for (let i = 0; i < heroNames.length; i++) {
|
||||
const childTitle = heroes.get(i).element(by.tagName('h3')).getText();
|
||||
const childDetail = heroes.get(i).element(by.tagName('p')).getText();
|
||||
const childTitle = await heroes.get(i).element(by.tagName('h3')).getText();
|
||||
const childDetail = await heroes.get(i).element(by.tagName('p')).getText();
|
||||
expect(childTitle).toEqual(heroNames[i] + ' says:');
|
||||
expect(childDetail).toContain(masterName);
|
||||
}
|
||||
@@ -28,23 +28,23 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
describe('Parent-to-child communication with setter', () => {
|
||||
// #docregion parent-to-child-setter
|
||||
// ...
|
||||
it('should display trimmed, non-empty names', () => {
|
||||
it('should display trimmed, non-empty names', async () => {
|
||||
const nonEmptyNameIndex = 0;
|
||||
const nonEmptyName = '"Dr IQ"';
|
||||
const parent = element(by.tagName('app-name-parent'));
|
||||
const hero = parent.all(by.tagName('app-name-child')).get(nonEmptyNameIndex);
|
||||
|
||||
const displayName = hero.element(by.tagName('h3')).getText();
|
||||
const displayName = await hero.element(by.tagName('h3')).getText();
|
||||
expect(displayName).toEqual(nonEmptyName);
|
||||
});
|
||||
|
||||
it('should replace empty name with default name', () => {
|
||||
it('should replace empty name with default name', async () => {
|
||||
const emptyNameIndex = 1;
|
||||
const defaultName = '"<no name set>"';
|
||||
const parent = element(by.tagName('app-name-parent'));
|
||||
const hero = parent.all(by.tagName('app-name-child')).get(emptyNameIndex);
|
||||
|
||||
const displayName = hero.element(by.tagName('h3')).getText();
|
||||
const displayName = await hero.element(by.tagName('h3')).getText();
|
||||
expect(displayName).toEqual(defaultName);
|
||||
});
|
||||
// ...
|
||||
@@ -55,15 +55,15 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
// #docregion parent-to-child-onchanges
|
||||
// ...
|
||||
// Test must all execute in this exact order
|
||||
it('should set expected initial values', () => {
|
||||
const actual = getActual();
|
||||
it('should set expected initial values', async () => {
|
||||
const actual = await getActual();
|
||||
|
||||
const initialLabel = 'Version 1.23';
|
||||
const initialLog = 'Initial value of major set to 1, Initial value of minor set to 23';
|
||||
|
||||
expect(actual.label).toBe(initialLabel);
|
||||
expect(actual.count).toBe(1);
|
||||
expect(actual.logs.get(0).getText()).toBe(initialLog);
|
||||
expect(await actual.logs.get(0).getText()).toBe(initialLog);
|
||||
});
|
||||
|
||||
it('should set expected values after clicking \'Minor\' twice', async () => {
|
||||
@@ -73,14 +73,14 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
await newMinorButton.click();
|
||||
await newMinorButton.click();
|
||||
|
||||
const actual = getActual();
|
||||
const actual = await getActual();
|
||||
|
||||
const labelAfter2Minor = 'Version 1.25';
|
||||
const logAfter2Minor = 'minor changed from 24 to 25';
|
||||
|
||||
expect(actual.label).toBe(labelAfter2Minor);
|
||||
expect(actual.count).toBe(3);
|
||||
expect(actual.logs.get(2).getText()).toBe(logAfter2Minor);
|
||||
expect(await actual.logs.get(2).getText()).toBe(logAfter2Minor);
|
||||
});
|
||||
|
||||
it('should set expected values after clicking \'Major\' once', async () => {
|
||||
@@ -88,26 +88,26 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
const newMajorButton = repoTag.all(by.tagName('button')).get(1);
|
||||
|
||||
await newMajorButton.click();
|
||||
const actual = getActual();
|
||||
const actual = await getActual();
|
||||
|
||||
const labelAfterMajor = 'Version 2.0';
|
||||
const logAfterMajor = 'major changed from 1 to 2, minor changed from 23 to 0';
|
||||
|
||||
expect(actual.label).toBe(labelAfterMajor);
|
||||
expect(actual.count).toBe(2);
|
||||
expect(actual.logs.get(1).getText()).toBe(logAfterMajor);
|
||||
expect(await actual.logs.get(1).getText()).toBe(logAfterMajor);
|
||||
});
|
||||
|
||||
function getActual() {
|
||||
async function getActual() {
|
||||
const versionTag = element(by.tagName('app-version-child'));
|
||||
const label = versionTag.element(by.tagName('h3')).getText();
|
||||
const label = await versionTag.element(by.tagName('h3')).getText();
|
||||
const ul = versionTag.element((by.tagName('ul')));
|
||||
const logs = ul.all(by.tagName('li'));
|
||||
|
||||
return {
|
||||
label,
|
||||
logs,
|
||||
count: logs.count()
|
||||
count: await logs.count(),
|
||||
};
|
||||
}
|
||||
// ...
|
||||
@@ -117,9 +117,9 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
describe('Child-to-parent communication', () => {
|
||||
// #docregion child-to-parent
|
||||
// ...
|
||||
it('should not emit the event initially', () => {
|
||||
it('should not emit the event initially', async () => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker')).element(by.tagName('h3'));
|
||||
expect(voteLabel.getText()).toBe('Agree: 0, Disagree: 0');
|
||||
expect(await voteLabel.getText()).toBe('Agree: 0, Disagree: 0');
|
||||
});
|
||||
|
||||
it('should process Agree vote', async () => {
|
||||
@@ -129,7 +129,7 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
|
||||
await agreeButton1.click();
|
||||
|
||||
expect(voteLabel.getText()).toBe('Agree: 1, Disagree: 0');
|
||||
expect(await voteLabel.getText()).toBe('Agree: 1, Disagree: 0');
|
||||
});
|
||||
|
||||
it('should process Disagree vote', async () => {
|
||||
@@ -139,7 +139,7 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
|
||||
await agreeButton1.click();
|
||||
|
||||
expect(voteLabel.getText()).toBe('Agree: 0, Disagree: 1');
|
||||
expect(await voteLabel.getText()).toBe('Agree: 0, Disagree: 1');
|
||||
});
|
||||
// ...
|
||||
// #enddocregion child-to-parent
|
||||
@@ -204,8 +204,8 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
|
||||
await announceButton.click();
|
||||
|
||||
expect(history.count()).toBe(1);
|
||||
expect(history.get(0).getText()).toMatch(/Mission.* announced/);
|
||||
expect(await history.count()).toBe(1);
|
||||
expect(await history.get(0).getText()).toMatch(/Mission.* announced/);
|
||||
});
|
||||
|
||||
it('should confirm the mission by Lovell', async () => {
|
||||
@@ -229,8 +229,8 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
await announceButton.click();
|
||||
await confirmButton.click();
|
||||
|
||||
expect(history.count()).toBe(2);
|
||||
expect(history.get(1).getText()).toBe(`${astronaut} confirmed the mission`);
|
||||
expect(await history.count()).toBe(2);
|
||||
expect(await history.get(1).getText()).toBe(`${astronaut} confirmed the mission`);
|
||||
}
|
||||
// ...
|
||||
// #enddocregion bidirectional-service
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* tslint:disable:forin */
|
||||
// #docregion
|
||||
import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
|
||||
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-version-child',
|
||||
@@ -17,7 +17,7 @@ export class VersionChildComponent implements OnChanges {
|
||||
@Input() minor: number;
|
||||
changeLog: string[] = [];
|
||||
|
||||
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const log: string[] = [];
|
||||
for (const propName in changes) {
|
||||
const changedProp = changes[propName];
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Component Overview', () => {
|
||||
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should display component overview works ', async () => {
|
||||
expect(await element(by.css('p')).getText()).toEqual('component-overview works!');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
<app-component-overview></app-component-overview>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'component-overview'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('component-overview');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain('component-overview app is running!');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'component-overview';
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ComponentOverviewComponent } from './component-overview/component-overview.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ComponentOverviewComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// #docplaster
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion template
|
||||
@Component({
|
||||
selector: 'app-component-overview',
|
||||
template: '<h1>Hello World!</h1>',
|
||||
})
|
||||
// #enddocregion template
|
||||
|
||||
export class ComponentOverviewComponent {
|
||||
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// #docplaster
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion templatebacktick
|
||||
@Component({
|
||||
selector: 'app-component-overview',
|
||||
template: `<h1>Hello World!</h1>
|
||||
<p>This template definition spans
|
||||
multiple lines.</p>`
|
||||
})
|
||||
// #enddocregion templatebacktick
|
||||
|
||||
export class ComponentOverviewComponent {
|
||||
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// #docplaster
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion styles
|
||||
@Component({
|
||||
selector: 'app-component-overview',
|
||||
template: '<h1>Hello World!</h1>',
|
||||
styles: ['h1 { font-weight: normal; }']
|
||||
})
|
||||
// #enddocregion styles
|
||||
|
||||
export class ComponentOverviewComponent {
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
<p>component-overview works!</p>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ComponentOverviewComponent } from './component-overview.component';
|
||||
|
||||
describe('ComponentOverviewComponent', () => {
|
||||
let component: ComponentOverviewComponent;
|
||||
let fixture: ComponentFixture<ComponentOverviewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ComponentOverviewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ComponentOverviewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// #docplaster
|
||||
// #docregion import
|
||||
import { Component } from '@angular/core';
|
||||
// #enddocregion import
|
||||
|
||||
// #docregion decorator, decorator-skeleton, selector, templateUrl
|
||||
@Component({
|
||||
// #enddocregion decorator-skeleton
|
||||
selector: 'app-component-overview',
|
||||
// #enddocregion selector
|
||||
templateUrl: './component-overview.component.html',
|
||||
// #enddocregion templateUrl
|
||||
styleUrls: ['./component-overview.component.css']
|
||||
// #docregion decorator-skeleton, selector, templateUrl
|
||||
})
|
||||
// #enddocregion decorator, decorator-skeleton, selector, templateUrl
|
||||
|
||||
// #docregion class
|
||||
export class ComponentOverviewComponent {
|
||||
|
||||
}
|
||||
// #enddocregion class
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ComponentOverview</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"description": "Component Overview",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags":["overview", "component"]
|
||||
}
|
||||
@@ -2,67 +2,65 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Component Style Tests', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('scopes component styles to component view', () => {
|
||||
it('scopes component styles to component view', async () => {
|
||||
const componentH1 = element(by.css('app-root > h1'));
|
||||
const externalH1 = element(by.css('body > h1'));
|
||||
|
||||
// Note: sometimes webdriver returns the fontWeight as "normal",
|
||||
// other times as "400", both of which are equal in CSS terms.
|
||||
expect(componentH1.getCssValue('fontWeight')).toMatch(/normal|400/);
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toMatch(/normal|400/);
|
||||
expect(await componentH1.getCssValue('fontWeight')).toMatch(/normal|400/);
|
||||
expect(await externalH1.getCssValue('fontWeight')).not.toMatch(/normal|400/);
|
||||
});
|
||||
|
||||
|
||||
it('allows styling :host element', () => {
|
||||
it('allows styling :host element', async () => {
|
||||
const host = element(by.css('app-hero-details'));
|
||||
|
||||
expect(host.getCssValue('borderWidth')).toEqual('1px');
|
||||
expect(await host.getCssValue('borderWidth')).toEqual('1px');
|
||||
});
|
||||
|
||||
it('supports :host() in function form', () => {
|
||||
it('supports :host() in function form', async () => {
|
||||
const host = element(by.css('app-hero-details'));
|
||||
|
||||
host.element(by.buttonText('Activate')).click();
|
||||
expect(host.getCssValue('borderWidth')).toEqual('3px');
|
||||
await host.element(by.buttonText('Activate')).click();
|
||||
expect(await host.getCssValue('borderWidth')).toEqual('3px');
|
||||
});
|
||||
|
||||
it('allows conditional :host-context() styling', () => {
|
||||
it('allows conditional :host-context() styling', async () => {
|
||||
const h2 = element(by.css('app-hero-details h2'));
|
||||
|
||||
expect(h2.getCssValue('backgroundColor')).toEqual('rgba(238, 238, 255, 1)'); // #eeeeff
|
||||
expect(await h2.getCssValue('backgroundColor')).toEqual('rgba(238, 238, 255, 1)'); // #eeeeff
|
||||
});
|
||||
|
||||
it('styles both view and content children with /deep/', () => {
|
||||
it('styles both view and content children with /deep/', async () => {
|
||||
const viewH3 = element(by.css('app-hero-team h3'));
|
||||
const contentH3 = element(by.css('app-hero-controls h3'));
|
||||
|
||||
expect(viewH3.getCssValue('fontStyle')).toEqual('italic');
|
||||
expect(contentH3.getCssValue('fontStyle')).toEqual('italic');
|
||||
expect(await viewH3.getCssValue('fontStyle')).toEqual('italic');
|
||||
expect(await contentH3.getCssValue('fontStyle')).toEqual('italic');
|
||||
});
|
||||
|
||||
it('includes styles loaded with CSS @import', () => {
|
||||
it('includes styles loaded with CSS @import', async () => {
|
||||
const host = element(by.css('app-hero-details'));
|
||||
|
||||
expect(host.getCssValue('padding')).toEqual('10px');
|
||||
expect(await host.getCssValue('padding')).toEqual('10px');
|
||||
});
|
||||
|
||||
it('processes template inline styles', () => {
|
||||
it('processes template inline styles', async () => {
|
||||
const button = element(by.css('app-hero-controls button'));
|
||||
const externalButton = element(by.css('body > button'));
|
||||
expect(button.getCssValue('backgroundColor')).toEqual('rgba(255, 255, 255, 1)'); // #ffffff
|
||||
expect(externalButton.getCssValue('backgroundColor')).not.toEqual('rgba(255, 255, 255, 1)');
|
||||
expect(await button.getCssValue('backgroundColor')).toEqual('rgba(255, 255, 255, 1)'); // #ffffff
|
||||
expect(await externalButton.getCssValue('backgroundColor')).not.toEqual('rgba(255, 255, 255, 1)');
|
||||
});
|
||||
|
||||
it('processes template <link>s', () => {
|
||||
it('processes template <link>s', async () => {
|
||||
const li = element(by.css('app-hero-team li:first-child'));
|
||||
const externalLi = element(by.css('body > ul li'));
|
||||
|
||||
expect(li.getCssValue('listStyleType')).toEqual('square');
|
||||
expect(externalLi.getCssValue('listStyleType')).not.toEqual('square');
|
||||
expect(await li.getCssValue('listStyleType')).toEqual('square');
|
||||
expect(await externalLi.getCssValue('listStyleType')).not.toEqual('square');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/* #enddocregion hostcontext */
|
||||
|
||||
/* #docregion deep */
|
||||
:host /deep/ h3 {
|
||||
:host ::ng-deep h3 {
|
||||
font-style: italic;
|
||||
}
|
||||
/* #enddocregion deep */
|
||||
|
||||
@@ -2,81 +2,72 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Dependency Injection Cookbook', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should render Logged in User example', async () => {
|
||||
const loggedInUser = element(by.cssContainingText('h3', 'Logged in user'));
|
||||
expect(await loggedInUser.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render Logged in User example', () => {
|
||||
const loggedInUser = element.all(by.xpath('//h3[text()="Logged in user"]')).get(0);
|
||||
expect(loggedInUser).toBeDefined();
|
||||
it('"Bombasto" should be the logged in user', async () => {
|
||||
const loggedInUser = element(by.cssContainingText('div', 'Name: Bombasto'));
|
||||
expect(await loggedInUser.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('"Bombasto" should be the logged in user', () => {
|
||||
const loggedInUser = element.all(by.xpath('//div[text()="Name: Bombasto"]')).get(0);
|
||||
expect(loggedInUser).toBeDefined();
|
||||
it('should render sorted heroes', async () => {
|
||||
const sortedHeroes = element(by.cssContainingText('h3', 'Sorted Heroes'));
|
||||
expect(await sortedHeroes.isPresent()).toBe(true);
|
||||
|
||||
const sortedHeroElems = element.all(by.css('app-sorted-heroes div'));
|
||||
const sortedHeroNames = await sortedHeroElems.map(elem => elem.getText());
|
||||
expect(sortedHeroNames).toEqual(['Dr Nice', 'Magma', 'RubberMan']);
|
||||
});
|
||||
|
||||
it('should render sorted heroes', () => {
|
||||
const sortedHeroes = element.all(by.xpath('//h3[text()="Sorted Heroes" and position()=1]')).get(0);
|
||||
expect(sortedHeroes).toBeDefined();
|
||||
it('should render Hero of the Month', async () => {
|
||||
const heroOfTheMonth = element(by.cssContainingText('h3', 'Hero of the Month'));
|
||||
expect(await heroOfTheMonth.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('Dr Nice should be in sorted heroes', () => {
|
||||
const sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Dr Nice" and position()=2]')).get(0);
|
||||
expect(sortedHero).toBeDefined();
|
||||
it('should render Hero Bios', async () => {
|
||||
const heroBios = element(by.cssContainingText('h3', 'Hero Bios'));
|
||||
expect(await heroBios.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('RubberMan should be in sorted heroes', () => {
|
||||
const sortedHero = element.all(by.xpath('//sorted-heroes/[text()="RubberMan" and position()=3]')).get(0);
|
||||
expect(sortedHero).toBeDefined();
|
||||
it('should render Magma\'s description in Hero Bios', async () => {
|
||||
const magmaBioElem = element.all(by.css('app-hero-bio')).get(1);
|
||||
const magmaNameElem = magmaBioElem.element(by.css('h4'));
|
||||
const magmaDescElem = magmaBioElem.element(by.css('textarea'));
|
||||
|
||||
expect(await magmaNameElem.getText()).toBe('Magma');
|
||||
expect(await magmaDescElem.getAttribute('value')).toBe('Hero of all trades');
|
||||
});
|
||||
|
||||
it('Magma should be in sorted heroes', () => {
|
||||
const sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Magma"]')).get(0);
|
||||
expect(sortedHero).toBeDefined();
|
||||
it('should render Magma\'s phone in Hero Bios and Contacts', async () => {
|
||||
const magmaPhone = element(by.cssContainingText('div', 'Phone #: 555-555-5555'));
|
||||
expect(await magmaPhone.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render Hero of the Month', () => {
|
||||
const heroOfTheMonth = element.all(by.xpath('//h3[text()="Hero of the month"]')).get(0);
|
||||
expect(heroOfTheMonth).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render Hero Bios', () => {
|
||||
const heroBios = element.all(by.xpath('//h3[text()="Hero Bios"]')).get(0);
|
||||
expect(heroBios).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render Magma\'s description in Hero Bios', () => {
|
||||
const magmaText = element.all(by.xpath('//textarea[text()="Hero of all trades"]')).get(0);
|
||||
expect(magmaText).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render Magma\'s phone in Hero Bios and Contacts', () => {
|
||||
const magmaPhone = element.all(by.xpath('//div[text()="Phone #: 555-555-5555"]')).get(0);
|
||||
expect(magmaPhone).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render Hero-of-the-Month runner-ups', () => {
|
||||
const runnersUp = element(by.id('rups1')).getText();
|
||||
it('should render Hero-of-the-Month runner-ups', async () => {
|
||||
const runnersUp = await element(by.id('rups1')).getText();
|
||||
expect(runnersUp).toContain('RubberMan, Dr Nice');
|
||||
});
|
||||
|
||||
it('should render DateLogger log entry in Hero-of-the-Month', () => {
|
||||
const logs = element.all(by.id('logs')).get(0).getText();
|
||||
it('should render DateLogger log entry in Hero-of-the-Month', async () => {
|
||||
const logs = await element.all(by.id('logs')).get(0).getText();
|
||||
expect(logs).toContain('INFO: starting up at');
|
||||
});
|
||||
|
||||
it('should highlight Hero Bios and Contacts container when mouseover', () => {
|
||||
it('should highlight Hero Bios and Contacts container when mouseover', async () => {
|
||||
const target = element(by.css('div[appHighlight="yellow"]'));
|
||||
const yellow = 'rgba(255, 255, 0, 1)';
|
||||
|
||||
expect(target.getCssValue('background-color')).not.toEqual(yellow);
|
||||
expect(await target.getCssValue('background-color')).not.toEqual(yellow);
|
||||
|
||||
browser.actions().mouseMove(target).perform();
|
||||
await browser.actions().mouseMove(target).perform();
|
||||
|
||||
// Wait for up to 2s for the background color to be updated,
|
||||
// to account for slow environments (e.g. CI).
|
||||
browser.wait(() => target.getCssValue('background-color').then(c => c === yellow), 2000);
|
||||
await browser.wait(async () => await target.getCssValue('background-color') === yellow, 2000);
|
||||
});
|
||||
|
||||
describe('in Parent Finder', () => {
|
||||
@@ -85,20 +76,20 @@ describe('Dependency Injection Cookbook', () => {
|
||||
const carol1 = element(by.css('alex carol p'));
|
||||
const carol2 = element(by.css('barry carol p'));
|
||||
|
||||
it('"Cathy" should find "Alex" via the component class', () => {
|
||||
expect(cathy1.getText()).toContain('Found Alex via the component');
|
||||
it('"Cathy" should find "Alex" via the component class', async () => {
|
||||
expect(await cathy1.getText()).toContain('Found Alex via the component');
|
||||
});
|
||||
|
||||
it('"Craig" should not find "Alex" via the base class', () => {
|
||||
expect(craig1.getText()).toContain('Did not find Alex via the base');
|
||||
it('"Craig" should not find "Alex" via the base class', async () => {
|
||||
expect(await craig1.getText()).toContain('Did not find Alex via the base');
|
||||
});
|
||||
|
||||
it('"Carol" within "Alex" should have "Alex" parent', () => {
|
||||
expect(carol1.getText()).toContain('Alex');
|
||||
it('"Carol" within "Alex" should have "Alex" parent', async () => {
|
||||
expect(await carol1.getText()).toContain('Alex');
|
||||
});
|
||||
|
||||
it('"Carol" within "Barry" should have "Barry" parent', () => {
|
||||
expect(carol2.getText()).toContain('Barry');
|
||||
it('"Carol" within "Barry" should have "Barry" parent', async () => {
|
||||
expect(await carol2.getText()).toContain('Barry');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -18,5 +18,5 @@ export abstract class MinimalLogger {
|
||||
// #enddocregion minimal-logger-transpiled
|
||||
*/
|
||||
|
||||
// See http://stackoverflow.com/questions/43154832/unexpected-token-export-in-angular-app-with-systemjs-and-typescript/
|
||||
// See https://stackoverflow.com/questions/43154832/unexpected-token-export-in-angular-app-with-systemjs-and-typescript/
|
||||
export const _ = 0;
|
||||
|
||||
@@ -16,7 +16,7 @@ export class BrowserStorageService {
|
||||
constructor(@Inject(BROWSER_STORAGE) public storage: Storage) {}
|
||||
|
||||
get(key: string) {
|
||||
this.storage.getItem(key);
|
||||
return this.storage.getItem(key);
|
||||
}
|
||||
|
||||
set(key: string, value: string) {
|
||||
|
||||
@@ -5,193 +5,185 @@ describe('Dependency Injection Tests', () => {
|
||||
let expectedMsg: string;
|
||||
let expectedMsgRx: RegExp;
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
describe('Cars:', () => {
|
||||
|
||||
it('DI car displays as expected', () => {
|
||||
it('DI car displays as expected', async () => {
|
||||
expectedMsg = 'DI car with 4 cylinders and Flintstone tires.';
|
||||
expect(element(by.css('#di')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#di')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('No DI car displays as expected', () => {
|
||||
it('No DI car displays as expected', async () => {
|
||||
expectedMsg = 'No DI car with 4 cylinders and Flintstone tires.';
|
||||
expect(element(by.css('#nodi')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#nodi')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('Injector car displays as expected', () => {
|
||||
it('Injector car displays as expected', async () => {
|
||||
expectedMsg = 'Injector car with 4 cylinders and Flintstone tires.';
|
||||
expect(element(by.css('#injector')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#injector')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('Factory car displays as expected', () => {
|
||||
it('Factory car displays as expected', async () => {
|
||||
expectedMsg = 'Factory car with 4 cylinders and Flintstone tires.';
|
||||
expect(element(by.css('#factory')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#factory')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('Simple car displays as expected', () => {
|
||||
it('Simple car displays as expected', async () => {
|
||||
expectedMsg = 'Simple car with 4 cylinders and Flintstone tires.';
|
||||
expect(element(by.css('#simple')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#simple')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('Super car displays as expected', () => {
|
||||
it('Super car displays as expected', async () => {
|
||||
expectedMsg = 'Super car with 12 cylinders and Flintstone tires.';
|
||||
expect(element(by.css('#super')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#super')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('Test car displays as expected', () => {
|
||||
it('Test car displays as expected', async () => {
|
||||
expectedMsg = 'Test car with 8 cylinders and YokoGoodStone tires.';
|
||||
expect(element(by.css('#test')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#test')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Other Injections:', () => {
|
||||
it('DI car displays as expected', () => {
|
||||
it('DI car displays as expected', async () => {
|
||||
expectedMsg = 'DI car with 4 cylinders and Flintstone tires.';
|
||||
expect(element(by.css('#car')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#car')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('Hero displays as expected', () => {
|
||||
it('Hero displays as expected', async () => {
|
||||
expectedMsg = 'Dr Nice';
|
||||
expect(element(by.css('#hero')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#hero')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('Optional injection displays as expected', () => {
|
||||
it('Optional injection displays as expected', async () => {
|
||||
expectedMsg = 'R.O.U.S.\'s? I don\'t think they exist!';
|
||||
expect(element(by.css('#rodent')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#rodent')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tests:', () => {
|
||||
|
||||
it('Tests display as expected', () => {
|
||||
it('Tests display as expected', async () => {
|
||||
expectedMsgRx = /Tests passed/;
|
||||
expect(element(by.css('#tests')).getText()).toMatch(expectedMsgRx);
|
||||
expect(await element(by.css('#tests')).getText()).toMatch(expectedMsgRx);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Provider variations:', () => {
|
||||
|
||||
it('P1 (class) displays as expected', () => {
|
||||
it('P1 (class) displays as expected', async () => {
|
||||
expectedMsg = 'Hello from logger provided with Logger class';
|
||||
expect(element(by.css('#p1')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p1')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P3 (provide) displays as expected', () => {
|
||||
it('P3 (provide) displays as expected', async () => {
|
||||
expectedMsg = 'Hello from logger provided with useClass:Logger';
|
||||
expect(element(by.css('#p3')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p3')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P4 (useClass:BetterLogger) displays as expected', () => {
|
||||
it('P4 (useClass:BetterLogger) displays as expected', async () => {
|
||||
expectedMsg = 'Hello from logger provided with useClass:BetterLogger';
|
||||
expect(element(by.css('#p4')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p4')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P5 (useClass:EvenBetterLogger - dependency) displays as expected', () => {
|
||||
it('P5 (useClass:EvenBetterLogger - dependency) displays as expected', async () => {
|
||||
expectedMsg = 'Message to Bob: Hello from EvenBetterlogger';
|
||||
expect(element(by.css('#p5')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p5')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P6a (no alias) displays as expected', () => {
|
||||
it('P6a (no alias) displays as expected', async () => {
|
||||
expectedMsg = 'Hello OldLogger (but we want NewLogger)';
|
||||
expect(element(by.css('#p6a')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p6a')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P6b (alias) displays as expected', () => {
|
||||
it('P6b (alias) displays as expected', async () => {
|
||||
expectedMsg = 'Hello from NewLogger (via aliased OldLogger)';
|
||||
expect(element(by.css('#p6b')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p6b')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P7 (useValue) displays as expected', () => {
|
||||
it('P7 (useValue) displays as expected', async () => {
|
||||
expectedMsg = 'Silent logger says "Shhhhh!". Provided via "useValue"';
|
||||
expect(element(by.css('#p7')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p7')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P8 (useFactory) displays as expected', () => {
|
||||
it('P8 (useFactory) displays as expected', async () => {
|
||||
expectedMsg = 'Hero service injected successfully via heroServiceProvider';
|
||||
expect(element(by.css('#p8')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p8')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P9 (InjectionToken) displays as expected', () => {
|
||||
it('P9 (InjectionToken) displays as expected', async () => {
|
||||
expectedMsg = 'APP_CONFIG Application title is Dependency Injection';
|
||||
expect(element(by.css('#p9')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p9')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
it('P10 (optional dependency) displays as expected', () => {
|
||||
it('P10 (optional dependency) displays as expected', async () => {
|
||||
expectedMsg = 'Optional logger was not available';
|
||||
expect(element(by.css('#p10')).getText()).toEqual(expectedMsg);
|
||||
expect(await element(by.css('#p10')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
});
|
||||
|
||||
describe('User/Heroes:', () => {
|
||||
it('User is Bob - unauthorized', () => {
|
||||
it('User is Bob - unauthorized', async () => {
|
||||
expectedMsgRx = /Bob, is not authorized/;
|
||||
expect(element(by.css('#user')).getText()).toMatch(expectedMsgRx);
|
||||
expect(await element(by.css('#user')).getText()).toMatch(expectedMsgRx);
|
||||
});
|
||||
|
||||
it('should have button', () => {
|
||||
expect(element.all(by.cssContainingText('button', 'Next User'))
|
||||
it('should have button', async () => {
|
||||
expect(await element.all(by.cssContainingText('button', 'Next User'))
|
||||
.get(0).isDisplayed()).toBe(true, '\'Next User\' button should be displayed');
|
||||
});
|
||||
|
||||
it('unauthorized user should have multiple unauthorized heroes', () => {
|
||||
it('unauthorized user should have multiple unauthorized heroes', async () => {
|
||||
const heroes = element.all(by.css('#unauthorized app-hero-list div'));
|
||||
expect(heroes.count()).toBeGreaterThan(0);
|
||||
expect(await heroes.count()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('unauthorized user should have no secret heroes', () => {
|
||||
it('unauthorized user should have no secret heroes', async () => {
|
||||
const heroes = element.all(by.css('#unauthorized app-hero-list div'));
|
||||
expect(heroes.count()).toBeGreaterThan(0);
|
||||
expect(await heroes.count()).toBeGreaterThan(0);
|
||||
|
||||
const filteredHeroes = heroes.filter((elem: ElementFinder, index: number) => {
|
||||
return elem.getText().then((text: string) => /secret/.test(text));
|
||||
});
|
||||
|
||||
expect(filteredHeroes.count()).toEqual(0);
|
||||
const filteredHeroes = heroes.filter(async elem => /secret/.test(await elem.getText()));
|
||||
expect(await filteredHeroes.count()).toEqual(0);
|
||||
});
|
||||
|
||||
it('unauthorized user should have no authorized heroes listed', () => {
|
||||
expect(element.all(by.css('#authorized app-hero-list div')).count()).toEqual(0);
|
||||
it('unauthorized user should have no authorized heroes listed', async () => {
|
||||
expect(await element.all(by.css('#authorized app-hero-list div')).count()).toEqual(0);
|
||||
});
|
||||
|
||||
describe('after button click', () => {
|
||||
|
||||
beforeAll((done: any) => {
|
||||
beforeAll(async () => {
|
||||
const buttonEle = element.all(by.cssContainingText('button', 'Next User')).get(0);
|
||||
buttonEle.click().then(done, done);
|
||||
await buttonEle.click();
|
||||
});
|
||||
|
||||
it('User is Alice - authorized', () => {
|
||||
it('User is Alice - authorized', async () => {
|
||||
expectedMsgRx = /Alice, is authorized/;
|
||||
expect(element(by.css('#user')).getText()).toMatch(expectedMsgRx);
|
||||
expect(await element(by.css('#user')).getText()).toMatch(expectedMsgRx);
|
||||
});
|
||||
|
||||
it('authorized user should have multiple authorized heroes ', () => {
|
||||
it('authorized user should have multiple authorized heroes ', async () => {
|
||||
const heroes = element.all(by.css('#authorized app-hero-list div'));
|
||||
expect(heroes.count()).toBeGreaterThan(0);
|
||||
expect(await heroes.count()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('authorized user should have multiple authorized heroes with tree-shakeable HeroesService', () => {
|
||||
it('authorized user should have multiple authorized heroes with tree-shakeable HeroesService', async () => {
|
||||
const heroes = element.all(by.css('#tspAuthorized app-hero-list div'));
|
||||
expect(heroes.count()).toBeGreaterThan(0);
|
||||
expect(await heroes.count()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('authorized user should have secret heroes', () => {
|
||||
it('authorized user should have secret heroes', async () => {
|
||||
const heroes = element.all(by.css('#authorized app-hero-list div'));
|
||||
expect(heroes.count()).toBeGreaterThan(0);
|
||||
expect(await heroes.count()).toBeGreaterThan(0);
|
||||
|
||||
const filteredHeroes = heroes.filter((elem: ElementFinder, index: number) => {
|
||||
return elem.getText().then((text: string) => /secret/.test(text));
|
||||
});
|
||||
|
||||
expect(filteredHeroes.count()).toBeGreaterThan(0);
|
||||
const filteredHeroes = heroes.filter(async elem => /secret/.test(await elem.getText()));
|
||||
expect(await filteredHeroes.count()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('authorized user should have no unauthorized heroes listed', () => {
|
||||
expect(element.all(by.css('#unauthorized app-hero-list div')).count()).toEqual(0);
|
||||
it('authorized user should have no unauthorized heroes listed', async () => {
|
||||
expect(await element.all(by.css('#unauthorized app-hero-list div')).count()).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Injectable({
|
||||
// we declare that this service should be created
|
||||
// declares that this service should be created
|
||||
// by the root application injector.
|
||||
providedIn: 'root',
|
||||
})
|
||||
|
||||
@@ -205,7 +205,7 @@ export class Provider8Component {
|
||||
template,
|
||||
/*
|
||||
// #docregion providers-9-interface
|
||||
// FAIL! Can't use interface as provider token
|
||||
// Can't use interface as provider token
|
||||
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
|
||||
// #enddocregion providers-9-interface
|
||||
*/
|
||||
@@ -217,7 +217,7 @@ export class Provider9Component implements OnInit {
|
||||
log: string;
|
||||
/*
|
||||
// #docregion provider-9-ctor-interface
|
||||
// FAIL! Can't inject using the interface as the parameter type
|
||||
// Can't inject using the interface as the parameter type
|
||||
constructor(private config: AppConfig){ }
|
||||
// #enddocregion provider-9-ctor-interface
|
||||
*/
|
||||
|
||||
@@ -4,24 +4,22 @@ describe('Displaying Data Tests', () => {
|
||||
const title = 'Tour of Heroes';
|
||||
const defaultHero = 'Windstorm';
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it(`should display correct title: ${title}`, async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual(title);
|
||||
});
|
||||
|
||||
it('should display correct title: ' + title, () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual(title);
|
||||
it(`should have correct default hero: ${defaultHero}`, async () => {
|
||||
expect(await element(by.css('h2')).getText()).toContain(defaultHero);
|
||||
});
|
||||
|
||||
it('should have correct default hero: ' + defaultHero, () => {
|
||||
expect(element(by.css('h2')).getText()).toContain(defaultHero);
|
||||
});
|
||||
|
||||
it('should have heroes', () => {
|
||||
it('should have heroes', async () => {
|
||||
const heroEls = element.all(by.css('li'));
|
||||
expect(heroEls.count()).not.toBe(0, 'should have heroes');
|
||||
expect(await heroEls.count()).not.toBe(0, 'should have heroes');
|
||||
});
|
||||
|
||||
it('should display "there are many heroes!"', () => {
|
||||
expect(element(by.css('ul ~ p')).getText()).toContain('There are many heroes!');
|
||||
it('should display "there are many heroes!"', async () => {
|
||||
expect(await element(by.css('ul ~ p')).getText()).toContain('There are many heroes!');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,11 +3,9 @@ import { browser, element, by } from 'protractor';
|
||||
describe('Docs Style Guide', () => {
|
||||
const title = 'Authors Style Guide Sample';
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should display correct title: ' + title, () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual(title);
|
||||
it(`should display correct title: ${title}`, async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual(title);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
/* tslint:disable:quotemark */
|
||||
describe('Dynamic Component Loader', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
// The tests trigger periodic asynchronous operations (via `setInterval()`), which will prevent
|
||||
// the app from stabilizing. See https://angular.io/api/core/ApplicationRef#is-stable-examples
|
||||
// for more details.
|
||||
// To allow the tests to complete, we will disable automatically waiting for the Angular app to
|
||||
// stabilize.
|
||||
beforeAll(() => browser.waitForAngularEnabled(false));
|
||||
afterAll(() => browser.waitForAngularEnabled(true));
|
||||
|
||||
it('should load ad banner', () => {
|
||||
const headline = element(by.xpath("//h4[text()='Featured Hero Profile']"));
|
||||
const name = element(by.xpath("//h3[text()='Bombasto']"));
|
||||
const bio = element(by.xpath("//p[text()='Brave as they come']"));
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
expect(name).toBeDefined();
|
||||
expect(headline).toBeDefined();
|
||||
expect(bio).toBeDefined();
|
||||
it('should load ad banner', async () => {
|
||||
const headline = element(by.cssContainingText('h3', 'Featured Hero Profile'));
|
||||
const name = element(by.cssContainingText('h4', 'Bombasto'));
|
||||
const bio = element(by.cssContainingText('p', 'Brave as they come'));
|
||||
|
||||
expect(await headline.isPresent()).toBe(true);
|
||||
expect(await name.isPresent()).toBe(true);
|
||||
expect(await bio.isPresent()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,25 +3,20 @@ import { browser, element, by } from 'protractor';
|
||||
/* tslint:disable:quotemark */
|
||||
describe('Dynamic Form', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should submit form', () => {
|
||||
it('should submit form', async () => {
|
||||
const firstNameElement = element.all(by.css('input[id=firstName]')).get(0);
|
||||
expect(firstNameElement.getAttribute('value')).toEqual('Bombasto');
|
||||
expect(await firstNameElement.getAttribute('value')).toEqual('Bombasto');
|
||||
|
||||
const emailElement = element.all(by.css('input[id=emailAddress]')).get(0);
|
||||
const email = 'test@test.com';
|
||||
emailElement.sendKeys(email);
|
||||
expect(emailElement.getAttribute('value')).toEqual(email);
|
||||
await emailElement.sendKeys(email);
|
||||
expect(await emailElement.getAttribute('value')).toEqual(email);
|
||||
|
||||
element(by.css('select option[value="solid"]')).click();
|
||||
|
||||
const saveButton = element.all(by.css('button')).get(0);
|
||||
saveButton.click().then(() => {
|
||||
expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true);
|
||||
});
|
||||
await element(by.css('select option[value="solid"]')).click();
|
||||
await element.all(by.css('button')).get(0).click();
|
||||
expect(await element(by.cssContainingText('strong', 'Saved the following values')).isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -6,14 +6,14 @@ describe('Elements', () => {
|
||||
const popupButtons = element.all(by.css('button'));
|
||||
|
||||
// Helpers
|
||||
const click = (elem: ElementFinder) => {
|
||||
const click = async (elem: ElementFinder) => {
|
||||
// Waiting for the element to be clickable, makes the tests less flaky.
|
||||
browser.wait(EC.elementToBeClickable(elem), 5000);
|
||||
elem.click();
|
||||
await browser.wait(EC.elementToBeClickable(elem), 5000);
|
||||
await elem.click();
|
||||
};
|
||||
const waitForText = (elem: ElementFinder) => {
|
||||
const waitForText = async (elem: ElementFinder) => {
|
||||
// Waiting for the element to have some text, makes the tests less flaky.
|
||||
browser.wait(async () => /\S/.test(await elem.getText()), 5000);
|
||||
await browser.wait(async () => /\S/.test(await elem.getText()), 5000);
|
||||
};
|
||||
|
||||
beforeEach(() => browser.get(''));
|
||||
@@ -23,29 +23,29 @@ describe('Elements', () => {
|
||||
const popupComponent = element(by.css('popup-component'));
|
||||
const closeButton = popupComponent.element(by.css('button'));
|
||||
|
||||
it('should be displayed on button click', () => {
|
||||
expect(popupComponent.isPresent()).toBe(false);
|
||||
it('should be displayed on button click', async () => {
|
||||
expect(await popupComponent.isPresent()).toBe(false);
|
||||
|
||||
click(popupComponentButton);
|
||||
expect(popupComponent.isPresent()).toBe(true);
|
||||
await click(popupComponentButton);
|
||||
expect(await popupComponent.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should display the specified message', () => {
|
||||
messageInput.clear();
|
||||
messageInput.sendKeys('Angular rocks!');
|
||||
it('should display the specified message', async () => {
|
||||
await messageInput.clear();
|
||||
await messageInput.sendKeys('Angular rocks!');
|
||||
|
||||
click(popupComponentButton);
|
||||
waitForText(popupComponent);
|
||||
await click(popupComponentButton);
|
||||
await waitForText(popupComponent);
|
||||
|
||||
expect(popupComponent.getText()).toContain('Popup: Angular rocks!');
|
||||
expect(await popupComponent.getText()).toContain('Popup: Angular rocks!');
|
||||
});
|
||||
|
||||
it('should be closed on "close" button click', () => {
|
||||
popupComponentButton.click();
|
||||
expect(popupComponent.isPresent()).toBe(true);
|
||||
it('should be closed on "close" button click', async () => {
|
||||
await click(popupComponentButton);
|
||||
expect(await popupComponent.isPresent()).toBe(true);
|
||||
|
||||
click(closeButton);
|
||||
expect(popupComponent.isPresent()).toBe(false);
|
||||
await click(closeButton);
|
||||
expect(await popupComponent.isPresent()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,29 +54,29 @@ describe('Elements', () => {
|
||||
const popupElement = element(by.css('popup-element'));
|
||||
const closeButton = popupElement.element(by.css('button'));
|
||||
|
||||
it('should be displayed on button click', () => {
|
||||
expect(popupElement.isPresent()).toBe(false);
|
||||
it('should be displayed on button click', async () => {
|
||||
expect(await popupElement.isPresent()).toBe(false);
|
||||
|
||||
click(popupElementButton);
|
||||
expect(popupElement.isPresent()).toBe(true);
|
||||
await click(popupElementButton);
|
||||
expect(await popupElement.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should display the specified message', () => {
|
||||
messageInput.clear();
|
||||
messageInput.sendKeys('Angular rocks!');
|
||||
it('should display the specified message', async () => {
|
||||
await messageInput.clear();
|
||||
await messageInput.sendKeys('Angular rocks!');
|
||||
|
||||
click(popupElementButton);
|
||||
waitForText(popupElement);
|
||||
await click(popupElementButton);
|
||||
await waitForText(popupElement);
|
||||
|
||||
expect(popupElement.getText()).toContain('Popup: Angular rocks!');
|
||||
expect(await popupElement.getText()).toContain('Popup: Angular rocks!');
|
||||
});
|
||||
|
||||
it('should be closed on "close" button click', () => {
|
||||
popupElementButton.click();
|
||||
expect(popupElement.isPresent()).toBe(true);
|
||||
it('should be closed on "close" button click', async () => {
|
||||
await click(popupElementButton);
|
||||
expect(await popupElement.isPresent()).toBe(true);
|
||||
|
||||
click(closeButton);
|
||||
expect(popupElement.isPresent()).toBe(false);
|
||||
await click(closeButton);
|
||||
expect(await popupElement.isPresent()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,9 +2,7 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Event binding example', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
const saveButton = element.all(by.css('button')).get(0);
|
||||
const onSaveButton = element.all(by.css('button')).get(1);
|
||||
@@ -14,55 +12,55 @@ describe('Event binding example', () => {
|
||||
const saveProp = element.all(by.css('button')).get(5);
|
||||
|
||||
|
||||
it('should display Event Binding with Angular', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Event Binding');
|
||||
it('should display Event Binding with Angular', async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual('Event Binding');
|
||||
});
|
||||
|
||||
it('should display 6 buttons', () => {
|
||||
expect(saveButton.getText()).toBe('Save');
|
||||
expect(onSaveButton.getText()).toBe('on-click Save');
|
||||
expect(myClick.getText()).toBe('click with myClick');
|
||||
expect(deleteButton.getText()).toBe('Delete');
|
||||
expect(saveNoProp.getText()).toBe('Save, no propagation');
|
||||
expect(saveProp.getText()).toBe('Save with propagation');
|
||||
it('should display 6 buttons', async () => {
|
||||
expect(await saveButton.getText()).toBe('Save');
|
||||
expect(await onSaveButton.getText()).toBe('on-click Save');
|
||||
expect(await myClick.getText()).toBe('click with myClick');
|
||||
expect(await deleteButton.getText()).toBe('Delete');
|
||||
expect(await saveNoProp.getText()).toBe('Save, no propagation');
|
||||
expect(await saveProp.getText()).toBe('Save with propagation');
|
||||
});
|
||||
|
||||
it('should support user input', () => {
|
||||
it('should support user input', async () => {
|
||||
const input = element(by.css('input'));
|
||||
const bindingResult = element.all(by.css('h4')).get(1);
|
||||
expect(bindingResult.getText()).toEqual('Result: teapot');
|
||||
input.sendKeys('abc');
|
||||
expect(bindingResult.getText()).toEqual('Result: teapotabc');
|
||||
expect(await bindingResult.getText()).toEqual('Result: teapot');
|
||||
await input.sendKeys('abc');
|
||||
expect(await bindingResult.getText()).toEqual('Result: teapotabc');
|
||||
});
|
||||
|
||||
it('should hide the item img', async () => {
|
||||
await deleteButton.click();
|
||||
browser.switchTo().alert().accept();
|
||||
expect(element.all(by.css('img')).get(0).getCssValue('display')).toEqual('none');
|
||||
await browser.switchTo().alert().accept();
|
||||
expect(await element.all(by.css('img')).get(0).getCssValue('display')).toEqual('none');
|
||||
});
|
||||
|
||||
it('should show two alerts', async () => {
|
||||
const parentDiv = element.all(by.css('.parent-div'));
|
||||
const childDiv = element.all(by.css('div > div')).get(1);
|
||||
await parentDiv.click();
|
||||
browser.switchTo().alert().accept();
|
||||
expect(childDiv.getText()).toEqual('Click me too! (child)');
|
||||
await browser.switchTo().alert().accept();
|
||||
expect(await childDiv.getText()).toEqual('Click me too! (child)');
|
||||
await childDiv.click();
|
||||
expect(browser.switchTo().alert().getText()).toEqual('Click me. Event target class is child-div');
|
||||
browser.switchTo().alert().accept();
|
||||
expect(await browser.switchTo().alert().getText()).toEqual('Click me. Event target class is child-div');
|
||||
await browser.switchTo().alert().accept();
|
||||
});
|
||||
|
||||
it('should show 1 alert from Save, no prop, button', async () => {
|
||||
await saveNoProp.click();
|
||||
expect(browser.switchTo().alert().getText()).toEqual('Saved. Event target is Save, no propagation');
|
||||
browser.switchTo().alert().accept();
|
||||
expect(await browser.switchTo().alert().getText()).toEqual('Saved. Event target is Save, no propagation');
|
||||
await browser.switchTo().alert().accept();
|
||||
});
|
||||
|
||||
it('should show 2 alerts from Save w/prop button', async () => {
|
||||
await saveProp.click();
|
||||
expect(browser.switchTo().alert().getText()).toEqual('Saved.');
|
||||
browser.switchTo().alert().accept();
|
||||
expect(browser.switchTo().alert().getText()).toEqual('Saved.');
|
||||
browser.switchTo().alert().accept();
|
||||
expect(await browser.switchTo().alert().getText()).toEqual('Saved.');
|
||||
await browser.switchTo().alert().accept();
|
||||
expect(await browser.switchTo().alert().getText()).toEqual('Saved.');
|
||||
await browser.switchTo().alert().accept();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,11 +7,8 @@ describe('feature-modules App', () => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display message saying app works', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('app works!');
|
||||
it('should display message saying app works', async () => {
|
||||
await page.navigateTo();
|
||||
expect(await page.getTitleText()).toEqual('app works!');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ import { browser, element, by, protractor, ElementFinder, ElementArrayFinder } f
|
||||
// THESE TESTS ARE INCOMPLETE
|
||||
describe('Form Validation Tests', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
describe('Template-driven form', () => {
|
||||
beforeAll(() => {
|
||||
@@ -71,178 +69,178 @@ function getPage(sectionTag: string) {
|
||||
|
||||
function tests(title: string) {
|
||||
|
||||
it('should display correct title', () => {
|
||||
expect(page.title.getText()).toContain(title);
|
||||
it('should display correct title', async () => {
|
||||
expect(await page.title.getText()).toContain(title);
|
||||
});
|
||||
|
||||
it('should not display submitted message before submit', () => {
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('p'))).toBe(false);
|
||||
it('should not display submitted message before submit', async () => {
|
||||
expect(await page.heroSubmitted.isElementPresent(by.css('p'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should have form buttons', () => {
|
||||
expect(page.heroFormButtons.count()).toEqual(2);
|
||||
it('should have form buttons', async () => {
|
||||
expect(await page.heroFormButtons.count()).toEqual(2);
|
||||
});
|
||||
|
||||
it('should have error at start', () => {
|
||||
expectFormIsInvalid();
|
||||
it('should have error at start', async () => {
|
||||
await expectFormIsInvalid();
|
||||
});
|
||||
|
||||
// it('showForm', () => {
|
||||
// page.form.getInnerHtml().then(html => console.log(html));
|
||||
// });
|
||||
|
||||
it('should have disabled submit button', () => {
|
||||
expect(page.heroFormButtons.get(0).isEnabled()).toBe(false);
|
||||
it('should have disabled submit button', async () => {
|
||||
expect(await page.heroFormButtons.get(0).isEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('resetting name to valid name should clear errors', () => {
|
||||
it('resetting name to valid name should clear errors', async () => {
|
||||
const ele = page.nameInput;
|
||||
expect(ele.isPresent()).toBe(true, 'nameInput should exist');
|
||||
ele.clear();
|
||||
ele.sendKeys(testName);
|
||||
expectFormIsValid();
|
||||
expect(await ele.isPresent()).toBe(true, 'nameInput should exist');
|
||||
await ele.clear();
|
||||
await ele.sendKeys(testName);
|
||||
await expectFormIsValid();
|
||||
});
|
||||
|
||||
it('should produce "required" error after clearing name', () => {
|
||||
page.nameInput.clear();
|
||||
// page.alterEgoInput.click(); // to blur ... didn't work
|
||||
page.nameInput.sendKeys('x', protractor.Key.BACK_SPACE); // ugh!
|
||||
expect(page.form.getAttribute('class')).toMatch('ng-invalid');
|
||||
expect(page.errorMessages.get(0).getText()).toContain('required');
|
||||
it('should produce "required" error after clearing name', async () => {
|
||||
await page.nameInput.clear();
|
||||
// await page.alterEgoInput.click(); // to blur ... didn't work
|
||||
await page.nameInput.sendKeys('x', protractor.Key.BACK_SPACE); // ugh!
|
||||
expect(await page.form.getAttribute('class')).toMatch('ng-invalid');
|
||||
expect(await page.errorMessages.get(0).getText()).toContain('required');
|
||||
});
|
||||
|
||||
it('should produce "at least 4 characters" error when name="x"', () => {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('x'); // too short
|
||||
expectFormIsInvalid();
|
||||
expect(page.errorMessages.get(0).getText()).toContain('at least 4 characters');
|
||||
it('should produce "at least 4 characters" error when name="x"', async () => {
|
||||
await page.nameInput.clear();
|
||||
await page.nameInput.sendKeys('x'); // too short
|
||||
await expectFormIsInvalid();
|
||||
expect(await page.errorMessages.get(0).getText()).toContain('at least 4 characters');
|
||||
});
|
||||
|
||||
it('resetting name to valid name again should clear errors', () => {
|
||||
page.nameInput.sendKeys(testName);
|
||||
expectFormIsValid();
|
||||
it('resetting name to valid name again should clear errors', async () => {
|
||||
await page.nameInput.sendKeys(testName);
|
||||
await expectFormIsValid();
|
||||
});
|
||||
|
||||
it('should have enabled submit button', () => {
|
||||
it('should have enabled submit button', async () => {
|
||||
const submitBtn = page.heroFormButtons.get(0);
|
||||
expect(submitBtn.isEnabled()).toBe(true);
|
||||
expect(await submitBtn.isEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide form after submit', () => {
|
||||
page.heroFormButtons.get(0).click();
|
||||
expect(page.heroFormButtons.get(0).isDisplayed()).toBe(false);
|
||||
it('should hide form after submit', async () => {
|
||||
await page.heroFormButtons.get(0).click();
|
||||
expect(await page.heroFormButtons.get(0).isDisplayed()).toBe(false);
|
||||
});
|
||||
|
||||
it('submitted form should be displayed', () => {
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('p'))).toBe(true);
|
||||
it('submitted form should be displayed', async () => {
|
||||
expect(await page.heroSubmitted.isElementPresent(by.css('p'))).toBe(true);
|
||||
});
|
||||
|
||||
it('submitted form should have new hero name', () => {
|
||||
expect(page.heroSubmitted.getText()).toContain(testName);
|
||||
it('submitted form should have new hero name', async () => {
|
||||
expect(await page.heroSubmitted.getText()).toContain(testName);
|
||||
});
|
||||
|
||||
it('clicking edit button should reveal form again', () => {
|
||||
it('clicking edit button should reveal form again', async () => {
|
||||
const newFormBtn = page.heroSubmitted.element(by.css('button'));
|
||||
newFormBtn.click();
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('p')))
|
||||
await newFormBtn.click();
|
||||
expect(await page.heroSubmitted.isElementPresent(by.css('p')))
|
||||
.toBe(false, 'submitted hidden again');
|
||||
expect(page.title.isDisplayed()).toBe(true, 'can see form title');
|
||||
expect(await page.title.isDisplayed()).toBe(true, 'can see form title');
|
||||
});
|
||||
}
|
||||
|
||||
function expectFormIsValid() {
|
||||
expect(page.form.getAttribute('class')).toMatch('ng-valid');
|
||||
async function expectFormIsValid() {
|
||||
expect(await page.form.getAttribute('class')).toMatch('ng-valid');
|
||||
}
|
||||
|
||||
function expectFormIsInvalid() {
|
||||
expect(page.form.getAttribute('class')).toMatch('ng-invalid');
|
||||
async function expectFormIsInvalid() {
|
||||
expect(await page.form.getAttribute('class')).toMatch('ng-invalid');
|
||||
}
|
||||
|
||||
function triggerAlterEgoValidation() {
|
||||
async function triggerAlterEgoValidation() {
|
||||
// alterEgo has updateOn set to 'blur', click outside of the input to trigger the blur event
|
||||
element(by.css('app-root')).click();
|
||||
await element(by.css('app-root')).click();
|
||||
}
|
||||
|
||||
function waitForAlterEgoValidation() {
|
||||
async function waitForAlterEgoValidation() {
|
||||
// alterEgo async validation will be performed in 400ms
|
||||
browser.sleep(400);
|
||||
await browser.sleep(400);
|
||||
}
|
||||
|
||||
function bobTests() {
|
||||
const emsg = 'Name cannot be Bob.';
|
||||
|
||||
it('should produce "no bob" error after setting name to "Bobby"', () => {
|
||||
it('should produce "no bob" error after setting name to "Bobby"', async () => {
|
||||
// Re-populate select element
|
||||
page.powerSelect.click();
|
||||
page.powerOption.click();
|
||||
await page.powerSelect.click();
|
||||
await page.powerOption.click();
|
||||
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('Bobby');
|
||||
expectFormIsInvalid();
|
||||
expect(page.errorMessages.get(0).getText()).toBe(emsg);
|
||||
await page.nameInput.clear();
|
||||
await page.nameInput.sendKeys('Bobby');
|
||||
await expectFormIsInvalid();
|
||||
expect(await page.errorMessages.get(0).getText()).toBe(emsg);
|
||||
});
|
||||
|
||||
it('should be ok again with valid name', () => {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys(testName);
|
||||
expectFormIsValid();
|
||||
it('should be ok again with valid name', async () => {
|
||||
await page.nameInput.clear();
|
||||
await page.nameInput.sendKeys(testName);
|
||||
await expectFormIsValid();
|
||||
});
|
||||
}
|
||||
|
||||
function asyncValidationTests() {
|
||||
const emsg = 'Alter ego is already taken.';
|
||||
|
||||
it(`should produce "${emsg}" error after setting alterEgo to Eric`, () => {
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('Eric');
|
||||
it(`should produce "${emsg}" error after setting alterEgo to Eric`, async () => {
|
||||
await page.alterEgoInput.clear();
|
||||
await page.alterEgoInput.sendKeys('Eric');
|
||||
|
||||
triggerAlterEgoValidation();
|
||||
waitForAlterEgoValidation();
|
||||
await triggerAlterEgoValidation();
|
||||
await waitForAlterEgoValidation();
|
||||
|
||||
expectFormIsInvalid();
|
||||
expect(page.alterEgoErrors.getText()).toBe(emsg);
|
||||
await expectFormIsInvalid();
|
||||
expect(await page.alterEgoErrors.getText()).toBe(emsg);
|
||||
});
|
||||
|
||||
it('should be ok again with different values', () => {
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('John');
|
||||
it('should be ok again with different values', async () => {
|
||||
await page.alterEgoInput.clear();
|
||||
await page.alterEgoInput.sendKeys('John');
|
||||
|
||||
triggerAlterEgoValidation();
|
||||
waitForAlterEgoValidation();
|
||||
await triggerAlterEgoValidation();
|
||||
await waitForAlterEgoValidation();
|
||||
|
||||
expectFormIsValid();
|
||||
expect(page.alterEgoErrors.isPresent()).toBe(false);
|
||||
await expectFormIsValid();
|
||||
expect(await page.alterEgoErrors.isPresent()).toBe(false);
|
||||
});
|
||||
}
|
||||
|
||||
function crossValidationTests() {
|
||||
const emsg = 'Name cannot match alter ego.';
|
||||
|
||||
it(`should produce "${emsg}" error after setting name and alter ego to the same value`, () => {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('Batman');
|
||||
it(`should produce "${emsg}" error after setting name and alter ego to the same value`, async () => {
|
||||
await page.nameInput.clear();
|
||||
await page.nameInput.sendKeys('Batman');
|
||||
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('Batman');
|
||||
await page.alterEgoInput.clear();
|
||||
await page.alterEgoInput.sendKeys('Batman');
|
||||
|
||||
triggerAlterEgoValidation();
|
||||
waitForAlterEgoValidation();
|
||||
await triggerAlterEgoValidation();
|
||||
await waitForAlterEgoValidation();
|
||||
|
||||
expectFormIsInvalid();
|
||||
expect(page.crossValidationErrorMessage.getText()).toBe(emsg);
|
||||
await expectFormIsInvalid();
|
||||
expect(await page.crossValidationErrorMessage.getText()).toBe(emsg);
|
||||
});
|
||||
|
||||
it('should be ok again with different values', () => {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('Batman');
|
||||
it('should be ok again with different values', async () => {
|
||||
await page.nameInput.clear();
|
||||
await page.nameInput.sendKeys('Batman');
|
||||
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('Superman');
|
||||
await page.alterEgoInput.clear();
|
||||
await page.alterEgoInput.sendKeys('Superman');
|
||||
|
||||
triggerAlterEgoValidation();
|
||||
waitForAlterEgoValidation();
|
||||
await triggerAlterEgoValidation();
|
||||
await waitForAlterEgoValidation();
|
||||
|
||||
expectFormIsValid();
|
||||
expect(page.crossValidationErrorMessage.isPresent()).toBe(false);
|
||||
await expectFormIsValid();
|
||||
expect(await page.crossValidationErrorMessage.isPresent()).toBe(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { AppPage } from './app.po';
|
||||
|
||||
describe('Forms Overview Tests', () => {
|
||||
describe('forms-overvoew App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display a title', async () => {
|
||||
await page.navigateTo();
|
||||
expect(await page.getTitleText()).toEqual('Forms Overview');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,61 +2,56 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Forms Tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
it('should display correct title', async () => {
|
||||
expect(await element.all(by.css('h1')).get(0).getText()).toEqual('Hero Form');
|
||||
});
|
||||
|
||||
it('should display correct title', () => {
|
||||
expect(element.all(by.css('h1')).get(0).getText()).toEqual('Hero Form');
|
||||
});
|
||||
|
||||
|
||||
it('should not display message before submit', () => {
|
||||
it('should not display message before submit', async () => {
|
||||
const ele = element(by.css('h2'));
|
||||
expect(ele.isDisplayed()).toBe(false);
|
||||
expect(await ele.isDisplayed()).toBe(false);
|
||||
});
|
||||
|
||||
it('should hide form after submit', () => {
|
||||
it('should hide form after submit', async () => {
|
||||
const ele = element.all(by.css('h1')).get(0);
|
||||
expect(ele.isDisplayed()).toBe(true);
|
||||
expect(await ele.isDisplayed()).toBe(true);
|
||||
|
||||
const b = element.all(by.css('button[type=submit]')).get(0);
|
||||
b.click().then(() => {
|
||||
expect(ele.isDisplayed()).toBe(false);
|
||||
});
|
||||
await b.click();
|
||||
expect(await ele.isDisplayed()).toBe(false);
|
||||
});
|
||||
|
||||
it('should display message after submit', () => {
|
||||
it('should display message after submit', async () => {
|
||||
const b = element.all(by.css('button[type=submit]')).get(0);
|
||||
b.click().then(() => {
|
||||
expect(element(by.css('h2')).getText()).toContain('You submitted the following');
|
||||
});
|
||||
await b.click();
|
||||
expect(await element(by.css('h2')).getText()).toContain('You submitted the following');
|
||||
});
|
||||
|
||||
it('should hide form after submit', () => {
|
||||
it('should hide form after submit', async () => {
|
||||
const alterEgoEle = element.all(by.css('input[name=alterEgo]')).get(0);
|
||||
expect(alterEgoEle.isDisplayed()).toBe(true);
|
||||
expect(await alterEgoEle.isDisplayed()).toBe(true);
|
||||
|
||||
const submitButtonEle = element.all(by.css('button[type=submit]')).get(0);
|
||||
submitButtonEle.click().then(() => {
|
||||
expect(alterEgoEle.isDisplayed()).toBe(false);
|
||||
});
|
||||
await submitButtonEle.click();
|
||||
expect(await alterEgoEle.isDisplayed()).toBe(false);
|
||||
});
|
||||
|
||||
it('should reflect submitted data after submit', () => {
|
||||
const test = 'testing 1 2 3';
|
||||
let newValue: string;
|
||||
it('should reflect submitted data after submit', async () => {
|
||||
const alterEgoEle = element.all(by.css('input[name=alterEgo]')).get(0);
|
||||
alterEgoEle.getAttribute('value').then((value: string) => {
|
||||
alterEgoEle.sendKeys(test);
|
||||
newValue = value + test;
|
||||
expect(alterEgoEle.getAttribute('value')).toEqual(newValue);
|
||||
const b = element.all(by.css('button[type=submit]')).get(0);
|
||||
return b.click();
|
||||
}).then(() => {
|
||||
const alterEgoTextEle = element(by.cssContainingText('div', 'Alter Ego'));
|
||||
expect(alterEgoTextEle.isPresent()).toBe(true, 'cannot locate "Alter Ego" label');
|
||||
const divEle = element(by.cssContainingText('div', newValue));
|
||||
expect(divEle.isPresent()).toBe(true, 'cannot locate div with this text: ' + newValue);
|
||||
});
|
||||
const value = await alterEgoEle.getAttribute('value');
|
||||
const test = 'testing 1 2 3';
|
||||
const newValue = value + test;
|
||||
|
||||
await alterEgoEle.sendKeys(test);
|
||||
expect(await alterEgoEle.getAttribute('value')).toEqual(newValue);
|
||||
|
||||
const b = element.all(by.css('button[type=submit]')).get(0);
|
||||
await b.click();
|
||||
|
||||
const alterEgoTextEle = element(by.cssContainingText('div', 'Alter Ego'));
|
||||
expect(await alterEgoTextEle.isPresent()).toBe(true, 'cannot locate "Alter Ego" label');
|
||||
const divEle = element(by.cssContainingText('div', newValue));
|
||||
expect(await divEle.isPresent()).toBe(true, `cannot locate div with this text: ${newValue}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { browser, element, by, ExpectedConditions as EC, logging, ElementFinder, ElementArrayFinder } from 'protractor';
|
||||
import { browser, element, by, ExpectedConditions as EC, logging } from 'protractor';
|
||||
|
||||
describe('Getting Started', () => {
|
||||
const pageElements = {
|
||||
|
||||
+4
-1
@@ -11,9 +11,10 @@ import { products } from '../products';
|
||||
templateUrl: './product-details.component.html',
|
||||
styleUrls: ['./product-details.component.css']
|
||||
})
|
||||
// #docregion props-methods, add-to-cart
|
||||
// #docregion props-methods, add-to-cart, product-prop
|
||||
export class ProductDetailsComponent implements OnInit {
|
||||
product;
|
||||
// #enddocregion product-prop
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -25,7 +26,9 @@ export class ProductDetailsComponent implements OnInit {
|
||||
this.route.paramMap.subscribe(params => {
|
||||
this.product = products[+params.get('productId')];
|
||||
});
|
||||
// #docregion product-prop
|
||||
}
|
||||
// #enddocregion product-prop
|
||||
// #enddocregion get-product
|
||||
// #docregion props-methods
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ import { browser, by, element } from 'protractor';
|
||||
|
||||
describe('Hierarchical dependency injection', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
describe('Heroes Scenario', () => {
|
||||
const page = {
|
||||
@@ -21,81 +19,68 @@ describe('Hierarchical dependency injection', () => {
|
||||
saveButtonEl: element(by.cssContainingText('app-heroes-list app-hero-tax-return button', 'Save'))
|
||||
};
|
||||
|
||||
it('should list multiple heroes', () => {
|
||||
expect(element.all(by.css('app-heroes-list li')).count()).toBeGreaterThan(1);
|
||||
it('should list multiple heroes', async () => {
|
||||
expect(await element.all(by.css('app-heroes-list li')).count()).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should show no hero tax-returns at the start', () => {
|
||||
expect(element.all(by.css('app-heroes-list li app-hero-tax-return')).count()).toBe(0);
|
||||
it('should show no hero tax-returns at the start', async () => {
|
||||
expect(await element.all(by.css('app-heroes-list li app-hero-tax-return')).count()).toBe(0);
|
||||
});
|
||||
|
||||
it('should open first hero in app-hero-tax-return view after click', () => {
|
||||
page.heroEl.getText()
|
||||
.then(val => {
|
||||
page.heroName = val;
|
||||
})
|
||||
.then(() => page.heroEl.click())
|
||||
.then(() => {
|
||||
expect(page.heroCardEl.isDisplayed()).toBe(true);
|
||||
});
|
||||
it('should open first hero in app-hero-tax-return view after click', async () => {
|
||||
page.heroName = await page.heroEl.getText();
|
||||
await page.heroEl.click();
|
||||
expect(await page.heroCardEl.isDisplayed()).toBe(true);
|
||||
});
|
||||
|
||||
it('hero tax-return should have first hero\'s name', () => {
|
||||
it('hero tax-return should have first hero\'s name', async () => {
|
||||
// Not `page.tax-returnNameInputEl.getAttribute('value')` although later that is essential
|
||||
expect(page.taxReturnNameEl.getText()).toEqual(page.heroName);
|
||||
expect(await page.taxReturnNameEl.getText()).toEqual(page.heroName);
|
||||
});
|
||||
|
||||
it('should be able to cancel change', () => {
|
||||
page.incomeInputEl.clear()
|
||||
.then(() => page.incomeInputEl.sendKeys('777'))
|
||||
.then(() => {
|
||||
expect(page.incomeInputEl.getAttribute('value')).toBe('777', 'income should be 777');
|
||||
return page.cancelButtonEl.click();
|
||||
})
|
||||
.then(() => {
|
||||
expect(page.incomeInputEl.getAttribute('value')).not.toBe('777', 'income should not be 777');
|
||||
});
|
||||
it('should be able to cancel change', async () => {
|
||||
await page.incomeInputEl.clear();
|
||||
await page.incomeInputEl.sendKeys('777');
|
||||
expect(await page.incomeInputEl.getAttribute('value')).toBe('777', 'income should be 777');
|
||||
|
||||
await page.cancelButtonEl.click();
|
||||
expect(await page.incomeInputEl.getAttribute('value')).not.toBe('777', 'income should not be 777');
|
||||
});
|
||||
|
||||
it('should be able to save change', () => {
|
||||
page.incomeInputEl.clear()
|
||||
.then(() => page.incomeInputEl.sendKeys('999'))
|
||||
.then(() => {
|
||||
expect(page.incomeInputEl.getAttribute('value')).toBe('999', 'income should be 999');
|
||||
return page.saveButtonEl.click();
|
||||
})
|
||||
.then(() => {
|
||||
expect(page.incomeInputEl.getAttribute('value')).toBe('999', 'income should still be 999');
|
||||
});
|
||||
it('should be able to save change', async () => {
|
||||
await page.incomeInputEl.clear();
|
||||
await page.incomeInputEl.sendKeys('999');
|
||||
expect(await page.incomeInputEl.getAttribute('value')).toBe('999', 'income should be 999');
|
||||
|
||||
await page.saveButtonEl.click();
|
||||
expect(await page.incomeInputEl.getAttribute('value')).toBe('999', 'income should still be 999');
|
||||
});
|
||||
|
||||
it('should be able to close tax-return', () => {
|
||||
page.saveButtonEl.click()
|
||||
.then(() => {
|
||||
expect(element.all(by.css('app-heroes-list li app-hero-tax-return')).count()).toBe(0);
|
||||
});
|
||||
it('should be able to close tax-return', async () => {
|
||||
await page.saveButtonEl.click();
|
||||
expect(await element.all(by.css('app-heroes-list li app-hero-tax-return')).count()).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Villains Scenario', () => {
|
||||
it('should list multiple villains', () => {
|
||||
expect(element.all(by.css('app-villains-list li')).count()).toBeGreaterThan(1);
|
||||
it('should list multiple villains', async () => {
|
||||
expect(await element.all(by.css('app-villains-list li')).count()).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cars Scenario', () => {
|
||||
|
||||
it('A-component should use expected services', () => {
|
||||
expect(element(by.css('a-car')).getText()).toContain('C1-E1-T1');
|
||||
it('A-component should use expected services', async () => {
|
||||
expect(await element(by.css('a-car')).getText()).toContain('C1-E1-T1');
|
||||
});
|
||||
|
||||
it('B-component should use expected services', () => {
|
||||
expect(element(by.css('b-car')).getText()).toContain('C2-E2-T1');
|
||||
it('B-component should use expected services', async () => {
|
||||
expect(await element(by.css('b-car')).getText()).toContain('C2-E2-T1');
|
||||
});
|
||||
|
||||
it('C-component should use expected services', () => {
|
||||
expect(element(by.css('c-car')).getText()).toContain('C3-E2-T1');
|
||||
it('C-component should use expected services', async () => {
|
||||
expect(await element(by.css('c-car')).getText()).toContain('C3-E2-T1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { browser, element, by, ElementFinder } from 'protractor';
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const page = {
|
||||
@@ -24,115 +24,113 @@ const page = {
|
||||
uploadMessage: element(by.css('app-uploader p'))
|
||||
};
|
||||
|
||||
const checkLogForMessage = (message: string) => {
|
||||
expect(page.logList.getText()).toContain(message);
|
||||
const checkLogForMessage = async (message: string) => {
|
||||
expect(await page.logList.getText()).toContain(message);
|
||||
};
|
||||
|
||||
describe('Http Tests', () => {
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
describe('Heroes', () => {
|
||||
it('retrieves the list of heroes at startup', () => {
|
||||
expect(page.heroesListItems.count()).toBe(4);
|
||||
expect(page.heroesListItems.get(0).getText()).toContain('Dr Nice');
|
||||
checkLogForMessage('GET "api/heroes"');
|
||||
it('retrieves the list of heroes at startup', async () => {
|
||||
expect(await page.heroesListItems.count()).toBe(4);
|
||||
expect(await page.heroesListItems.get(0).getText()).toContain('Dr Nice');
|
||||
await checkLogForMessage('GET "api/heroes"');
|
||||
});
|
||||
|
||||
it('makes a POST to add a new hero', () => {
|
||||
page.heroesListInput.sendKeys('Magneta');
|
||||
page.heroesListAddButton.click();
|
||||
expect(page.heroesListItems.count()).toBe(5);
|
||||
checkLogForMessage('POST "api/heroes"');
|
||||
it('makes a POST to add a new hero', async () => {
|
||||
await page.heroesListInput.sendKeys('Magneta');
|
||||
await page.heroesListAddButton.click();
|
||||
expect(await page.heroesListItems.count()).toBe(5);
|
||||
await checkLogForMessage('POST "api/heroes"');
|
||||
});
|
||||
|
||||
it('makes a GET to search for a hero', () => {
|
||||
page.heroesListInput.sendKeys('Celeritas');
|
||||
page.heroesListSearchButton.click();
|
||||
checkLogForMessage('GET "api/heroes?name=Celeritas"');
|
||||
it('makes a GET to search for a hero', async () => {
|
||||
await page.heroesListInput.sendKeys('Celeritas');
|
||||
await page.heroesListSearchButton.click();
|
||||
await checkLogForMessage('GET "api/heroes?name=Celeritas"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Messages', () => {
|
||||
it('can clear the logs', () => {
|
||||
expect(page.logListItems.count()).toBe(1);
|
||||
page.logClearButton.click();
|
||||
expect(page.logListItems.count()).toBe(0);
|
||||
it('can clear the logs', async () => {
|
||||
expect(await page.logListItems.count()).toBe(1);
|
||||
await page.logClearButton.click();
|
||||
expect(await page.logListItems.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Configuration', () => {
|
||||
it('can fetch the configuration JSON file', () => {
|
||||
page.configGetButton.click();
|
||||
checkLogForMessage('GET "assets/config.json"');
|
||||
expect(page.configSpan.getText()).toContain('Heroes API URL is "api/heroes"');
|
||||
expect(page.configSpan.getText()).toContain('Textfile URL is "assets/textfile.txt"');
|
||||
it('can fetch the configuration JSON file', async () => {
|
||||
await page.configGetButton.click();
|
||||
await checkLogForMessage('GET "assets/config.json"');
|
||||
expect(await page.configSpan.getText()).toContain('Heroes API URL is "api/heroes"');
|
||||
expect(await page.configSpan.getText()).toContain('Textfile URL is "assets/textfile.txt"');
|
||||
});
|
||||
|
||||
it('can fetch the configuration JSON file with headers', () => {
|
||||
page.configGetResponseButton.click();
|
||||
checkLogForMessage('GET "assets/config.json"');
|
||||
expect(page.configSpan.getText()).toContain('Response headers:');
|
||||
expect(page.configSpan.getText()).toContain('content-type: application/json; charset=UTF-8');
|
||||
it('can fetch the configuration JSON file with headers', async () => {
|
||||
await page.configGetResponseButton.click();
|
||||
await checkLogForMessage('GET "assets/config.json"');
|
||||
expect(await page.configSpan.getText()).toContain('Response headers:');
|
||||
expect(await page.configSpan.getText()).toContain('content-type: application/json; charset=UTF-8');
|
||||
});
|
||||
|
||||
it('can clear the configuration log', () => {
|
||||
page.configGetResponseButton.click();
|
||||
expect(page.configSpan.getText()).toContain('Response headers:');
|
||||
page.configClearButton.click();
|
||||
expect(page.configSpan.isPresent()).toBeFalsy();
|
||||
it('can clear the configuration log', async () => {
|
||||
await page.configGetResponseButton.click();
|
||||
expect(await page.configSpan.getText()).toContain('Response headers:');
|
||||
await page.configClearButton.click();
|
||||
expect(await page.configSpan.isPresent()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('throws an error for a non valid url', () => {
|
||||
page.configErrorButton.click();
|
||||
checkLogForMessage('GET "not/a/real/url"');
|
||||
expect(page.configErrorMessage.getText()).toContain('"Something bad happened; please try again later."');
|
||||
it('throws an error for a non valid url', async () => {
|
||||
await page.configErrorButton.click();
|
||||
await checkLogForMessage('GET "not/a/real/url"');
|
||||
expect(await page.configErrorMessage.getText()).toContain('"Something bad happened; please try again later."');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Download', () => {
|
||||
it('can download a txt file and show it', () => {
|
||||
page.downloadButton.click();
|
||||
checkLogForMessage('DownloaderService downloaded "assets/textfile.txt"');
|
||||
checkLogForMessage('GET "assets/textfile.txt"');
|
||||
expect(page.downloadMessage.getText()).toContain('Contents: "This is the downloaded text file "');
|
||||
it('can download a txt file and show it', async () => {
|
||||
await page.downloadButton.click();
|
||||
await checkLogForMessage('DownloaderService downloaded "assets/textfile.txt"');
|
||||
await checkLogForMessage('GET "assets/textfile.txt"');
|
||||
expect(await page.downloadMessage.getText()).toContain('Contents: "This is the downloaded text file "');
|
||||
});
|
||||
|
||||
it('can clear the log of the download', () => {
|
||||
page.downloadButton.click();
|
||||
expect(page.downloadMessage.getText()).toContain('Contents: "This is the downloaded text file "');
|
||||
page.downloadClearButton.click();
|
||||
expect(page.downloadMessage.isPresent()).toBeFalsy();
|
||||
it('can clear the log of the download', async () => {
|
||||
await page.downloadButton.click();
|
||||
expect(await page.downloadMessage.getText()).toContain('Contents: "This is the downloaded text file "');
|
||||
await page.downloadClearButton.click();
|
||||
expect(await page.downloadMessage.isPresent()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Upload', () => {
|
||||
it('can upload a file', () => {
|
||||
it('can upload a file', async () => {
|
||||
const filename = 'app.po.ts';
|
||||
const url = resolve(__dirname, filename);
|
||||
page.uploadInput.sendKeys(url);
|
||||
checkLogForMessage('POST "/upload/file" succeeded in');
|
||||
expect(page.uploadMessage.getText()).toContain(
|
||||
await page.uploadInput.sendKeys(url);
|
||||
await checkLogForMessage('POST "/upload/file" succeeded in');
|
||||
expect(await page.uploadMessage.getText()).toContain(
|
||||
`File "${filename}" was completely uploaded!`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PackageSearch', () => {
|
||||
it('can search for npm package and find in cache', () => {
|
||||
it('can search for npm package and find in cache', async () => {
|
||||
const packageName = 'angular';
|
||||
page.searchInput.sendKeys(packageName);
|
||||
checkLogForMessage(
|
||||
await page.searchInput.sendKeys(packageName);
|
||||
await checkLogForMessage(
|
||||
'Caching response from "https://npmsearch.com/query?q=angular"');
|
||||
expect(page.searchListItems.count()).toBeGreaterThan(1, 'angular items');
|
||||
expect(await page.searchListItems.count()).toBeGreaterThan(1, 'angular items');
|
||||
|
||||
page.searchInput.clear();
|
||||
page.searchInput.sendKeys(' ');
|
||||
expect(page.searchListItems.count()).toBe(0, 'search empty');
|
||||
await page.searchInput.clear();
|
||||
await page.searchInput.sendKeys(' ');
|
||||
expect(await page.searchListItems.count()).toBe(0, 'search empty');
|
||||
|
||||
page.searchInput.clear();
|
||||
page.searchInput.sendKeys(packageName);
|
||||
checkLogForMessage(
|
||||
await page.searchInput.clear();
|
||||
await page.searchInput.sendKeys(packageName);
|
||||
await checkLogForMessage(
|
||||
'Found cached response for "https://npmsearch.com/query?q=angular"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,44 +2,42 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('i18n E2E Tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
it('should display i18n translated welcome: Bonjour !', async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual('Bonjour i18n !');
|
||||
});
|
||||
|
||||
it('should display i18n translated welcome: Bonjour !', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Bonjour i18n !');
|
||||
it('should display the node texts without elements', async () => {
|
||||
expect(await element(by.css('app-root')).getText()).toContain(`Je n'affiche aucun élément`);
|
||||
});
|
||||
|
||||
it('should display the node texts without elements', () => {
|
||||
expect(element(by.css('app-root')).getText()).toContain(`Je n'affiche aucun élément`);
|
||||
});
|
||||
|
||||
it('should display the translated title attribute', () => {
|
||||
const title = element(by.css('img')).getAttribute('title');
|
||||
it('should display the translated title attribute', async () => {
|
||||
const title = await element(by.css('img')).getAttribute('title');
|
||||
expect(title).toBe(`Logo d'Angular`);
|
||||
});
|
||||
|
||||
it('should display the ICU plural expression', () => {
|
||||
expect(element.all(by.css('span')).get(0).getText()).toBe(`Mis à jour à l'instant`);
|
||||
it('should display the ICU plural expression', async () => {
|
||||
expect(await element.all(by.css('span')).get(0).getText()).toBe(`Mis à jour à l'instant`);
|
||||
});
|
||||
|
||||
it('should display the ICU select expression', () => {
|
||||
it('should display the ICU select expression', async () => {
|
||||
const selectIcuExp = element.all(by.css('span')).get(1);
|
||||
expect(selectIcuExp.getText()).toBe(`L'auteur est une femme`);
|
||||
element.all(by.css('button')).get(2).click();
|
||||
expect(selectIcuExp.getText()).toBe(`L'auteur est un homme`);
|
||||
expect(await selectIcuExp.getText()).toBe(`L'auteur est une femme`);
|
||||
await element.all(by.css('button')).get(2).click();
|
||||
expect(await selectIcuExp.getText()).toBe(`L'auteur est un homme`);
|
||||
});
|
||||
|
||||
it('should display the nested expression', () => {
|
||||
it('should display the nested expression', async () => {
|
||||
const nestedExp = element.all(by.css('span')).get(2);
|
||||
const incBtn = element.all(by.css('button')).get(0);
|
||||
expect(nestedExp.getText()).toBe(`Mis à jour: à l'instant`);
|
||||
incBtn.click();
|
||||
expect(nestedExp.getText()).toBe(`Mis à jour: il y a une minute`);
|
||||
incBtn.click();
|
||||
incBtn.click();
|
||||
element.all(by.css('button')).get(4).click();
|
||||
expect(nestedExp.getText()).toBe(`Mis à jour: il y a 3 minutes par autre`);
|
||||
expect(await nestedExp.getText()).toBe(`Mis à jour: à l'instant`);
|
||||
await incBtn.click();
|
||||
expect(await nestedExp.getText()).toBe(`Mis à jour: il y a une minute`);
|
||||
await incBtn.click();
|
||||
await incBtn.click();
|
||||
await element.all(by.css('button')).get(4).click();
|
||||
expect(await nestedExp.getText()).toBe(`Mis à jour: il y a 3 minutes par autre`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { logging } from 'selenium-webdriver';
|
||||
import { browser, element, by, logging } from 'protractor';
|
||||
|
||||
describe('Inputs and Outputs', () => {
|
||||
|
||||
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
|
||||
// helper function used to test what's logged to the console
|
||||
async function logChecker(button, contents) {
|
||||
async function logChecker(contents) {
|
||||
const logs = await browser
|
||||
.manage()
|
||||
.logs()
|
||||
@@ -17,9 +14,9 @@ describe('Inputs and Outputs', () => {
|
||||
expect(messages.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
it('should have title Inputs and Outputs', () => {
|
||||
it('should have title Inputs and Outputs', async () => {
|
||||
const title = element.all(by.css('h1')).get(0);
|
||||
expect(title.getText()).toEqual('Inputs and Outputs');
|
||||
expect(await title.getText()).toEqual('Inputs and Outputs');
|
||||
});
|
||||
|
||||
it('should add 123 to the parent list', async () => {
|
||||
@@ -28,36 +25,35 @@ describe('Inputs and Outputs', () => {
|
||||
const addedItem = element.all(by.css('li')).get(4);
|
||||
await addToListInput.sendKeys('123');
|
||||
await addToParentButton.click();
|
||||
expect(addedItem.getText()).toEqual('123');
|
||||
expect(await addedItem.getText()).toEqual('123');
|
||||
});
|
||||
|
||||
it('should delete item', async () => {
|
||||
const deleteButton = element.all(by.css('button')).get(1);
|
||||
const contents = 'Child';
|
||||
await deleteButton.click();
|
||||
await logChecker(deleteButton, contents);
|
||||
await logChecker(contents);
|
||||
});
|
||||
|
||||
it('should log buy the item', async () => {
|
||||
const buyButton = element.all(by.css('button')).get(2);
|
||||
const contents = 'Child';
|
||||
await buyButton.click();
|
||||
await logChecker(buyButton, contents);
|
||||
await logChecker(contents);
|
||||
});
|
||||
|
||||
it('should save item for later', async () => {
|
||||
const saveButton = element.all(by.css('button')).get(3);
|
||||
const contents = 'Child';
|
||||
await saveButton.click();
|
||||
await logChecker(saveButton, contents);
|
||||
await logChecker(contents);
|
||||
});
|
||||
|
||||
it('should add item to wishlist', async () => {
|
||||
const addToParentButton = element.all(by.css('button')).get(4);
|
||||
const addedItem = element.all(by.css('li')).get(6);
|
||||
await addToParentButton.click();
|
||||
expect(addedItem.getText()).toEqual('Television');
|
||||
expect(await addedItem.getText()).toEqual('Television');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -2,46 +2,44 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Interpolation e2e tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
it('should display Interpolation and Template Expressions', async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual('Interpolation and Template Expressions');
|
||||
});
|
||||
|
||||
it('should display Interpolation and Template Expressions', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Interpolation and Template Expressions');
|
||||
it('should display Current customer: Maria', async () => {
|
||||
expect(await element.all(by.css('h3')).get(0).getText()).toBe(`Current customer: Maria`);
|
||||
});
|
||||
|
||||
it('should display Current customer: Maria', () => {
|
||||
expect(element.all(by.css('h3')).get(0).getText()).toBe(`Current customer: Maria`);
|
||||
it('should display The sum of 1 + 1 is not 4.', async () => {
|
||||
expect(await element.all(by.css('p:last-child')).get(0).getText()).toBe(`The sum of 1 + 1 is not 4.`);
|
||||
});
|
||||
|
||||
it('should display The sum of 1 + 1 is not 4.', () => {
|
||||
expect(element.all(by.css('p:last-child')).get(0).getText()).toBe(`The sum of 1 + 1 is not 4.`);
|
||||
it('should display Expression Context', async () => {
|
||||
expect(await element.all(by.css('h2')).get(1).getText()).toBe(`Expression Context`);
|
||||
});
|
||||
|
||||
it('should display Expression Context', () => {
|
||||
expect(element.all(by.css('h2')).get(1).getText()).toBe(`Expression Context`);
|
||||
it('should display a list of customers', async () => {
|
||||
expect(await element.all(by.css('li')).get(0).getText()).toBe(`Maria`);
|
||||
});
|
||||
|
||||
it('should display a list of customers', () => {
|
||||
expect(element.all(by.css('li')).get(0).getText()).toBe(`Maria`);
|
||||
});
|
||||
|
||||
it('should display two pictures', () => {
|
||||
it('should display two pictures', async () => {
|
||||
const pottedPlant = element.all(by.css('img')).get(0);
|
||||
const lamp = element.all(by.css('img')).get(1);
|
||||
|
||||
expect(pottedPlant.getAttribute('src')).toContain('potted-plant');
|
||||
expect(pottedPlant.isDisplayed()).toBe(true);
|
||||
expect(await pottedPlant.getAttribute('src')).toContain('potted-plant');
|
||||
expect(await pottedPlant.isDisplayed()).toBe(true);
|
||||
|
||||
expect(lamp.getAttribute('src')).toContain('lamp');
|
||||
expect(lamp.isDisplayed()).toBe(true);
|
||||
expect(await lamp.getAttribute('src')).toContain('lamp');
|
||||
expect(await lamp.isDisplayed()).toBe(true);
|
||||
});
|
||||
|
||||
it('should support user input', () => {
|
||||
it('should support user input', async () => {
|
||||
const input = element(by.css('input'));
|
||||
const label = element(by.css('label'));
|
||||
expect(label.getText()).toEqual('Type something:');
|
||||
input.sendKeys('abc');
|
||||
expect(label.getText()).toEqual('Type something: abc');
|
||||
expect(await label.getText()).toEqual('Type something:');
|
||||
await input.sendKeys('abc');
|
||||
expect(await label.getText()).toEqual('Type something: abc');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,8 +9,10 @@ import { CUSTOMERS } from './customers';
|
||||
})
|
||||
export class AppComponent {
|
||||
customers = CUSTOMERS;
|
||||
|
||||
// #docregion customer
|
||||
currentCustomer = 'Maria';
|
||||
// #enddocregion customer
|
||||
|
||||
title = 'Featured product:';
|
||||
itemImageUrl = '../assets/potted-plant.png';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { element, by } from 'protractor';
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
|
||||
describe('providers App', () => {
|
||||
@@ -7,41 +7,34 @@ describe('providers App', () => {
|
||||
const buttons = element.all(by.css('button'));
|
||||
const customersButton = buttons.get(0);
|
||||
const ordersButton = buttons.get(1);
|
||||
const homeButton = buttons.get(2);
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
page = new AppPage();
|
||||
await page.navigateTo();
|
||||
});
|
||||
|
||||
it('should display message saying app works', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('Lazy loading feature modules');
|
||||
it('should display message saying app works', async () => {
|
||||
expect(await page.getTitleText()).toEqual('Lazy loading feature modules');
|
||||
});
|
||||
|
||||
describe('Customers', () => {
|
||||
beforeEach(() => {
|
||||
customersButton.click();
|
||||
});
|
||||
beforeEach(() => customersButton.click());
|
||||
|
||||
it('should show customers when the button is clicked', () => {
|
||||
const customersMessage = element(by.css('app-customers > p'));
|
||||
expect(customersMessage.getText()).toBe('customers works!');
|
||||
it('should show customers when the button is clicked', async () => {
|
||||
const customersMessage = element(by.css('app-customers > p'));
|
||||
expect(await customersMessage.getText()).toBe('customers works!');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Orders', () => {
|
||||
beforeEach(() => {
|
||||
ordersButton.click();
|
||||
});
|
||||
beforeEach(() => ordersButton.click());
|
||||
|
||||
it('should show orders when the button is clicked', () => {
|
||||
const ordersMessage = element(by.css('app-orders > p'));
|
||||
expect(ordersMessage.getText()).toBe('orders works!');
|
||||
it('should show orders when the button is clicked', async () => {
|
||||
const ordersMessage = element(by.css('app-orders > p'));
|
||||
expect(await ordersMessage.getText()).toBe('orders works!');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
import { browser, element, ElementFinder, by } from 'protractor';
|
||||
|
||||
describe('Lifecycle hooks', () => {
|
||||
const sendKeys = (el: ElementFinder, input: string) =>
|
||||
input.split('').reduce((prev, c) => prev.then(() => el.sendKeys(c)), Promise.resolve());
|
||||
const sendKeys = async (el: ElementFinder, input: string) => {
|
||||
for (const c of input.split('')) {
|
||||
await el.sendKeys(c);
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should open correctly', () => {
|
||||
expect(element.all(by.css('h2')).get(0).getText()).toEqual('Peek-A-Boo');
|
||||
it('should open correctly', async () => {
|
||||
expect(await element.all(by.css('h2')).get(0).getText()).toEqual('Peek-A-Boo');
|
||||
});
|
||||
|
||||
it('should support peek-a-boo', async () => {
|
||||
const pabComp = element(by.css('peek-a-boo-parent peek-a-boo'));
|
||||
expect(pabComp.isPresent()).toBe(false, 'should not be able to find the "peek-a-boo" component');
|
||||
expect(await pabComp.isPresent()).toBe(false, 'should not be able to find the "peek-a-boo" component');
|
||||
|
||||
const pabButton = element.all(by.css('peek-a-boo-parent button')).get(0);
|
||||
const updateHeroButton = element.all(by.css('peek-a-boo-parent button')).get(1);
|
||||
expect(pabButton.getText()).toContain('Create Peek');
|
||||
expect(await pabButton.getText()).toContain('Create Peek');
|
||||
|
||||
await pabButton.click();
|
||||
expect(pabButton.getText()).toContain('Destroy Peek');
|
||||
expect(pabComp.isDisplayed()).toBe(true, 'should be able to see the "peek-a-boo" component');
|
||||
expect(pabComp.getText()).toContain('Windstorm');
|
||||
expect(pabComp.getText()).not.toContain('Windstorm!');
|
||||
expect(updateHeroButton.isPresent()).toBe(true, 'should be able to see the update hero button');
|
||||
expect(await pabButton.getText()).toContain('Destroy Peek');
|
||||
expect(await pabComp.isDisplayed()).toBe(true, 'should be able to see the "peek-a-boo" component');
|
||||
expect(await pabComp.getText()).toContain('Windstorm');
|
||||
expect(await pabComp.getText()).not.toContain('Windstorm!');
|
||||
expect(await updateHeroButton.isPresent()).toBe(true, 'should be able to see the update hero button');
|
||||
|
||||
await updateHeroButton.click();
|
||||
expect(pabComp.getText()).toContain('Windstorm!');
|
||||
expect(await pabComp.getText()).toContain('Windstorm!');
|
||||
|
||||
await pabButton.click();
|
||||
expect(pabComp.isPresent()).toBe(false, 'should no longer be able to find the "peek-a-boo" component');
|
||||
expect(await pabComp.isPresent()).toBe(false, 'should no longer be able to find the "peek-a-boo" component');
|
||||
});
|
||||
|
||||
it('should support OnChanges hook', () => {
|
||||
it('should support OnChanges hook', async () => {
|
||||
const onChangesViewEle = element.all(by.css('on-changes div')).get(0);
|
||||
const inputEles = element.all(by.css('on-changes-parent input'));
|
||||
const heroNameInputEle = inputEles.get(1);
|
||||
@@ -42,15 +43,15 @@ describe('Lifecycle hooks', () => {
|
||||
const titleEle = onChangesViewEle.element(by.css('p'));
|
||||
const changeLogEles = onChangesViewEle.all(by.css('div'));
|
||||
|
||||
expect(titleEle.getText()).toContain('Windstorm can sing');
|
||||
expect(changeLogEles.count()).toEqual(2, 'should start with 2 messages');
|
||||
heroNameInputEle.sendKeys('-foo-');
|
||||
expect(titleEle.getText()).toContain('Windstorm-foo- can sing');
|
||||
expect(changeLogEles.count()).toEqual(2, 'should still have 2 messages');
|
||||
powerInputEle.sendKeys('-bar-');
|
||||
expect(titleEle.getText()).toContain('Windstorm-foo- can sing-bar-');
|
||||
expect(await titleEle.getText()).toContain('Windstorm can sing');
|
||||
expect(await changeLogEles.count()).toEqual(2, 'should start with 2 messages');
|
||||
await heroNameInputEle.sendKeys('-foo-');
|
||||
expect(await titleEle.getText()).toContain('Windstorm-foo- can sing');
|
||||
expect(await changeLogEles.count()).toEqual(2, 'should still have 2 messages');
|
||||
await powerInputEle.sendKeys('-bar-');
|
||||
expect(await titleEle.getText()).toContain('Windstorm-foo- can sing-bar-');
|
||||
// 7 == 2 previously + length of '-bar-'
|
||||
expect(changeLogEles.count()).toEqual(7, 'should have 7 messages now');
|
||||
expect(await changeLogEles.count()).toEqual(7, 'should have 7 messages now');
|
||||
});
|
||||
|
||||
it('should support DoCheck hook', async () => {
|
||||
@@ -62,7 +63,7 @@ describe('Lifecycle hooks', () => {
|
||||
const changeLogEles = doCheckViewEle.all(by.css('div'));
|
||||
let logCount: number;
|
||||
|
||||
expect(titleEle.getText()).toContain('Windstorm can sing');
|
||||
expect(await titleEle.getText()).toContain('Windstorm can sing');
|
||||
|
||||
let count = await changeLogEles.count();
|
||||
// 3 messages to start
|
||||
@@ -71,13 +72,13 @@ describe('Lifecycle hooks', () => {
|
||||
logCount = count;
|
||||
await sendKeys(heroNameInputEle, '-foo-');
|
||||
count = await changeLogEles.count();
|
||||
expect(titleEle.getText()).toContain('Windstorm-foo- can sing');
|
||||
expect(await titleEle.getText()).toContain('Windstorm-foo- can sing');
|
||||
expect(count).toBeGreaterThanOrEqual(logCount + 5, 'should add at least one more message for each keystroke');
|
||||
|
||||
logCount = count;
|
||||
await sendKeys(powerInputEle, '-bar-');
|
||||
count = await changeLogEles.count();
|
||||
expect(titleEle.getText()).toContain('Windstorm-foo- can sing-bar-');
|
||||
expect(await titleEle.getText()).toContain('Windstorm-foo- can sing-bar-');
|
||||
expect(count).toBeGreaterThanOrEqual(logCount + 5, 'should add at least one more message for each keystroke');
|
||||
});
|
||||
|
||||
@@ -89,15 +90,15 @@ describe('Lifecycle hooks', () => {
|
||||
const childViewInputEle = parentEle.element(by.css('app-child-view input'));
|
||||
let logCount: number;
|
||||
|
||||
expect(childViewInputEle.getAttribute('value')).toContain('Magneta');
|
||||
expect(commentEle.isPresent()).toBe(false, 'comment should not be in DOM');
|
||||
expect(await childViewInputEle.getAttribute('value')).toContain('Magneta');
|
||||
expect(await commentEle.isPresent()).toBe(false, 'comment should not be in DOM');
|
||||
|
||||
logCount = await logEles.count();
|
||||
await childViewInputEle.sendKeys('-test-');
|
||||
|
||||
expect(childViewInputEle.getAttribute('value')).toContain('-test-');
|
||||
expect(commentEle.isPresent()).toBe(true, 'should have comment because >10 chars');
|
||||
expect(commentEle.getText()).toContain('long name');
|
||||
expect(await childViewInputEle.getAttribute('value')).toContain('-test-');
|
||||
expect(await commentEle.isPresent()).toBe(true, 'should have comment because >10 chars');
|
||||
expect(await commentEle.getText()).toContain('long name');
|
||||
|
||||
const count = await logEles.count();
|
||||
expect(logCount + 7).toBeGreaterThan(count - 3, '7 additional log messages should have been added');
|
||||
@@ -105,7 +106,7 @@ describe('Lifecycle hooks', () => {
|
||||
|
||||
logCount = count;
|
||||
await buttonEle.click();
|
||||
expect(logEles.count()).toBeLessThan(logCount, 'log should shrink after reset');
|
||||
expect(await logEles.count()).toBeLessThan(logCount, 'log should shrink after reset');
|
||||
});
|
||||
|
||||
it('should support AfterContent hooks', async () => {
|
||||
@@ -116,19 +117,19 @@ describe('Lifecycle hooks', () => {
|
||||
const childViewInputEle = parentEle.element(by.css('app-child input'));
|
||||
let logCount = await logEles.count();
|
||||
|
||||
expect(childViewInputEle.getAttribute('value')).toContain('Magneta');
|
||||
expect(commentEle.isPresent()).toBe(false, 'comment should not be in DOM');
|
||||
expect(await childViewInputEle.getAttribute('value')).toContain('Magneta');
|
||||
expect(await commentEle.isPresent()).toBe(false, 'comment should not be in DOM');
|
||||
|
||||
await sendKeys(childViewInputEle, '-test-');
|
||||
const count = await logEles.count();
|
||||
expect(childViewInputEle.getAttribute('value')).toContain('-test-');
|
||||
expect(commentEle.isPresent()).toBe(true, 'should have comment because >10 chars');
|
||||
expect(commentEle.getText()).toContain('long name');
|
||||
expect(await childViewInputEle.getAttribute('value')).toContain('-test-');
|
||||
expect(await commentEle.isPresent()).toBe(true, 'should have comment because >10 chars');
|
||||
expect(await commentEle.getText()).toContain('long name');
|
||||
expect(count).toBeGreaterThanOrEqual(logCount + 5, 'additional log messages should have been added');
|
||||
|
||||
logCount = count;
|
||||
await buttonEle.click();
|
||||
expect(logEles.count()).toBeLessThan(logCount, 'log should shrink after reset');
|
||||
expect(await logEles.count()).toBeLessThan(logCount, 'log should shrink after reset');
|
||||
});
|
||||
|
||||
it('should support spy\'s OnInit & OnDestroy hooks', async () => {
|
||||
@@ -138,18 +139,18 @@ describe('Lifecycle hooks', () => {
|
||||
const heroEles = element.all(by.css('spy-parent div[appSpy'));
|
||||
const logEles = element.all(by.css('spy-parent h4 ~ div'));
|
||||
|
||||
expect(heroEles.count()).toBe(2, 'should have two heroes displayed');
|
||||
expect(logEles.count()).toBe(2, 'should have two log entries');
|
||||
expect(await heroEles.count()).toBe(2, 'should have two heroes displayed');
|
||||
expect(await logEles.count()).toBe(2, 'should have two log entries');
|
||||
|
||||
await inputEle.sendKeys('-test-');
|
||||
await addHeroButtonEle.click();
|
||||
expect(heroEles.count()).toBe(3, 'should have added one hero');
|
||||
expect(heroEles.get(2).getText()).toContain('-test-');
|
||||
expect(logEles.count()).toBe(3, 'should now have 3 log entries');
|
||||
expect(await heroEles.count()).toBe(3, 'should have added one hero');
|
||||
expect(await heroEles.get(2).getText()).toContain('-test-');
|
||||
expect(await logEles.count()).toBe(3, 'should now have 3 log entries');
|
||||
|
||||
await resetHeroesButtonEle.click();
|
||||
expect(heroEles.count()).toBe(0, 'should no longer have any heroes');
|
||||
expect(logEles.count()).toBe(7, 'should now have 7 log entries - 3 orig + 1 reset + 3 removeall');
|
||||
expect(await heroEles.count()).toBe(0, 'should no longer have any heroes');
|
||||
expect(await logEles.count()).toBe(7, 'should now have 7 log entries - 3 orig + 1 reset + 3 removeall');
|
||||
});
|
||||
|
||||
it('should support "spy counter"', async () => {
|
||||
@@ -158,15 +159,15 @@ describe('Lifecycle hooks', () => {
|
||||
const textEle = element(by.css('counter-parent app-counter > div'));
|
||||
const logEles = element.all(by.css('counter-parent h4 ~ div'));
|
||||
|
||||
expect(textEle.getText()).toContain('Counter = 0');
|
||||
expect(logEles.count()).toBe(2, 'should start with two log entries');
|
||||
expect(await textEle.getText()).toContain('Counter = 0');
|
||||
expect(await logEles.count()).toBe(2, 'should start with two log entries');
|
||||
|
||||
await updateCounterButtonEle.click();
|
||||
expect(textEle.getText()).toContain('Counter = 1');
|
||||
expect(logEles.count()).toBe(3, 'should now have 3 log entries');
|
||||
expect(await textEle.getText()).toContain('Counter = 1');
|
||||
expect(await logEles.count()).toBe(3, 'should now have 3 log entries');
|
||||
|
||||
await resetCounterButtonEle.click();
|
||||
expect(textEle.getText()).toContain('Counter = 0');
|
||||
expect(logEles.count()).toBe(7, 'should now have 7 log entries - 3 prev + 1 reset + 2 destroy + 1 init');
|
||||
expect(await textEle.getText()).toContain('Counter = 0');
|
||||
expect(await logEles.count()).toBe(7, 'should now have 7 log entries - 3 prev + 1 reset + 2 destroy + 1 init');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,116 +56,109 @@ describe('NgModule-example', () => {
|
||||
// tests
|
||||
function appTitleTests(color: string, name?: string) {
|
||||
return () => {
|
||||
it('should have a gray header', () => {
|
||||
it('should have a gray header', async () => {
|
||||
const commons = getCommonsSectionStruct();
|
||||
expect(commons.title.getCssValue('backgroundColor')).toBe(color);
|
||||
expect(await commons.title.getCssValue('backgroundColor')).toBe(color);
|
||||
});
|
||||
|
||||
it('should welcome us', () => {
|
||||
it('should welcome us', async () => {
|
||||
const commons = getCommonsSectionStruct();
|
||||
expect(commons.subtitle.getText()).toBe('Welcome, ' + (name || 'Miss Marple'));
|
||||
expect(await commons.subtitle.getText()).toBe(`Welcome, ${name || 'Miss Marple'}`);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function contactTests(color: string, name?: string) {
|
||||
return () => {
|
||||
it('shows the contact\'s owner', () => {
|
||||
it('shows the contact\'s owner', async () => {
|
||||
const contacts = getContactSectionStruct();
|
||||
expect(contacts.header.getText()).toBe((name || 'Miss Marple') + '\'s Contacts');
|
||||
expect(await contacts.header.getText()).toBe(`${name || 'Miss Marple'}'s Contacts`);
|
||||
});
|
||||
|
||||
it('can cycle between contacts', () => {
|
||||
it('can cycle between contacts', async () => {
|
||||
const contacts = getContactSectionStruct();
|
||||
const nextButton = contacts.nextContactButton;
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Yasha');
|
||||
expect(contacts.contactNameHeader.getCssValue('backgroundColor')).toBe(color);
|
||||
nextButton.click().then(() => {
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Iulia');
|
||||
return nextButton.click();
|
||||
}).then(() => {
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Karina');
|
||||
});
|
||||
expect(await contacts.contactNameHeader.getText()).toBe('Awesome Yasha');
|
||||
expect(await contacts.contactNameHeader.getCssValue('backgroundColor')).toBe(color);
|
||||
|
||||
await nextButton.click();
|
||||
expect(await contacts.contactNameHeader.getText()).toBe('Awesome Iulia');
|
||||
|
||||
await nextButton.click();
|
||||
expect(await contacts.contactNameHeader.getText()).toBe('Awesome Karina');
|
||||
});
|
||||
|
||||
it('can create a new contact', () => {
|
||||
it('can create a new contact', async () => {
|
||||
const contacts = getContactSectionStruct();
|
||||
const newContactButton = contacts.newContactButton;
|
||||
const nextButton = contacts.nextContactButton;
|
||||
const input = contacts.input;
|
||||
const saveButton = contacts.saveButton;
|
||||
|
||||
newContactButton.click().then(() => {
|
||||
input.click();
|
||||
nextButton.click();
|
||||
expect(contacts.validationError.getText()).toBe('Name is required.');
|
||||
input.click();
|
||||
contacts.input.sendKeys('Watson');
|
||||
saveButton.click();
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Watson');
|
||||
await newContactButton.click();
|
||||
await input.click();
|
||||
await nextButton.click();
|
||||
expect(await contacts.validationError.getText()).toBe('Name is required.');
|
||||
|
||||
});
|
||||
await input.click();
|
||||
await contacts.input.sendKeys('Watson');
|
||||
await saveButton.click();
|
||||
expect(await contacts.contactNameHeader.getText()).toBe('Awesome Watson');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
describe('index.html', () => {
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
describe('app-title', appTitleTests(white, 'Miss Marple'));
|
||||
|
||||
describe('contact', contactTests(lightgray, 'Miss Marple'));
|
||||
|
||||
describe('item center', () => {
|
||||
beforeEach(() => {
|
||||
getCommonsSectionStruct().itemButton.click();
|
||||
beforeEach(() => getCommonsSectionStruct().itemButton.click());
|
||||
|
||||
it('shows a list of items', async () => {
|
||||
const item = getItemSectionStruct();
|
||||
expect(await item.title.getText()).toBe('Items List');
|
||||
expect(await item.items.count()).toBe(4);
|
||||
expect(await item.items.get(0).getText()).toBe('1 - Sticky notes');
|
||||
});
|
||||
|
||||
it('shows a list of items', () => {
|
||||
it('can navigate to one item details', async () => {
|
||||
const item = getItemSectionStruct();
|
||||
expect(item.title.getText()).toBe('Items List');
|
||||
expect(item.items.count()).toBe(4);
|
||||
expect(item.items.get(0).getText()).toBe('1 - Sticky notes');
|
||||
});
|
||||
|
||||
it('can navigate to one item details', () => {
|
||||
const item = getItemSectionStruct();
|
||||
item.items.get(0).click().then(() => {
|
||||
expect(item.itemId.getText()).toBe('Item id: 1');
|
||||
return item.listLink.click();
|
||||
}).then(() => {
|
||||
// We are back to the list
|
||||
expect(item.items.count()).toBe(4);
|
||||
});
|
||||
await item.items.get(0).click();
|
||||
expect(await item.itemId.getText()).toBe('Item id: 1');
|
||||
|
||||
await item.listLink.click();
|
||||
// We are back to the list
|
||||
expect(await item.items.count()).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('customers', () => {
|
||||
beforeEach(() => {
|
||||
getCommonsSectionStruct().customersButton.click();
|
||||
beforeEach(() => getCommonsSectionStruct().customersButton.click());
|
||||
|
||||
it('shows a list of customers', async () => {
|
||||
const customers = getCustomersSectionStruct();
|
||||
expect(await customers.header.getText()).toBe('Customers of Miss Marple times 2');
|
||||
expect(await customers.title.getText()).toBe('Customer List');
|
||||
expect(await customers.items.count()).toBe(6);
|
||||
expect(await customers.items.get(0).getText()).toBe('11 - Julian');
|
||||
});
|
||||
|
||||
it('shows a list of customers', () => {
|
||||
it('can navigate and edit one customer details', async () => {
|
||||
const customers = getCustomersSectionStruct();
|
||||
expect(customers.header.getText()).toBe('Customers of Miss Marple times 2');
|
||||
expect(customers.title.getText()).toBe('Customer List');
|
||||
expect(customers.items.count()).toBe(6);
|
||||
expect(customers.items.get(0).getText()).toBe('11 - Julian');
|
||||
});
|
||||
|
||||
it('can navigate and edit one customer details', () => {
|
||||
const customers = getCustomersSectionStruct();
|
||||
customers.items.get(0).click().then(() => {
|
||||
expect(customers.itemId.getText()).toBe('Id: 11');
|
||||
customers.itemInput.sendKeys(' try');
|
||||
return customers.listLink.click();
|
||||
}).then(() => {
|
||||
// We are back to the list
|
||||
expect(customers.items.count()).toBe(6);
|
||||
expect(customers.items.get(0).getText()).toBe('11 - Julian try');
|
||||
});
|
||||
await customers.items.get(0).click();
|
||||
expect(await customers.itemId.getText()).toBe('Id: 11');
|
||||
|
||||
await customers.itemInput.sendKeys(' try');
|
||||
await customers.listLink.click();
|
||||
// We are back to the list
|
||||
expect(await customers.items.count()).toBe(6);
|
||||
expect(await customers.items.get(0).getText()).toBe('11 - Julian try');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,114 +1,108 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
const { version: angularVersion } = require('@angular/core/package.json');
|
||||
|
||||
describe('Pipes', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
browser.get('');
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
it('should open correctly', async () => {
|
||||
expect(await element.all(by.tagName('h1')).get(0).getText()).toEqual('Pipes');
|
||||
expect(await element(by.css('app-hero-birthday p')).getText()).toEqual(`The hero's birthday is Apr 15, 1988`);
|
||||
});
|
||||
|
||||
it('should open correctly', () => {
|
||||
expect(element.all(by.tagName('h1')).get(0).getText()).toEqual('Pipes');
|
||||
expect(element(by.css('app-hero-birthday p')).getText()).toEqual(`The hero's birthday is Apr 15, 1988`);
|
||||
it('should show 4 heroes', async () => {
|
||||
expect(await element.all(by.css('app-hero-list div')).count()).toEqual(4);
|
||||
});
|
||||
|
||||
it('should show 4 heroes', () => {
|
||||
expect(element.all(by.css('app-hero-list div')).count()).toEqual(4);
|
||||
it('should show a familiar hero in json', async () => {
|
||||
expect(await element(by.cssContainingText('app-hero-list p', 'Heroes as JSON')).getText()).toContain('Bombasto');
|
||||
});
|
||||
|
||||
it('should show a familiar hero in json', () => {
|
||||
expect(element(by.cssContainingText('app-hero-list p', 'Heroes as JSON')).getText()).toContain('Bombasto');
|
||||
it('should show alternate birthday formats', async () => {
|
||||
expect(await element(by.cssContainingText('app-root > p', `The hero's birthday is Apr 15, 1988`)).isDisplayed())
|
||||
.toBe(true);
|
||||
expect(await element(by.cssContainingText('app-root > p', `The hero's birthday is 04/15/88`)).isDisplayed())
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it('should show alternate birthday formats', () => {
|
||||
expect(element(by.cssContainingText('app-root > p', `The hero's birthday is Apr 15, 1988`)).isDisplayed()).toBe(true);
|
||||
expect(element(by.cssContainingText('app-root > p', `The hero's birthday is 04/15/88`)).isDisplayed()).toBe(true);
|
||||
});
|
||||
|
||||
it('should be able to toggle birthday formats', () => {
|
||||
it('should be able to toggle birthday formats', async () => {
|
||||
const birthDayEle = element(by.css('app-hero-birthday2 > p'));
|
||||
if (angularVersion.indexOf('4.') === 0) { // Breaking change between v4 and v5 (https://github.com/angular/angular/commit/079d884)
|
||||
expect(birthDayEle.getText()).toEqual(`The hero's birthday is 4/15/1988`);
|
||||
} else {
|
||||
expect(birthDayEle.getText()).toEqual(`The hero's birthday is 4/15/88`);
|
||||
}
|
||||
expect(await birthDayEle.getText()).toEqual(`The hero's birthday is 4/15/88`);
|
||||
|
||||
const buttonEle = element(by.cssContainingText('app-hero-birthday2 > button', 'Toggle Format'));
|
||||
expect(buttonEle.isDisplayed()).toBe(true);
|
||||
buttonEle.click().then(() => {
|
||||
expect(birthDayEle.getText()).toEqual(`The hero's birthday is Friday, April 15, 1988`);
|
||||
});
|
||||
expect(await buttonEle.isDisplayed()).toBe(true);
|
||||
|
||||
await buttonEle.click();
|
||||
expect(await birthDayEle.getText()).toEqual(`The hero's birthday is Friday, April 15, 1988`);
|
||||
});
|
||||
|
||||
it('should be able to chain and compose pipes', () => {
|
||||
it('should be able to chain and compose pipes', async () => {
|
||||
const chainedPipeEles = element.all(by.cssContainingText('app-root p', `The chained hero's`));
|
||||
expect(chainedPipeEles.count()).toBe(3, 'should have 3 chained pipe examples');
|
||||
expect(chainedPipeEles.get(0).getText()).toContain('APR 15, 1988');
|
||||
expect(chainedPipeEles.get(1).getText()).toContain('FRIDAY, APRIL 15, 1988');
|
||||
expect(chainedPipeEles.get(2).getText()).toContain('FRIDAY, APRIL 15, 1988');
|
||||
expect(await chainedPipeEles.count()).toBe(3, 'should have 3 chained pipe examples');
|
||||
expect(await chainedPipeEles.get(0).getText()).toContain('APR 15, 1988');
|
||||
expect(await chainedPipeEles.get(1).getText()).toContain('FRIDAY, APRIL 15, 1988');
|
||||
expect(await chainedPipeEles.get(2).getText()).toContain('FRIDAY, APRIL 15, 1988');
|
||||
});
|
||||
|
||||
it('should be able to use ExponentialStrengthPipe pipe', () => {
|
||||
it('should be able to use ExponentialStrengthPipe pipe', async () => {
|
||||
const ele = element(by.css('app-power-booster p'));
|
||||
expect(ele.getText()).toContain('Super power boost: 1024');
|
||||
expect(await ele.getText()).toContain('Super power boost: 1024');
|
||||
});
|
||||
|
||||
it('should be able to use the exponential calculator', () => {
|
||||
it('should be able to use the exponential calculator', async () => {
|
||||
const eles = element.all(by.css('app-power-boost-calculator input'));
|
||||
const baseInputEle = eles.get(0);
|
||||
const factorInputEle = eles.get(1);
|
||||
const outputEle = element(by.css('app-power-boost-calculator p'));
|
||||
baseInputEle.clear().then(() => {
|
||||
baseInputEle.sendKeys('7');
|
||||
return factorInputEle.clear();
|
||||
}).then(() => {
|
||||
factorInputEle.sendKeys('3');
|
||||
expect(outputEle.getText()).toContain('343');
|
||||
});
|
||||
|
||||
await baseInputEle.clear();
|
||||
await baseInputEle.sendKeys('7');
|
||||
await factorInputEle.clear();
|
||||
await factorInputEle.sendKeys('3');
|
||||
expect(await outputEle.getText()).toContain('343');
|
||||
});
|
||||
|
||||
|
||||
it('should support flying heroes (pure) ', () => {
|
||||
it('should support flying heroes (pure) ', async () => {
|
||||
const nameEle = element(by.css('app-flying-heroes input[type="text"]'));
|
||||
const canFlyCheckEle = element(by.css('app-flying-heroes #can-fly'));
|
||||
const mutateCheckEle = element(by.css('app-flying-heroes #mutate'));
|
||||
const resetEle = element(by.css('app-flying-heroes button'));
|
||||
const flyingHeroesEle = element.all(by.css('app-flying-heroes #flyers div'));
|
||||
|
||||
expect(canFlyCheckEle.getAttribute('checked')).toEqual('true', 'should default to "can fly"');
|
||||
expect(mutateCheckEle.getAttribute('checked')).toEqual('true', 'should default to mutating array');
|
||||
expect(flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly');
|
||||
expect(await canFlyCheckEle.getAttribute('checked')).toEqual('true', 'should default to "can fly"');
|
||||
expect(await mutateCheckEle.getAttribute('checked')).toEqual('true', 'should default to mutating array');
|
||||
expect(await flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly');
|
||||
|
||||
nameEle.sendKeys('test1\n');
|
||||
expect(flyingHeroesEle.count()).toEqual(2, 'no change while mutating array');
|
||||
mutateCheckEle.click().then(() => {
|
||||
nameEle.sendKeys('test2\n');
|
||||
expect(flyingHeroesEle.count()).toEqual(4, 'not mutating; should see both adds');
|
||||
expect(flyingHeroesEle.get(2).getText()).toContain('test1');
|
||||
expect(flyingHeroesEle.get(3).getText()).toContain('test2');
|
||||
return resetEle.click();
|
||||
})
|
||||
.then(() => {
|
||||
expect(flyingHeroesEle.count()).toEqual(2, 'reset should restore original flying heroes');
|
||||
});
|
||||
await nameEle.sendKeys('test1\n');
|
||||
expect(await flyingHeroesEle.count()).toEqual(2, 'no change while mutating array');
|
||||
|
||||
await mutateCheckEle.click();
|
||||
await nameEle.sendKeys('test2\n');
|
||||
expect(await flyingHeroesEle.count()).toEqual(4, 'not mutating; should see both adds');
|
||||
expect(await flyingHeroesEle.get(2).getText()).toContain('test1');
|
||||
expect(await flyingHeroesEle.get(3).getText()).toContain('test2');
|
||||
|
||||
await resetEle.click();
|
||||
expect(await flyingHeroesEle.count()).toEqual(2, 'reset should restore original flying heroes');
|
||||
});
|
||||
|
||||
|
||||
it('should support flying heroes (impure) ', () => {
|
||||
it('should support flying heroes (impure) ', async () => {
|
||||
const nameEle = element(by.css('app-flying-heroes-impure input[type="text"]'));
|
||||
const canFlyCheckEle = element(by.css('app-flying-heroes-impure #can-fly'));
|
||||
const mutateCheckEle = element(by.css('app-flying-heroes-impure #mutate'));
|
||||
const flyingHeroesEle = element.all(by.css('app-flying-heroes-impure #flyers div'));
|
||||
|
||||
expect(canFlyCheckEle.getAttribute('checked')).toEqual('true', 'should default to "can fly"');
|
||||
expect(mutateCheckEle.getAttribute('checked')).toEqual('true', 'should default to mutating array');
|
||||
expect(flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly');
|
||||
expect(await canFlyCheckEle.getAttribute('checked')).toEqual('true', 'should default to "can fly"');
|
||||
expect(await mutateCheckEle.getAttribute('checked')).toEqual('true', 'should default to mutating array');
|
||||
expect(await flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly');
|
||||
|
||||
nameEle.sendKeys('test1\n');
|
||||
expect(flyingHeroesEle.count()).toEqual(3, 'new flying hero should show in mutating array');
|
||||
await nameEle.sendKeys('test1\n');
|
||||
expect(await flyingHeroesEle.count()).toEqual(3, 'new flying hero should show in mutating array');
|
||||
});
|
||||
|
||||
it('should show an async hero message', () => {
|
||||
expect(element.all(by.tagName('app-hero-message')).get(0).getText()).toContain('hero');
|
||||
it('should show an async hero message', async () => {
|
||||
expect(await element.all(by.tagName('app-hero-message')).get(0).getText()).toContain('hero');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<a href="#flying-heroes-impure">Flying Heroes filter pipe (impure)</a><br>
|
||||
<a href="#hero-message">Async Hero Message and AsyncPipe</a><br>
|
||||
<a href="#hero-list">Hero List with caching FetchJsonPipe</a><br>
|
||||
<a href="#pipe-precedence">Pipes and Precedence</a><br>
|
||||
|
||||
|
||||
<hr>
|
||||
@@ -80,4 +81,7 @@
|
||||
<a id="hero-list"></a>
|
||||
<app-hero-list></app-hero-list>
|
||||
|
||||
<div style="margin-top:12em;"></div>
|
||||
<hr>
|
||||
<a id="pipe-precedence"></a>
|
||||
<app-precedence></app-precedence>
|
||||
<hr>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { HeroBirthday2Component } from './hero-birthday2.component';
|
||||
import { HeroListComponent } from './hero-list.component';
|
||||
import { PowerBoostCalculatorComponent } from './power-boost-calculator.component';
|
||||
import { PowerBoosterComponent } from './power-booster.component';
|
||||
import { PrecedenceComponent } from './precedence.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@@ -36,7 +37,8 @@ import { PowerBoosterComponent } from './power-booster.component';
|
||||
FlyingHeroesPipe,
|
||||
FlyingHeroesImpurePipe,
|
||||
FetchJsonPipe,
|
||||
ExponentialStrengthPipe
|
||||
ExponentialStrengthPipe,
|
||||
PrecedenceComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<!-- #docplaster-->
|
||||
<!-- #docregion -->
|
||||
<h2>{{title}}</h2>
|
||||
|
||||
<p>Basic uppercase pipe (no precedence considerations necessary): {{ 'text' | uppercase }}</p>
|
||||
<p>The following shows that a pipe has higher precedence than a ternary operator. If the pipe precedence were lower than the ternary operator precedence, the output would be 'TRUE'. Instead it is: {{ true ? 'true' : 'false' | uppercase }}</p>
|
||||
<p>The following shows how parentheses help Angular evaluate the whole statement:
|
||||
<!-- #docregion precedence -->
|
||||
<!-- use parentheses in the third operand so the pipe applies to the whole expression -->
|
||||
{{ (true ? 'true' : 'false') | uppercase }}
|
||||
<!-- #enddocregion precedence -->
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-precedence',
|
||||
templateUrl: './precedence.component.html',
|
||||
styles: []
|
||||
})
|
||||
|
||||
export class PrecedenceComponent {
|
||||
title = 'Pipes and Precedence';
|
||||
}
|
||||
@@ -3,52 +3,50 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Property binding e2e tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
it('should display Property Binding with Angular', async () => {
|
||||
expect(await element(by.css('h1')).getText()).toEqual('Property Binding with Angular');
|
||||
});
|
||||
|
||||
it('should display Property Binding with Angular', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Property Binding with Angular');
|
||||
it('should display four phone pictures', async () => {
|
||||
expect(await element.all(by.css('img')).isPresent()).toBe(true);
|
||||
expect(await element.all(by.css('img')).count()).toBe(4);
|
||||
});
|
||||
|
||||
it('should display four phone pictures', () => {
|
||||
expect(element.all(by.css('img')).isPresent()).toBe(true);
|
||||
expect(element.all(by.css('img')).count()).toBe(4);
|
||||
|
||||
it('should display Disabled button', async () => {
|
||||
expect(await element.all(by.css('button')).get(0).getText()).toBe(`Disabled Button`);
|
||||
});
|
||||
|
||||
it('should display Disabled button', () => {
|
||||
expect(element.all(by.css('button')).get(0).getText()).toBe(`Disabled Button`);
|
||||
it('should display Binding to a property of a directive', async () => {
|
||||
expect(await element.all(by.css('h2')).get(4).getText()).toBe(`Binding to a property of a directive`);
|
||||
});
|
||||
|
||||
it('should display Binding to a property of a directive', () => {
|
||||
expect(element.all(by.css('h2')).get(4).getText()).toBe(`Binding to a property of a directive`);
|
||||
it('should display blue', async () => {
|
||||
expect(await element.all(by.css('p')).get(0).getText()).toContain(`blue`);
|
||||
});
|
||||
|
||||
it('should display Your item is: lamp', () => {
|
||||
expect(element.all(by.css('p')).get(0).getText()).toContain(`blue`);
|
||||
});
|
||||
it('should display Your item is: lamp', () => {
|
||||
expect(element.all(by.css('p')).get(1).getText()).toContain(`Your item is: lamp`);
|
||||
it('should display Your item is: lamp', async () => {
|
||||
expect(await element.all(by.css('p')).get(1).getText()).toContain(`Your item is: lamp`);
|
||||
});
|
||||
|
||||
it('should display Your item is: parentItem', () => {
|
||||
expect(element.all(by.css('p')).get(2).getText()).toBe(`Your item is: parentItem`);
|
||||
it('should display Your item is: parentItem', async () => {
|
||||
expect(await element.all(by.css('p')).get(2).getText()).toBe(`Your item is: parentItem`);
|
||||
});
|
||||
|
||||
it('should display a ul', () => {
|
||||
expect(element.all(by.css('ul')).get(0).getText()).toContain(`tv`);
|
||||
it('should display a ul', async () => {
|
||||
expect(await element.all(by.css('ul')).get(0).getText()).toContain(`tv`);
|
||||
});
|
||||
|
||||
it('should display a ul containing phone', () => {
|
||||
expect(element.all(by.css('ul')).get(1).getText()).toBe(`21 phone`);
|
||||
it('should display a ul containing phone', async () => {
|
||||
expect(await element.all(by.css('ul')).get(1).getText()).toBe(`21 phone`);
|
||||
});
|
||||
|
||||
it('should display one-time initialized string', () => {
|
||||
expect(element.all(by.css('p')).get(3).getText()).toContain(`one-time initialized`);
|
||||
it('should display one-time initialized string', async () => {
|
||||
expect(await element.all(by.css('p')).get(3).getText()).toContain(`one-time initialized`);
|
||||
});
|
||||
|
||||
it('should display Malicious content', () => {
|
||||
expect(element.all(by.css('h2')).get(8).getText()).toBe(`Malicious content`);
|
||||
it('should display Malicious content', async () => {
|
||||
expect(await element.all(by.css('h2')).get(8).getText()).toBe(`Malicious content`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<!-- #docregion malicious-content -->
|
||||
<!--
|
||||
Angular generates a warning for the following line as it sanitizes them
|
||||
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
|
||||
WARNING: sanitizing HTML stripped some content (see https://g.co/ng/security#xss).
|
||||
-->
|
||||
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
|
||||
<!-- #enddocregion malicious-content -->
|
||||
|
||||
@@ -7,9 +7,15 @@ import { Component } from '@angular/core';
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
// #docregion item-image
|
||||
itemImageUrl = '../assets/phone.png';
|
||||
// #enddocregion item-image
|
||||
// #docregion boolean
|
||||
isUnchanged = true;
|
||||
// #enddocregion boolean
|
||||
// #docregion directive-property
|
||||
classes = 'special';
|
||||
// #enddocregion directive-property
|
||||
// #docregion parent-data-type
|
||||
parentItem = 'lamp';
|
||||
// #enddocregion parent-data-type
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { logging } from 'selenium-webdriver';
|
||||
|
||||
describe('Providers and ViewProviders', () => {
|
||||
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
it('shows basic flower emoji', async () => {
|
||||
expect(await element.all(by.css('p')).get(0).getText()).toContain('🌺');
|
||||
});
|
||||
|
||||
it('shows basic flower emoji', () => {
|
||||
expect(element.all(by.css('p')).get(0).getText()).toContain('🌺');
|
||||
it('shows whale emoji', async () => {
|
||||
expect(await element.all(by.css('p')).get(1).getText()).toContain('🐳');
|
||||
});
|
||||
|
||||
it('shows whale emoji', () => {
|
||||
expect(element.all(by.css('p')).get(1).getText()).toContain('🐳');
|
||||
});
|
||||
|
||||
it('shows sunflower from FlowerService', () => {
|
||||
expect(element.all(by.css('p')).get(8).getText()).toContain('🌻');
|
||||
it('shows sunflower from FlowerService', async () => {
|
||||
expect(await element.all(by.css('p')).get(8).getText()).toContain('🌻');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user