From 8851ba9c9d6b904538b5cb3e33a2e157d8631d98 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Wed, 22 Jul 2020 09:49:33 -0700 Subject: [PATCH 001/133] docs: release notes for the v10.0.5 release --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a882bd176f..8cb7513550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ + +## 10.0.5 (2020-07-22) + + +### Bug Fixes + +* **compiler:** properly associate source spans for implicitly closed elements ([#38126](https://github.com/angular/angular/issues/38126)) ([e80278c](https://github.com/angular/angular/commit/e80278c)), closes [#36118](https://github.com/angular/angular/issues/36118) +* **compiler-cli:** ensure file_system handles mixed Windows drives ([#38030](https://github.com/angular/angular/issues/38030)) ([dba4023](https://github.com/angular/angular/commit/dba4023)), closes [#36777](https://github.com/angular/angular/issues/36777) +* **core:** Allow modification of lifecycle hooks any time before bootstrap ([#38119](https://github.com/angular/angular/issues/38119)) ([14b4718](https://github.com/angular/angular/commit/14b4718)), closes [#30497](https://github.com/angular/angular/issues/30497) +* **core:** error due to integer overflow when there are too many host bindings ([#38014](https://github.com/angular/angular/issues/38014)) ([7b6e73c](https://github.com/angular/angular/commit/7b6e73c)), closes [#37876](https://github.com/angular/angular/issues/37876) [#37876](https://github.com/angular/angular/issues/37876) +* **core:** incorrectly validating properties on ng-content and ng-container ([#37773](https://github.com/angular/angular/issues/37773)) ([17ddab9](https://github.com/angular/angular/commit/17ddab9)) + + + ## 10.0.4 (2020-07-15) From 970d10c671b9f5a2361037c4382b98a19bf6d84e Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Wed, 22 Jul 2020 09:55:21 -0700 Subject: [PATCH 002/133] release: cut the v10.1.0-next.2 release --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb7513550..b374d1f1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ + +# 10.1.0-next.2 (2020-07-22) + + +### Bug Fixes + +* **core:** Allow modification of lifecycle hooks any time before bootstrap ([#35464](https://github.com/angular/angular/issues/35464)) ([737506e](https://github.com/angular/angular/commit/737506e)), closes [#30497](https://github.com/angular/angular/issues/30497) + + +### Features + +* **common:** add ReadonlyMap in place of Map in keyValuePipe ([#37311](https://github.com/angular/angular/issues/37311)) ([3373453](https://github.com/angular/angular/commit/3373453)), closes [#37308](https://github.com/angular/angular/issues/37308) +* **forms:** AbstractControl to store raw validators in addition to combined validators function ([#37881](https://github.com/angular/angular/issues/37881)) ([ad7046b](https://github.com/angular/angular/commit/ad7046b)) +* **localize:** allow duplicate messages to be handled during extraction ([#38082](https://github.com/angular/angular/issues/38082)) ([cf9a47b](https://github.com/angular/angular/commit/cf9a47b)), closes [#38077](https://github.com/angular/angular/issues/38077) + + + ## 10.0.5 (2020-07-22) diff --git a/package.json b/package.json index 7bfd97a7bd..06b39348d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "10.1.0-next.1", + "version": "10.1.0-next.2", "private": true, "description": "Angular - a web framework for modern web apps", "homepage": "https://github.com/angular/angular", From 5e742d29d0dc304a45a9fb2279c21924479a088b Mon Sep 17 00:00:00 2001 From: Boaz Rymland Date: Sun, 12 Apr 2020 14:04:47 +0300 Subject: [PATCH 003/133] docs: fix typo from singular to plural spelling (#36586) This commit fixes the spelling of the singular form of the word function to the plural spelling in packages/core/src/application_init.ts PR Close #36586 --- packages/core/src/application_init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/application_init.ts b/packages/core/src/application_init.ts index 7293d7c403..d1fabf99da 100644 --- a/packages/core/src/application_init.ts +++ b/packages/core/src/application_init.ts @@ -15,7 +15,7 @@ import {Inject, Injectable, InjectionToken, Optional} from './di'; * A [DI token](guide/glossary#di-token "DI token definition") that you can use to provide * one or more initialization functions. * - * The provided function are injected at application startup and executed during + * The provided functions are injected at application startup and executed during * app initialization. If any of these functions returns a Promise, initialization * does not complete until the Promise is resolved. * From d88711c2fcdf85dfccb142b53479a080f72b8391 Mon Sep 17 00:00:00 2001 From: Doug Parker Date: Mon, 20 Jul 2020 16:05:04 -0700 Subject: [PATCH 004/133] docs: add Ivy and View Engine test scripts (#38149) Developer docs previously stated to use `yarn bazel test //packages/...` which attempts to test all packages with View Engine (the default), even though not all support View Engine. This updates the doc to use `yarn test-ivy-aot` and `yarn test-non-ivy` which tests both Ivy and View Engine while filtering out tests which are not compatible with each renderer. PR Close #38149 --- docs/DEVELOPER.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md index 340f3194ea..0cb401ab7a 100644 --- a/docs/DEVELOPER.md +++ b/docs/DEVELOPER.md @@ -80,8 +80,10 @@ Bazel is used as the primary tool for building and testing Angular. Building and incremental with Bazel, and it's possible to only run tests for an individual package instead of for all packages. Read more about this in the [BAZEL.md](./BAZEL.md) document. -You should execute all test suites before submitting a PR to GitHub: -- `yarn bazel test packages/...` +You should execute all test suites before submitting a PR to GitHub. Note that not all tests +support both Ivy and View Engine, so they need to be run separately: +- `yarn test-ivy-aot //packages/...` +- `yarn test-non-ivy //packages/...` **Note**: The first test run will be much slower than future runs. This is because future runs will benefit from Bazel's capability to do incremental builds. From a1c9f7cdb3972aae9c0fe78c6a6b4d5ae105704a Mon Sep 17 00:00:00 2001 From: Georgi K <45083324+gkarapeev@users.noreply.github.com> Date: Tue, 21 Jul 2020 14:05:51 +0300 Subject: [PATCH 005/133] docs: fix typo in ng_control.ts (#38157) PR Close #38157 --- packages/forms/src/directives/ng_control.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/forms/src/directives/ng_control.ts b/packages/forms/src/directives/ng_control.ts index d8185e7381..95fe13f589 100644 --- a/packages/forms/src/directives/ng_control.ts +++ b/packages/forms/src/directives/ng_control.ts @@ -18,7 +18,7 @@ function unimplemented(): any { /** * @description - * A base class that all control `FormControl`-based directives extend. It binds a `FormControl` + * A base class that all `FormControl`-based directives extend. It binds a `FormControl` * object to a DOM element. * * @publicApi From 7491b854b38c78f3775dd94155581cd1e10f254b Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 22 Jul 2020 14:40:00 +0300 Subject: [PATCH 006/133] build(docs-infra): remove obsolete `typings.d.ts` files from angular.io and docs examples (#38173) There were two `typings.d.ts` files with SystemJS module definitions in `aio/src/` and `aio/tools/examples/shared/boilerplate/cli/`. These are remnants from old CLI versions that used SystemJS and are no longer needed. For docs examples specifically, these files were never copied over to example projects and thus not included in StackBlitz projects and ZIP archives. This commit removes these obsolete files. PR Close #38173 --- aio/src/typings.d.ts | 5 ----- aio/tools/example-zipper/exampleZipper.js | 1 - aio/tools/examples/shared/boilerplate/cli/src/typings.d.ts | 5 ----- 3 files changed, 11 deletions(-) delete mode 100644 aio/src/typings.d.ts delete mode 100644 aio/tools/examples/shared/boilerplate/cli/src/typings.d.ts diff --git a/aio/src/typings.d.ts b/aio/src/typings.d.ts deleted file mode 100644 index ef5c7bd620..0000000000 --- a/aio/src/typings.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* SystemJS module definition */ -declare var module: NodeModule; -interface NodeModule { - id: string; -} diff --git a/aio/tools/example-zipper/exampleZipper.js b/aio/tools/example-zipper/exampleZipper.js index 061c404f36..7245e2fa63 100644 --- a/aio/tools/example-zipper/exampleZipper.js +++ b/aio/tools/example-zipper/exampleZipper.js @@ -98,7 +98,6 @@ class ExampleZipper { 'src/favicon.ico', 'src/polyfills.ts', 'src/test.ts', - 'src/typings.d.ts', 'src/environments/**/*', 'src/testing/**/*', // Only ignore root package.json diff --git a/aio/tools/examples/shared/boilerplate/cli/src/typings.d.ts b/aio/tools/examples/shared/boilerplate/cli/src/typings.d.ts deleted file mode 100644 index ef5c7bd620..0000000000 --- a/aio/tools/examples/shared/boilerplate/cli/src/typings.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* SystemJS module definition */ -declare var module: NodeModule; -interface NodeModule { - id: string; -} From b3d81a837b842b199066cff04d7a0abf5422cbb8 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 22 Jul 2020 14:40:01 +0300 Subject: [PATCH 007/133] build(docs-infra): remove obsolete `systemjs.config.web[.build].js` files from docs examples (#38173) There were some `systemjs.config.web[.build].js` files in the `systemjs` boilerplate directory that are not used any more. In the past, these files were used in the Plunker-based live examples, but we no longer use Plunker for live examples. This commit removes these obsolete files. PR Close #38173 --- aio/tools/RELEASE.md | 5 - .../systemjs/src/systemjs.config.web.build.js | 101 ------------------ .../systemjs/src/systemjs.config.web.js | 87 --------------- 3 files changed, 193 deletions(-) delete mode 100644 aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web.build.js delete mode 100644 aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web.js diff --git a/aio/tools/RELEASE.md b/aio/tools/RELEASE.md index 64293bf196..a45f16bec3 100644 --- a/aio/tools/RELEASE.md +++ b/aio/tools/RELEASE.md @@ -14,11 +14,6 @@ There, select all the packages that are updated on the new Angular release. **2)** Changes to the tsconfig.json? There are several files in `/aio/tools/examples/shared/boilerplate/*/tsconfig.json` (based on the example type). -**3)** The files `/aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web[.build].js` contains the configuration for plunkers. They have some hardcoded versions that could be updated. - ->N.B.: Plunkers have been replaced by Stackblitz and (almost) all examples have be replaced by CLI/WebPack-based examples that do not use SystemJS. -The upgrade examples may still rely on SystemJS. - --- > NOTE(gkalpak): > There are some `package.json` files in `/aio/tools/examples/shared/boilerplate/*`. diff --git a/aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web.build.js b/aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web.build.js deleted file mode 100644 index 5bc80cdf45..0000000000 --- a/aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web.build.js +++ /dev/null @@ -1,101 +0,0 @@ -/* -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 http://angular.io/license -*/ - - -/** - * WEB VERSION FOR CURRENT ANGULAR BUILD - * (based on systemjs.config.js in angular.io) - * System configuration for Angular samples - * Adjust as necessary for your application needs. - * - * UNTESTED ! - */ -(function (global) { - System.config({ - // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER - transpiler: 'ts', - typescriptOptions: { - // Copy of compiler options in standard tsconfig.json - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": ["es2015", "dom"], - "noImplicitAny": true, - "suppressImplicitAnyIndexErrors": true - }, - meta: { - 'typescript': { - "exports": "ts" - } - }, - paths: { - // paths serve as alias - 'npm:': 'https://unpkg.com/', - 'ng:': 'https://cdn.rawgit.com/angular/' - }, - // map tells the System loader where to look for things - map: { - // our app is within the app folder - 'app': 'app', - - // angular bundles - '@angular/animations': 'ng:animations-builds/master/bundles/animations.umd.js', - '@angular/animations/browser': 'ng:animations-builds/master/bundles/animations-browser.umd.js', - '@angular/core': 'ng:core-builds/master/bundles/core.umd.js', - '@angular/common': 'ng:common-builds/master/bundles/common.umd.js', - '@angular/common/http': 'ng:common-builds/master/bundles/common-http.umd.js', - '@angular/compiler': 'ng:compiler-builds/master/bundles/compiler.umd.js', - '@angular/platform-browser': 'ng:platform-browser-builds/master/bundles/platform-browser.umd.js', - '@angular/platform-browser/animations': 'ng:animations-builds/master/bundles/platform-browser-animations.umd.js', - '@angular/platform-browser-dynamic': 'ng:platform-browser-dynamic-builds/master/bundles/platform-browser-dynamic.umd.js', - '@angular/router': 'ng:router-builds/master/bundles/router.umd.js', - '@angular/router/upgrade': 'ng:router-builds/master/bundles/router-upgrade.umd.js', - '@angular/forms': 'ng:forms-builds/master/bundles/forms.umd.js', - '@angular/upgrade': 'ng:upgrade-builds/master/bundles/upgrade.umd.js', - '@angular/upgrade/static': 'ng:upgrade-builds/master/bundles/upgrade-static.umd.js', - - // angular testing umd bundles (overwrite the shim mappings) - '@angular/core/testing': 'ng:core-builds/master/bundles/core-testing.umd.js', - '@angular/common/testing': 'ng:common-builds/master/bundles/common-testing.umd.js', - '@angular/common/http/testing': 'ng:common-builds/master/bundles/common-http-testing.umd.js', - '@angular/compiler/testing': 'ng:compiler-builds/master/bundles/compiler-testing.umd.js', - '@angular/platform-browser/testing': 'ng:platform-browser-builds/master/bundles/platform-browser-testing.umd.js', - '@angular/platform-browser-dynamic/testing': 'ng:platform-browser-dynamic-builds/master/bundles/platform-browser-dynamic-testing.umd.js', - '@angular/router/testing': 'ng:router-builds/master/bundles/router-testing.umd.js', - '@angular/forms/testing': 'ng:forms-builds/master/bundles/forms-testing.umd.js', - - // other libraries - 'rxjs': 'npm:rxjs@5.5.2', - 'rxjs/operators': 'npm:rxjs@5.5.2/operators/index.js', - 'tslib': 'npm:tslib/tslib.js', - 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js', - 'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js', - 'typescript': 'npm:typescript@2.4.2/lib/typescript.js', - - }, - // packages tells the System loader how to load when no filename and/or no extension - packages: { - app: { - main: './main.ts', - defaultExtension: 'ts', - meta: { - './*.ts': { - loader: 'systemjs-angular-loader.js' - } - } - }, - 'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' }, - 'rxjs/operators': {main: 'index.js', defaultExtension: 'js' }, - 'rxjs/testing': {main: 'index.js', defaultExtension: 'js' }, - 'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' }, - 'rxjs': { main: 'index.js', defaultExtension: 'js' }, - } - }); - -})(this); diff --git a/aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web.js b/aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web.js deleted file mode 100644 index e9b97f4928..0000000000 --- a/aio/tools/examples/shared/boilerplate/systemjs/src/systemjs.config.web.js +++ /dev/null @@ -1,87 +0,0 @@ -/* -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 http://angular.io/license -*/ - -/** - * WEB ANGULAR VERSION - * (based on systemjs.config.js in angular.io) - * System configuration for Angular samples - * Adjust as necessary for your application needs. - */ -(function (global) { - System.config({ - // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER - transpiler: 'ts', - typescriptOptions: { - // Copy of compiler options in standard tsconfig.json - "target": "es5", - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": ["es2015", "dom"], - "noImplicitAny": true, - "suppressImplicitAnyIndexErrors": true - }, - meta: { - 'typescript': { - "exports": "ts" - } - }, - paths: { - // paths serve as alias - 'npm:': 'https://unpkg.com/' - }, - // map tells the System loader where to look for things - map: { - // our app is within the app folder - 'app': 'app', - - // angular bundles - '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js', - '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js', - '@angular/core': 'npm:@angular/core/bundles/core.umd.js', - '@angular/common': 'npm:@angular/common/bundles/common.umd.js', - '@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.js', - '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', - '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', - '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js', - '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', - '@angular/router': 'npm:@angular/router/bundles/router.umd.js', - '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js', - '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', - '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js', - '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js', - - // other libraries - 'rxjs': 'npm:rxjs@5.5.2', - 'rxjs/operators': 'npm:rxjs@5.5.2/operators/index.js', - 'tslib': 'npm:tslib/tslib.js', - 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js', - 'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js', - 'typescript': 'npm:typescript@2.4.2/lib/typescript.js', - - }, - // packages tells the System loader how to load when no filename and/or no extension - packages: { - app: { - main: './main.ts', - defaultExtension: 'ts', - meta: { - './*.ts': { - loader: 'systemjs-angular-loader.js' - } - } - }, - 'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' }, - 'rxjs/operators': {main: 'index.js', defaultExtension: 'js' }, - 'rxjs/testing': {main: 'index.js', defaultExtension: 'js' }, - 'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' }, - 'rxjs': { main: 'index.js', defaultExtension: 'js' }, - } - }); - -})(this); From 937b89c230ab1eff4fc6b81635ce67629354de23 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 22 Jul 2020 14:40:02 +0300 Subject: [PATCH 008/133] fix(docs-infra): correctly add `polyfills.ts` file as boilerplate for `i18n` docs examples (#38173) Docs examples of type `i18n` need a slightly modified version of `polyfills.ts` that imports `@angular/localize/init`. Previously, this file was not included in `i18n` example projects for two reasons: - While the file was included in the `i18n` boilerplate files (at `aio/tools/examples/shared/boilerplate/i18n/`), it was not included in the boilerplate file list in `example-boilerplate.js`. - The file was in the wrong location: It was located at the project root instead of inside the `src/` directory. This commit addresses the above issues (i.e. adds the file to the boilerplate file list for `i18n` projects and moves the file inside the `src/` directory). PR Close #38173 --- aio/tools/examples/example-boilerplate.js | 2 +- aio/tools/examples/example-boilerplate.spec.js | 2 +- .../examples/shared/boilerplate/i18n/{ => src}/polyfills.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename aio/tools/examples/shared/boilerplate/i18n/{ => src}/polyfills.ts (100%) diff --git a/aio/tools/examples/example-boilerplate.js b/aio/tools/examples/example-boilerplate.js index 7292a43568..0bc06e8290 100644 --- a/aio/tools/examples/example-boilerplate.js +++ b/aio/tools/examples/example-boilerplate.js @@ -32,7 +32,7 @@ const cliRelativePath = BOILERPLATE_PATHS.cli.map(file => `../cli/${file}`); BOILERPLATE_PATHS.elements = [...cliRelativePath, 'package.json', 'src/polyfills.ts']; -BOILERPLATE_PATHS.i18n = [...cliRelativePath, 'angular.json', 'package.json']; +BOILERPLATE_PATHS.i18n = [...cliRelativePath, 'angular.json', 'package.json', 'src/polyfills.ts']; BOILERPLATE_PATHS['service-worker'] = [...cliRelativePath, 'angular.json', 'package.json']; diff --git a/aio/tools/examples/example-boilerplate.spec.js b/aio/tools/examples/example-boilerplate.spec.js index 1a4f8fb2ed..db9c8d5f72 100644 --- a/aio/tools/examples/example-boilerplate.spec.js +++ b/aio/tools/examples/example-boilerplate.spec.js @@ -11,7 +11,7 @@ describe('example-boilerplate tool', () => { const sharedNodeModulesDir = path.resolve(sharedDir, 'node_modules'); const BPFiles = { cli: 20, - i18n: 2, + i18n: 3, universal: 2, systemjs: 7, common: 1, diff --git a/aio/tools/examples/shared/boilerplate/i18n/polyfills.ts b/aio/tools/examples/shared/boilerplate/i18n/src/polyfills.ts similarity index 100% rename from aio/tools/examples/shared/boilerplate/i18n/polyfills.ts rename to aio/tools/examples/shared/boilerplate/i18n/src/polyfills.ts From 65da87722df75030f1f76c5bc188093e3f96e7f7 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 22 Jul 2020 14:40:02 +0300 Subject: [PATCH 009/133] fix(docs-infra): include `.gitignore` file in CLI-based docs examples (#38173) Previously, the `.gitignore` file that is part of the boilerplate files for CLI-based docs examples (located in `aio/tools/examples/shared/boilerplate/cli/`) was not added to the example projects, because it was not included in the boilerplate file list in `example-boilerplate.js`. This commit fixes it by adding the `.gitignore` file to the list. This ensures that docs examples more closely match CLI-generated projects. PR Close #38173 --- aio/content/examples/.gitignore | 1 + aio/tools/examples/example-boilerplate.js | 4 ++-- aio/tools/examples/example-boilerplate.spec.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/aio/content/examples/.gitignore b/aio/content/examples/.gitignore index bb099d3b7c..75300b0bdd 100644 --- a/aio/content/examples/.gitignore +++ b/aio/content/examples/.gitignore @@ -18,6 +18,7 @@ **/src/karma.conf.js **/.angular-cli.json **/.editorconfig +**/.gitignore **/angular.json **/tsconfig.json **/bs-config.e2e.json diff --git a/aio/tools/examples/example-boilerplate.js b/aio/tools/examples/example-boilerplate.js index 0bc06e8290..b0a4723873 100644 --- a/aio/tools/examples/example-boilerplate.js +++ b/aio/tools/examples/example-boilerplate.js @@ -16,8 +16,8 @@ const BOILERPLATE_PATHS = { 'src/assets/.gitkeep', 'browserslist', 'src/favicon.ico', 'karma.conf.js', 'src/polyfills.ts', 'src/test.ts', 'tsconfig.app.json', 'tsconfig.spec.json', 'tslint.json', 'e2e/src/app.po.ts', 'e2e/protractor-puppeteer.conf.js', - 'e2e/protractor.conf.js', 'e2e/tsconfig.json', '.editorconfig', 'angular.json', 'package.json', - 'tsconfig.json', 'tslint.json' + 'e2e/protractor.conf.js', 'e2e/tsconfig.json', '.editorconfig', '.gitignore', 'angular.json', + 'package.json', 'tsconfig.json', 'tslint.json' ], systemjs: [ 'src/systemjs-angular-loader.js', 'src/systemjs.config.js', 'src/tsconfig.json', diff --git a/aio/tools/examples/example-boilerplate.spec.js b/aio/tools/examples/example-boilerplate.spec.js index db9c8d5f72..4fb314fb08 100644 --- a/aio/tools/examples/example-boilerplate.spec.js +++ b/aio/tools/examples/example-boilerplate.spec.js @@ -10,7 +10,7 @@ describe('example-boilerplate tool', () => { const sharedDir = path.resolve(__dirname, 'shared'); const sharedNodeModulesDir = path.resolve(sharedDir, 'node_modules'); const BPFiles = { - cli: 20, + cli: 21, i18n: 3, universal: 2, systemjs: 7, From 5a733629b531083fd35563ee5bc286a8954da5fc Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 22 Jul 2020 14:40:03 +0300 Subject: [PATCH 010/133] build(docs-infra): remove boilerplate file listings in `example-boilerplate.js` (#38173) To avoid unnecessary code duplication in docs examples, we have some boilerplate files for various example types (in `aio/tools/examples/shared/boilerplate/`). These files are copied to each example project in `aio/content/examples/` (according to the example's type, as specified in its `example-config.json` file). Previously, the `example-boilerplate.js`, which is responsible for copying the boilerplate files, had lists for files to be copied for each project type and only copied the listed files from the boilerplate directory to the example directory. This approach had some drawbacks: - Files need to be updated in two separate locations: in the boilerplate directory that includes the files and the file list in `example-boilerplate.js`. - It is easy to add a file in the boilerplate directory but forget to add it in `example-boilerplate.js` and not realize that it is not being included in the example project (including the generated StackBlitz project and ZIP archive). This commit changes the approach by removing the boilerplate file listings from `example-boilerplate.js` and copying all files from a boilerplate directory to example directories. This addresses the above drawbacks and simplifies the `example-boilerplate.js` script. I have verified that the resulting code example doc regions as well as the generated StackBlitz projects and ZIP archives are identical to the ones generated before this commit. PR Close #38173 --- aio/tools/examples/example-boilerplate.js | 120 +++----- .../examples/example-boilerplate.spec.js | 284 +++++++++++++----- 2 files changed, 247 insertions(+), 157 deletions(-) diff --git a/aio/tools/examples/example-boilerplate.js b/aio/tools/examples/example-boilerplate.js index b0a4723873..bc1c939656 100644 --- a/aio/tools/examples/example-boilerplate.js +++ b/aio/tools/examples/example-boilerplate.js @@ -6,65 +6,13 @@ const yargs = require('yargs'); const SHARED_PATH = path.resolve(__dirname, 'shared'); const SHARED_NODE_MODULES_PATH = path.resolve(SHARED_PATH, 'node_modules'); + const BOILERPLATE_BASE_PATH = path.resolve(SHARED_PATH, 'boilerplate'); -const BOILERPLATE_COMMON_BASE_PATH = path.resolve(BOILERPLATE_BASE_PATH, 'common'); +const BOILERPLATE_CLI_PATH = path.resolve(BOILERPLATE_BASE_PATH, 'cli'); +const BOILERPLATE_COMMON_PATH = path.resolve(BOILERPLATE_BASE_PATH, 'common'); +const BOILERPLATE_VIEWENGINE_PATH = path.resolve(BOILERPLATE_BASE_PATH, 'viewengine'); + const EXAMPLES_BASE_PATH = path.resolve(__dirname, '../../content/examples'); - -const BOILERPLATE_PATHS = { - cli: [ - 'src/environments/environment.prod.ts', 'src/environments/environment.ts', - 'src/assets/.gitkeep', 'browserslist', 'src/favicon.ico', 'karma.conf.js', - 'src/polyfills.ts', 'src/test.ts', 'tsconfig.app.json', 'tsconfig.spec.json', - 'tslint.json', 'e2e/src/app.po.ts', 'e2e/protractor-puppeteer.conf.js', - 'e2e/protractor.conf.js', 'e2e/tsconfig.json', '.editorconfig', '.gitignore', 'angular.json', - 'package.json', 'tsconfig.json', 'tslint.json' - ], - systemjs: [ - 'src/systemjs-angular-loader.js', 'src/systemjs.config.js', 'src/tsconfig.json', - 'bs-config.json', 'bs-config.e2e.json', 'package.json', 'tslint.json' - ], - common: ['src/styles.css'] -}; - -// All paths in this tool are relative to the current boilerplate folder, i.e boilerplate/i18n -// This maps the CLI files that exists in a parent folder -const cliRelativePath = BOILERPLATE_PATHS.cli.map(file => `../cli/${file}`); - -BOILERPLATE_PATHS.elements = [...cliRelativePath, 'package.json', 'src/polyfills.ts']; - -BOILERPLATE_PATHS.i18n = [...cliRelativePath, 'angular.json', 'package.json', 'src/polyfills.ts']; - -BOILERPLATE_PATHS['service-worker'] = [...cliRelativePath, 'angular.json', 'package.json']; - -BOILERPLATE_PATHS.testing = [ - ...cliRelativePath, - 'angular.json', - 'tsconfig.app.json', - 'tsconfig.spec.json' -]; - -BOILERPLATE_PATHS.universal = [...cliRelativePath, 'angular.json', 'package.json']; - -BOILERPLATE_PATHS['getting-started'] = [ - ...cliRelativePath, - 'src/styles.css' -]; - -BOILERPLATE_PATHS.schematics = [ - ...cliRelativePath, - 'angular.json' -]; - -BOILERPLATE_PATHS['cli-ajs'] = [ - ...cliRelativePath, - 'package.json' -]; - -BOILERPLATE_PATHS.viewengine = { - systemjs: ['rollup-config.js', 'tsconfig-aot.json'], - cli: ['tsconfig.json'] -}; - const EXAMPLE_CONFIG_FILENAME = 'example-config.json'; class ExampleBoilerPlate { @@ -96,24 +44,26 @@ class ExampleBoilerPlate { const boilerPlateType = exampleConfig.projectType || 'cli'; const boilerPlateBasePath = path.resolve(BOILERPLATE_BASE_PATH, boilerPlateType); - // Copy the boilerplate specific files - BOILERPLATE_PATHS[boilerPlateType].forEach( - filePath => this.copyFile(boilerPlateBasePath, exampleFolder, filePath)); + // All example types other than `cli` and `systemjs` are based on `cli`. Copy over the `cli` + // boilerplate files first. + // (Some of these files might be later overwritten by type-specific files.) + if (boilerPlateType !== 'cli' && boilerPlateType !== 'systemjs') { + this.copyDirectoryContents(BOILERPLATE_CLI_PATH, exampleFolder); + } - // Copy the boilerplate common files - const useCommonBoilerplate = exampleConfig.useCommonBoilerplate !== false; + // Copy the type-specific boilerplate files. + this.copyDirectoryContents(boilerPlateBasePath, exampleFolder); - if (useCommonBoilerplate) { - BOILERPLATE_PATHS.common.forEach(filePath => this.copyFile(BOILERPLATE_COMMON_BASE_PATH, exampleFolder, filePath)); + // Copy the common boilerplate files (unless explicitly not used). + if (exampleConfig.useCommonBoilerplate !== false) { + this.copyDirectoryContents(BOILERPLATE_COMMON_PATH, exampleFolder); } // Copy ViewEngine (pre-Ivy) specific files if (viewengine) { const veBoilerPlateType = boilerPlateType === 'systemjs' ? 'systemjs' : 'cli'; - const veBoilerPlateBasePath = - path.resolve(BOILERPLATE_BASE_PATH, 'viewengine', veBoilerPlateType); - BOILERPLATE_PATHS.viewengine[veBoilerPlateType].forEach( - filePath => this.copyFile(veBoilerPlateBasePath, exampleFolder, filePath)); + const veBoilerPlateBasePath = path.resolve(BOILERPLATE_VIEWENGINE_PATH, veBoilerPlateType); + this.copyDirectoryContents(veBoilerPlateBasePath, exampleFolder); } }); } @@ -137,22 +87,28 @@ class ExampleBoilerPlate { return glob.sync(pattern, {ignore: [ignorePattern]}).map(file => path.dirname(file)); } - copyFile(sourceFolder, destinationFolder, filePath) { - const sourcePath = path.resolve(sourceFolder, filePath); - - // normalize path if needed - filePath = this.normalizePath(filePath); - - const destinationPath = path.resolve(destinationFolder, filePath); - fs.copySync(sourcePath, destinationPath, {overwrite: true}); - fs.chmodSync(destinationPath, 444); - } - loadJsonFile(filePath) { return fs.readJsonSync(filePath, {throws: false}) || {}; } - normalizePath(filePath) { - // transform for example ../cli/src/tsconfig.app.json to src/tsconfig.app.json - return filePath.replace(/\.{2}\/\w+\//, ''); + copyDirectoryContents(srcDir, dstDir) { + shelljs.ls('-Al', srcDir).forEach(stat => { + const srcPath = path.resolve(srcDir, stat.name); + const dstPath = path.resolve(dstDir, stat.name); + + if (stat.isDirectory()) { + // `srcPath` is a directory: Recursively copy it to `dstDir`. + shelljs.mkdir('-p', dstPath); + return this.copyDirectoryContents(srcPath, dstPath); + } else { + // `srcPath` is a file: Copy it to `dstDir`. + // (Also make the file non-writable to avoid accidental editing of boilerplate files). + if (shelljs.test('-f', dstPath)) { + // If the file already exists, ensure it is writable (so it can be overwritten). + shelljs.chmod(666, dstPath); + } + shelljs.cp(srcPath, dstDir); + shelljs.chmod(444, dstPath); + } + }); } } diff --git a/aio/tools/examples/example-boilerplate.spec.js b/aio/tools/examples/example-boilerplate.spec.js index 4fb314fb08..cff911a1c3 100644 --- a/aio/tools/examples/example-boilerplate.spec.js +++ b/aio/tools/examples/example-boilerplate.spec.js @@ -9,24 +9,13 @@ describe('example-boilerplate tool', () => { describe('add', () => { const sharedDir = path.resolve(__dirname, 'shared'); const sharedNodeModulesDir = path.resolve(sharedDir, 'node_modules'); - const BPFiles = { - cli: 21, - i18n: 3, - universal: 2, - systemjs: 7, - common: 1, - viewengine: { - cli: 1, - systemjs: 2, - }, - }; const exampleFolders = ['a/b', 'c/d']; beforeEach(() => { spyOn(fs, 'ensureSymlinkSync'); spyOn(fs, 'existsSync').and.returnValue(true); spyOn(shelljs, 'exec'); - spyOn(exampleBoilerPlate, 'copyFile'); + spyOn(exampleBoilerPlate, 'copyDirectoryContents'); spyOn(exampleBoilerPlate, 'getFoldersContaining').and.returnValue(exampleFolders); spyOn(exampleBoilerPlate, 'loadJsonFile').and.returnValue({}); }); @@ -61,58 +50,81 @@ describe('example-boilerplate tool', () => { it('should copy all the source boilerplate files for systemjs', () => { const boilerplateDir = path.resolve(sharedDir, 'boilerplate'); - exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { projectType: 'systemjs' } : {}); + exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'systemjs' }); + exampleBoilerPlate.add(); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes( - (BPFiles.cli) + - (BPFiles.systemjs) + - (BPFiles.common * exampleFolders.length) - ); - // for example - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/systemjs`, 'a/b', 'package.json'); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/common`, 'a/b', 'src/styles.css'); + + expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4); + expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([ + [`${boilerplateDir}/systemjs`, 'a/b'], + [`${boilerplateDir}/common`, 'a/b'], + [`${boilerplateDir}/systemjs`, 'c/d'], + [`${boilerplateDir}/common`, 'c/d'], + ]); }); it('should copy all the source boilerplate files for cli', () => { const boilerplateDir = path.resolve(sharedDir, 'boilerplate'); + exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'cli' }); + exampleBoilerPlate.add(); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes( - (BPFiles.cli * exampleFolders.length) + - (BPFiles.common * exampleFolders.length) - ); - // for example - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/cli`, 'a/b', 'package.json'); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/common`, 'c/d', 'src/styles.css'); + + expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4); + expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([ + [`${boilerplateDir}/cli`, 'a/b'], + [`${boilerplateDir}/common`, 'a/b'], + [`${boilerplateDir}/cli`, 'c/d'], + [`${boilerplateDir}/common`, 'c/d'], + ]); }); - it('should copy all the source boilerplate files for i18n', () => { + it('should default to `cli` if `projectType` is not specified', () => { const boilerplateDir = path.resolve(sharedDir, 'boilerplate'); - exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { projectType: 'i18n' } : {}) + exampleBoilerPlate.loadJsonFile.and.returnValue({}); + exampleBoilerPlate.add(); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes( - (BPFiles.cli + BPFiles.i18n) + - (BPFiles.cli) + - (BPFiles.common * exampleFolders.length) - ); - // for example - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/i18n`, 'a/b', '../cli/angular.json'); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/i18n`, 'a/b', 'package.json'); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/common`, 'c/d', 'src/styles.css'); + + expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4); + expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([ + [`${boilerplateDir}/cli`, 'a/b'], + [`${boilerplateDir}/common`, 'a/b'], + [`${boilerplateDir}/cli`, 'c/d'], + [`${boilerplateDir}/common`, 'c/d'], + ]); }); - it('should copy all the source boilerplate files for universal', () => { + it('should copy all the source boilerplate files for i18n (on top of the cli ones)', () => { const boilerplateDir = path.resolve(sharedDir, 'boilerplate'); - exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { projectType: 'universal' } : {}) + exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'i18n' }); + exampleBoilerPlate.add(); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes( - (BPFiles.cli + BPFiles.universal) + - (BPFiles.cli) + - (BPFiles.common * exampleFolders.length) - ); - // for example - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/universal`, 'a/b', '../cli/tslint.json'); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/universal`, 'a/b', 'angular.json'); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/common`, 'c/d', 'src/styles.css'); + + expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6); + expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([ + [`${boilerplateDir}/cli`, 'a/b'], + [`${boilerplateDir}/i18n`, 'a/b'], + [`${boilerplateDir}/common`, 'a/b'], + [`${boilerplateDir}/cli`, 'c/d'], + [`${boilerplateDir}/i18n`, 'c/d'], + [`${boilerplateDir}/common`, 'c/d'], + ]); + }); + + it('should copy all the source boilerplate files for universal (on top of the cli ones)', () => { + const boilerplateDir = path.resolve(sharedDir, 'boilerplate'); + exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'universal' }); + + exampleBoilerPlate.add(); + + expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6); + expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([ + [`${boilerplateDir}/cli`, 'a/b'], + [`${boilerplateDir}/universal`, 'a/b'], + [`${boilerplateDir}/common`, 'a/b'], + [`${boilerplateDir}/cli`, 'c/d'], + [`${boilerplateDir}/universal`, 'c/d'], + [`${boilerplateDir}/common`, 'c/d'], + ]); }); it('should try to load the example config file', () => { @@ -130,27 +142,55 @@ describe('example-boilerplate tool', () => { it('should copy all the source boilerplate files for systemjs', () => { const boilerplateDir = path.resolve(sharedDir, 'boilerplate'); - exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { projectType: 'systemjs' } : {}); + exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'systemjs' }); + exampleBoilerPlate.add(true); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes( - (BPFiles.cli + BPFiles.viewengine.cli) + - (BPFiles.systemjs + BPFiles.viewengine.systemjs) + - (BPFiles.common * exampleFolders.length) - ); - // for example - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/viewengine/systemjs`, 'a/b', 'tsconfig-aot.json'); + + expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6); + expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([ + [`${boilerplateDir}/systemjs`, 'a/b'], + [`${boilerplateDir}/common`, 'a/b'], + [`${boilerplateDir}/viewengine/systemjs`, 'a/b'], + [`${boilerplateDir}/systemjs`, 'c/d'], + [`${boilerplateDir}/common`, 'c/d'], + [`${boilerplateDir}/viewengine/systemjs`, 'c/d'], + ]); }); it('should copy all the source boilerplate files for cli', () => { const boilerplateDir = path.resolve(sharedDir, 'boilerplate'); + exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'cli' }); + exampleBoilerPlate.add(true); - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes( - (BPFiles.cli * exampleFolders.length) + - (BPFiles.viewengine.cli * exampleFolders.length) + - (BPFiles.common * exampleFolders.length) - ); - // for example - expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(`${boilerplateDir}/viewengine/cli`, 'a/b', 'tsconfig.json'); + + expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6); + expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([ + [`${boilerplateDir}/cli`, 'a/b'], + [`${boilerplateDir}/common`, 'a/b'], + [`${boilerplateDir}/viewengine/cli`, 'a/b'], + [`${boilerplateDir}/cli`, 'c/d'], + [`${boilerplateDir}/common`, 'c/d'], + [`${boilerplateDir}/viewengine/cli`, 'c/d'], + ]); + }); + + it('should copy all the source boilerplate files for elements', () => { + const boilerplateDir = path.resolve(sharedDir, 'boilerplate'); + exampleBoilerPlate.loadJsonFile.and.returnValue({ projectType: 'elements' }); + + exampleBoilerPlate.add(true); + + expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(8); + expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([ + [`${boilerplateDir}/cli`, 'a/b'], + [`${boilerplateDir}/elements`, 'a/b'], + [`${boilerplateDir}/common`, 'a/b'], + [`${boilerplateDir}/viewengine/cli`, 'a/b'], + [`${boilerplateDir}/cli`, 'c/d'], + [`${boilerplateDir}/elements`, 'c/d'], + [`${boilerplateDir}/common`, 'c/d'], + [`${boilerplateDir}/viewengine/cli`, 'c/d'], + ]); }); }); }); @@ -172,16 +212,110 @@ describe('example-boilerplate tool', () => { }); }); - describe('copyFile', () => { - it('should use copySync and chmodSync', () => { - spyOn(fs, 'copySync'); - spyOn(fs, 'chmodSync'); - exampleBoilerPlate.copyFile('source/folder', 'destination/folder', 'some/file/path'); - expect(fs.copySync).toHaveBeenCalledWith( - path.resolve('source/folder/some/file/path'), - path.resolve('destination/folder/some/file/path'), - { overwrite: true }); - expect(fs.chmodSync).toHaveBeenCalledWith(path.resolve('destination/folder/some/file/path'), 444); + describe('copyDirectoryContents', () => { + const spyFnFor = fnName => (...args) => { callLog.push(`${fnName}(${args.join(', ')})`); }; + let callLog; + + beforeEach(() => { + callLog = []; + spyOn(shelljs, 'chmod').and.callFake(spyFnFor('chmod')); + spyOn(shelljs, 'cp').and.callFake(spyFnFor('cp')); + spyOn(shelljs, 'mkdir').and.callFake(spyFnFor('mkdir')); + spyOn(shelljs, 'test').and.callFake(spyFnFor('test')); + }); + + it('should list all contents of a directory', () => { + const lsSpy = spyOn(shelljs, 'ls').and.returnValue([]); + exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir'); + expect(lsSpy).toHaveBeenCalledWith('-Al', 'source/dir'); + }); + + it('should use copy files and make them read-only', () => { + spyOn(shelljs, 'ls').and.returnValue([ + {name: 'file-1.txt', isDirectory: () => false}, + {name: 'file-2.txt', isDirectory: () => false}, + ]); + + exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir'); + + expect(callLog).toEqual([ + `test(-f, ${path.resolve('destination/dir/file-1.txt')})`, + `cp(${path.resolve('source/dir/file-1.txt')}, destination/dir)`, + `chmod(444, ${path.resolve('destination/dir/file-1.txt')})`, + + `test(-f, ${path.resolve('destination/dir/file-2.txt')})`, + `cp(${path.resolve('source/dir/file-2.txt')}, destination/dir)`, + `chmod(444, ${path.resolve('destination/dir/file-2.txt')})`, + ]); + }); + + it('should make existing files in destination writable before overwriting', () => { + spyOn(shelljs, 'ls').and.returnValue([ + {name: 'new-file.txt', isDirectory: () => false}, + {name: 'existing-file.txt', isDirectory: () => false}, + ]); + shelljs.test.and.callFake((_, filePath) => filePath.endsWith('existing-file.txt')); + + exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir'); + + expect(callLog).toEqual([ + `cp(${path.resolve('source/dir/new-file.txt')}, destination/dir)`, + `chmod(444, ${path.resolve('destination/dir/new-file.txt')})`, + + `chmod(666, ${path.resolve('destination/dir/existing-file.txt')})`, + `cp(${path.resolve('source/dir/existing-file.txt')}, destination/dir)`, + `chmod(444, ${path.resolve('destination/dir/existing-file.txt')})`, + ]); + }); + + it('should recursively copy sub-directories', () => { + spyOn(shelljs, 'ls') + .withArgs('-Al', 'source/dir').and.returnValue([ + {name: 'file-1.txt', isDirectory: () => false}, + {name: 'sub-dir-1', isDirectory: () => true}, + {name: 'file-2.txt', isDirectory: () => false}, + ]) + .withArgs('-Al', path.resolve('source/dir/sub-dir-1')).and.returnValue([ + {name: 'file-3.txt', isDirectory: () => false}, + {name: 'sub-dir-2', isDirectory: () => true}, + ]) + .withArgs('-Al', path.resolve('source/dir/sub-dir-1/sub-dir-2')).and.returnValue([ + {name: 'file-4.txt', isDirectory: () => false}, + ]); + + exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir'); + + expect(callLog).toEqual([ + // Copy `file-1.txt`. + `test(-f, ${path.resolve('destination/dir/file-1.txt')})`, + `cp(${path.resolve('source/dir/file-1.txt')}, destination/dir)`, + `chmod(444, ${path.resolve('destination/dir/file-1.txt')})`, + + // Create `sub-dir-1` and recursively copy its contents. + `mkdir(-p, ${path.resolve('destination/dir/sub-dir-1')})`, + + // Copy `sub-dir-1/file-3.txt`. + `test(-f, ${path.resolve('destination/dir/sub-dir-1/file-3.txt')})`, + 'cp(' + + `${path.resolve('source/dir/sub-dir-1/file-3.txt')}, ` + + `${path.resolve('destination/dir/sub-dir-1')})`, + `chmod(444, ${path.resolve('destination/dir/sub-dir-1/file-3.txt')})`, + + // Create `sub-dir-1/sub-dir-2` and recursively copy its contents. + `mkdir(-p, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2')})`, + + // Copy `sub-dir-1/sub-dir-2/file-4.txt`. + `test(-f, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2/file-4.txt')})`, + 'cp(' + + `${path.resolve('source/dir/sub-dir-1/sub-dir-2/file-4.txt')}, ` + + `${path.resolve('destination/dir/sub-dir-1/sub-dir-2')})`, + `chmod(444, ${path.resolve('destination/dir/sub-dir-1/sub-dir-2/file-4.txt')})`, + + // Copy `file-2.txt`. + `test(-f, ${path.resolve('destination/dir/file-2.txt')})`, + `cp(${path.resolve('source/dir/file-2.txt')}, destination/dir)`, + `chmod(444, ${path.resolve('destination/dir/file-2.txt')})`, + ]); }); }); From 778ad3776a5748854c805f58fec60a8cc0018be5 Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Tue, 23 Jun 2020 22:32:40 -0700 Subject: [PATCH 011/133] docs: create coding standards doc (#37700) Create initial document for Angular framework coding standards. This document will evolve over time. This version contains all non-controversial rules as discussed in a recent team meeting. Some text and examples were copied from angular/components. PR Close #37700 --- .pullapprove.yml | 1 + docs/CODING_STANDARDS.md | 254 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 docs/CODING_STANDARDS.md diff --git a/.pullapprove.yml b/.pullapprove.yml index 5d8ca326ac..78a460640a 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -1090,6 +1090,7 @@ groups: 'dev-infra/**', 'docs/BAZEL.md', 'docs/CARETAKER.md', + 'docs/CODING_STANDARDS.md', 'docs/COMMITTER.md', 'docs/DEBUG.md', 'docs/DEBUG_COMPONENTS_REPO_IVY.md', diff --git a/docs/CODING_STANDARDS.md b/docs/CODING_STANDARDS.md new file mode 100644 index 0000000000..7d9030cd9b --- /dev/null +++ b/docs/CODING_STANDARDS.md @@ -0,0 +1,254 @@ +# Angular Framework Coding Standards + +The coding practices in this doc apply only to development on Angular itself, not applications +built _with_ Angular. (Though you can follow them too if you really want). + +## Code style + +The [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) is the +basis for Angular's coding style, with additional guidance here pertaining to TypeScript. The team +uses `clang-format` to automatically format code; automatic formatting is enforced by CI. + +## Code practices + +### Write useful comments + +Comments that explain what some block of code does are nice; they can tell you something in less +time than it would take to follow through the code itself. + +Comments that explain why some block of code exists at all, or does something the way it does, +are _invaluable_. The "why" is difficult, or sometimes impossible, to track down without seeking out +the original author. When collaborators are in the same room, this hurts productivity. +When collaborators are in different timezones, this can be devastating to productivity. + +For example, this is a not-very-useful comment: +```typescript +// Set default tabindex. +if (!attributes['tabindex']) { + element.setAttribute('tabindex', '-1'); +} +``` + +While this is much more useful: +```typescript +// Unless the user specifies otherwise, the calendar should not be a tab stop. +// This prevents ngAria from overzealously adding a tabindex to anything with an ng-model. +if (!attributes['tabindex']) { + element.setAttribute('tabindex', '-1'); +} +``` + +In TypeScript code, use JsDoc-style comments for descriptions (on classes, members, etc.) and +use `//` style comments for everything else (explanations, background info, etc.). + +### API Design + +#### Boolean arguments + +Generally avoid adding boolean arguments to a method in cases where that argument means +"do something extra". In these cases, prefer breaking the behavior up into different functions. + +```typescript +// AVOID +function getTargetElement(createIfNotFound = false) { + // ... +} +``` + +```typescript +// PREFER +function getExistingTargetElement() { + // ... +} + +function createTargetElement() { + // ... +} +``` + +You can ignore this guidance when necessary for performance reasons in framework code. + +#### Optional arguments + +Use optional function arguments only when such an argument makes sense for an API or when required +for performance. Don't use optional arguments merely for convenience in implementation. + +### TypeScript + +#### Typing + +Avoid `any` where possible. If you find yourself using `any`, consider whether a generic or +`unknown` may be appropriate in your case. + +#### Getters and Setters + +Getters and setters introduce openings for side-effects, add more complexity for code readers, +and generate additional code when targeting older browsers. + +* Only use getters and setters for `@Input` properties or when otherwise required for API +compatibility. +* Avoid long or complex getters and setters. If the logic of an accessor would take more than +three lines, introduce a new method to contain the logic. +* A getter should immediately precede its corresponding setter. +* Decorators such as `@Input` should be applied to the getter and not the setter. +* Always use a `readonly` property instead of a getter (with no setter) when possible. + + ```typescript + /** YES */ + readonly active: boolean; + + /** NO */ + get active(): boolean { + // Using a getter solely to make the property read-only. + return this._active; + } + ``` + +#### Iteration + +Prefer `for` or `for of` to `Array.prototype.forEach`. The `forEach` API makes debugging harder +and may increase overhead from unnecessary function invocations (though modern browsers do usually +optimize this well). + +#### JsDoc comments + +All public APIs must have user-facing comments. These are extracted for API documentation and shown +in IDEs. + +Private and internal APIs should have JsDoc when they are not obvious. Ultimately it is the purview +of the code reviewer as to what is "obvious", but the rule of thumb is that *most* classes, +properties, and methods should have a JsDoc description. + +Properties should have a concise description of what the property means: +```typescript + /** The label position relative to the checkbox. Defaults to 'after' */ + @Input() labelPosition: 'before' | 'after' = 'after'; +``` + +Methods blocks should describe what the function does and provide a description for each parameter +and the return value: +```typescript + /** + * Opens a modal dialog containing the given component. + * @param component Type of the component to load into the dialog. + * @param config Dialog configuration options. + * @returns Reference to the newly-opened dialog. + */ + open(component: ComponentType, config?: MatDialogConfig): MatDialogRef { ... } +``` + +Boolean properties and return values should use "Whether..." as opposed to "True if...": +```ts + /** Whether the button is disabled. */ + disabled: boolean = false; +``` + +#### Try-Catch + +Only use `try-catch` blocks when dealing with legitimately unexpected errors. Don't use `try` to +avoid checking for expected error conditions such as null dereference or out-of-bound array access. + +Each `try-catch` block **must** include a comment that explains the +specific error being caught and why it cannot be prevented. + +##### Variable declarations + +Prefer `const` wherever possible, only using `let` when a value must change. Avoid `var` unless +absolutely necessary. + +##### `readonly` + +Use `readonly` members wherever possible. + +#### Naming + +##### General + +* Prefer writing out words instead of using abbreviations. +* Prefer *exact* names over short names (within reason). For example, `labelPosition` is better than +`align` because the former much more exactly communicates what the property means. +* Except for `@Input()` properties, use `is` and `has` prefixes for boolean properties / methods. + +##### Observables + +Don't suffix observables with `$`. + +##### Classes + +Name classes based on their responsibility. Names should capture what the code *does*, +not how it is used: +```typescript +/** NO: */ +class DefaultRouteReuseStrategy { } + +/** YES: */ +class NonStoringRouteReuseStrategy { } +``` + +##### Methods + +The name of a method should capture the action performed *by* that method rather than +describing when the method will be called. For example: + +```typescript +/** AVOID: does not describe what the function does. */ +handleClick() { + // ... +} + +/** PREFER: describes the action performed by the function. */ +activateRipple() { + // ... +} +``` + +##### Test classes and examples + +Give test classes and examples meaningful, descriptive names. + +```ts +/** PREFER: describes the scenario under test. */ +class FormGroupWithCheckboxAndRadios { /* ... */ } +class InputWithNgModel { /* ... */ } + + +/** AVOID: does not fully describe the scenario under test. */ +class Comp { /* ... */ } +class InputComp { /* ... */ } +``` + +#### RxJS + +When importing the `of` function to create an `Observable` from a value, alias the imported +function as `observableOf`. + +```typescript +import {of as observableOf} from 'rxjs'; +``` + +#### Testing + +##### Test names + +Use descriptive names for jasmine tests. Ideally, test names should read as a sentence, often of +the form "it should...". + +```typescript +/** PREFER: describes the scenario under test. */ +describe('Router', () => { + describe('with the default route reuse strategy', () => { + it('should not reuse routes upon location change', () => { + // ... + }); + }) +}); + +/** AVOID: does not fully describe the scenario under test. */ +describe('Router', () => { + describe('default strategy', () => { + it('should work', () => { + // ... + }); + }) +}); +``` From 8df888dfb48c2b272798d10af2b2d6c1415a0aec Mon Sep 17 00:00:00 2001 From: remackgeek Date: Sun, 28 Jun 2020 17:18:19 -0700 Subject: [PATCH 012/133] fix(elements): run strategy methods in correct zone (#37814) Default change detection fails in some cases for @angular/elements where component events are called from the wrong zone. This fixes the issue by running all ComponentNgElementStrategy methods in the same zone it was created in. Fixes #24181 PR Close #37814 --- goldens/size-tracking/aio-payloads.json | 4 +- .../src/component-factory-strategy.ts | 101 +++++++++++------- .../test/component-factory-strategy_spec.ts | 48 ++++++++- 3 files changed, 108 insertions(+), 45 deletions(-) diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index 4dbcab3374..9d240aea2f 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 2987, - "main-es2015": 450301, + "main-es2015": 450596, "polyfills-es2015": 52630 } } @@ -21,7 +21,7 @@ "master": { "uncompressed": { "runtime-es2015": 3097, - "main-es2015": 429200, + "main-es2015": 429885, "polyfills-es2015": 52195 } } diff --git a/packages/elements/src/component-factory-strategy.ts b/packages/elements/src/component-factory-strategy.ts index 799cfba683..35db08958a 100644 --- a/packages/elements/src/component-factory-strategy.ts +++ b/packages/elements/src/component-factory-strategy.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ApplicationRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, Type} from '@angular/core'; +import {ApplicationRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, EventEmitter, Injector, NgZone, OnChanges, SimpleChange, SimpleChanges, Type} from '@angular/core'; import {merge, Observable, ReplaySubject} from 'rxjs'; import {map, switchMap} from 'rxjs/operators'; @@ -73,6 +73,13 @@ export class ComponentNgElementStrategy implements NgElementStrategy { */ private readonly unchangedInputs = new Set(); + /** Service for setting zone context. */ + private readonly ngZone = this.injector.get(NgZone); + + /** The zone the element was created in or `null` if Zone.js is not loaded. */ + private readonly elementZone = + (typeof Zone === 'undefined') ? null : this.ngZone.run(() => Zone.current); + constructor(private componentFactory: ComponentFactory, private injector: Injector) {} /** @@ -80,16 +87,19 @@ export class ComponentNgElementStrategy implements NgElementStrategy { * destruction. */ connect(element: HTMLElement) { - // If the element is marked to be destroyed, cancel the task since the component was reconnected - if (this.scheduledDestroyFn !== null) { - this.scheduledDestroyFn(); - this.scheduledDestroyFn = null; - return; - } + this.runInZone(() => { + // If the element is marked to be destroyed, cancel the task since the component was + // reconnected + if (this.scheduledDestroyFn !== null) { + this.scheduledDestroyFn(); + this.scheduledDestroyFn = null; + return; + } - if (this.componentRef === null) { - this.initializeComponent(element); - } + if (this.componentRef === null) { + this.initializeComponent(element); + } + }); } /** @@ -97,19 +107,21 @@ export class ComponentNgElementStrategy implements NgElementStrategy { * being moved across the DOM. */ disconnect() { - // Return if there is no componentRef or the component is already scheduled for destruction - if (this.componentRef === null || this.scheduledDestroyFn !== null) { - return; - } - - // Schedule the component to be destroyed after a small timeout in case it is being - // moved elsewhere in the DOM - this.scheduledDestroyFn = scheduler.schedule(() => { - if (this.componentRef !== null) { - this.componentRef.destroy(); - this.componentRef = null; + this.runInZone(() => { + // Return if there is no componentRef or the component is already scheduled for destruction + if (this.componentRef === null || this.scheduledDestroyFn !== null) { + return; } - }, DESTROY_DELAY); + + // Schedule the component to be destroyed after a small timeout in case it is being + // moved elsewhere in the DOM + this.scheduledDestroyFn = scheduler.schedule(() => { + if (this.componentRef !== null) { + this.componentRef.destroy(); + this.componentRef = null; + } + }, DESTROY_DELAY); + }); } /** @@ -117,11 +129,13 @@ export class ComponentNgElementStrategy implements NgElementStrategy { * retrieved from the cached initialization values. */ getInputValue(property: string): any { - if (this.componentRef === null) { - return this.initialInputValues.get(property); - } + return this.runInZone(() => { + if (this.componentRef === null) { + return this.initialInputValues.get(property); + } - return this.componentRef.instance[property]; + return this.componentRef.instance[property]; + }); } /** @@ -129,22 +143,24 @@ export class ComponentNgElementStrategy implements NgElementStrategy { * cached and set when the component is created. */ setInputValue(property: string, value: any): void { - if (this.componentRef === null) { - this.initialInputValues.set(property, value); - return; - } + this.runInZone(() => { + if (this.componentRef === null) { + this.initialInputValues.set(property, value); + return; + } - // Ignore the value if it is strictly equal to the current value, except if it is `undefined` - // and this is the first change to the value (because an explicit `undefined` _is_ strictly - // equal to not having a value set at all, but we still need to record this as a change). - if (strictEquals(value, this.getInputValue(property)) && - !((value === undefined) && this.unchangedInputs.has(property))) { - return; - } + // Ignore the value if it is strictly equal to the current value, except if it is `undefined` + // and this is the first change to the value (because an explicit `undefined` _is_ strictly + // equal to not having a value set at all, but we still need to record this as a change). + if (strictEquals(value, this.getInputValue(property)) && + !((value === undefined) && this.unchangedInputs.has(property))) { + return; + } - this.recordInputChange(property, value); - this.componentRef.instance[property] = value; - this.scheduleDetectChanges(); + this.recordInputChange(property, value); + this.componentRef.instance[property] = value; + this.scheduleDetectChanges(); + }); } /** @@ -264,4 +280,9 @@ export class ComponentNgElementStrategy implements NgElementStrategy { this.callNgOnChanges(this.componentRef); this.componentRef.changeDetectorRef.detectChanges(); } + + /** Runs in the angular zone, if present. */ + private runInZone(fn: () => unknown) { + return (this.elementZone && Zone.current !== this.elementZone) ? this.ngZone.run(fn) : fn(); + } } diff --git a/packages/elements/test/component-factory-strategy_spec.ts b/packages/elements/test/component-factory-strategy_spec.ts index 53c9fbd0fc..577392fff2 100644 --- a/packages/elements/test/component-factory-strategy_spec.ts +++ b/packages/elements/test/component-factory-strategy_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ComponentFactory, ComponentRef, Injector, NgModuleRef, SimpleChange, SimpleChanges, Type} from '@angular/core'; +import {ApplicationRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, Injector, NgModuleRef, NgZone, SimpleChange, SimpleChanges, Type} from '@angular/core'; import {fakeAsync, tick} from '@angular/core/testing'; import {Subject} from 'rxjs'; @@ -20,22 +20,40 @@ describe('ComponentFactoryNgElementStrategy', () => { let injector: any; let componentRef: any; let applicationRef: any; + let ngZone: any; + + let injectables: Map; beforeEach(() => { factory = new FakeComponentFactory(); componentRef = factory.componentRef; applicationRef = jasmine.createSpyObj('applicationRef', ['attachView']); + + ngZone = jasmine.createSpyObj('ngZone', ['run']); + ngZone.run.and.callFake((fn: () => unknown) => fn()); + injector = jasmine.createSpyObj('injector', ['get']); - injector.get.and.returnValue(applicationRef); + injector.get.and.callFake((token: unknown) => { + if (!injectables.has(token)) { + throw new Error(`Failed to get injectable from mock injector: ${token}`); + } + return injectables.get(token); + }); + + injectables = new Map([ + [ApplicationRef, applicationRef], + [NgZone, ngZone], + ]); strategy = new ComponentNgElementStrategy(factory, injector); + ngZone.run.calls.reset(); }); it('should create a new strategy from the factory', () => { const factoryResolver = jasmine.createSpyObj('factoryResolver', ['resolveComponentFactory']); factoryResolver.resolveComponentFactory.and.returnValue(factory); - injector.get.and.returnValue(factoryResolver); + injectables.set(ComponentFactoryResolver, factoryResolver); const strategyFactory = new ComponentNgElementStrategyFactory(FakeComponent, injector); expect(strategyFactory.create(injector)).toBeTruthy(); @@ -266,6 +284,30 @@ describe('ComponentFactoryNgElementStrategy', () => { expect(componentRef.destroy).toHaveBeenCalledTimes(1); })); }); + + describe('runInZone', () => { + const param = 'foofoo'; + const fn = () => param; + + it('should run the callback directly when invoked in element\'s zone', () => { + expect(strategy['runInZone'](fn)).toEqual('foofoo'); + expect(ngZone.run).not.toHaveBeenCalled(); + }); + + it('should run the callback inside the element\'s zone when invoked in a different zone', + () => { + expect(Zone.root.run(() => (strategy['runInZone'](fn)))).toEqual('foofoo'); + expect(ngZone.run).toHaveBeenCalledWith(fn); + }); + + it('should run the callback directly when called without zone.js loaded', () => { + // simulate no zone.js loaded + (strategy as any)['elementZone'] = null; + + expect(Zone.root.run(() => (strategy['runInZone'](fn)))).toEqual('foofoo'); + expect(ngZone.run).not.toHaveBeenCalled(); + }); + }); }); export class FakeComponentWithoutNgOnChanges { From 062b8d90afaf60e0066568d64188ca074001986f Mon Sep 17 00:00:00 2001 From: Oussama Ben Brahim <14368835+benbraou@users.noreply.github.com> Date: Mon, 13 Jul 2020 01:35:09 +0200 Subject: [PATCH 013/133] refactor(forms): refactor common validators used in unit tests (#38020) A util file is added to forms test package: - it exposes simpleAsyncValidator, asyncValidator and asyncValidatorReturningObservable validators - it refactors simpleAsyncValidator and asyncValidator to use common promise creation code - it exposes currentStateOf allowing to get the validation state of a list of AbstractControl Closes #37831 PR Close #38020 --- packages/forms/test/directives_spec.ts | 19 +----- packages/forms/test/form_array_spec.ts | 22 +----- packages/forms/test/form_control_spec.ts | 33 +-------- packages/forms/test/form_group_spec.ts | 65 +----------------- packages/forms/test/util.ts | 87 ++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 133 deletions(-) create mode 100644 packages/forms/test/util.ts diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index 0f395e52cf..dd497dc30c 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -12,6 +12,7 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin import {AbstractControl, CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, ValidationErrors, Validator, Validators} from '@angular/forms'; import {composeValidators, selectValueAccessor} from '@angular/forms/src/directives/shared'; import {SpyNgControl, SpyValueAccessor} from './spies'; +import {asyncValidator} from './util'; class DummyControlValueAccessor implements ControlValueAccessor { writtenValue: any; @@ -30,24 +31,6 @@ class CustomValidatorDirective implements Validator { } } -function asyncValidator(expected: any, timeout = 0) { - return (c: AbstractControl): any => { - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - const res = c.value != expected ? {'async': true} : null; - if (timeout == 0) { - resolve(res); - } else { - setTimeout(() => { - resolve(res); - }, timeout); - } - return promise; - }; -} - { describe('Form Directives', () => { let defaultAccessor: DefaultValueAccessor; diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts index 68477d384a..7159500b47 100644 --- a/packages/forms/test/form_array_spec.ts +++ b/packages/forms/test/form_array_spec.ts @@ -11,29 +11,9 @@ import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/cor import {AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, ValidatorFn} from '@angular/forms'; import {Validators} from '@angular/forms/src/validators'; import {of} from 'rxjs'; +import {asyncValidator} from './util'; (function() { -function asyncValidator(expected: string, timeouts = {}) { - return (c: AbstractControl) => { - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; - const res = c.value != expected ? {'async': true} : null; - - if (t == 0) { - resolve(res); - } else { - setTimeout(() => { - resolve(res); - }, t); - } - - return promise; - }; -} - describe('FormArray', () => { describe('adding/removing', () => { let a: FormArray; diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index 46a65e743b..d9eaafdcd4 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -6,43 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {EventEmitter} from '@angular/core'; import {fakeAsync, tick} from '@angular/core/testing'; import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal'; -import {AbstractControl, AsyncValidatorFn, FormControl, FormGroup, ValidationErrors, Validators} from '@angular/forms'; +import {FormControl, FormGroup, Validators} from '@angular/forms'; import {FormArray} from '@angular/forms/src/model'; +import {asyncValidator, asyncValidatorReturningObservable} from './util'; (function() { -function asyncValidator(expected: string, timeouts = {}): AsyncValidatorFn { - return (c: AbstractControl) => { - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; - const res = c.value != expected ? {'async': true} : null; - - if (t == 0) { - resolve(res); - } else { - setTimeout(() => { - resolve(res); - }, t); - } - - return promise; - }; -} - -function asyncValidatorReturningObservable(c: AbstractControl) { - const e = new EventEmitter>(); - Promise.resolve(null).then(() => { - e.emit({'async': true}); - }); - return e; -} - function otherAsyncValidator() { return Promise.resolve({'other': true}); } diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index c46026e515..9addf0dd37 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -6,80 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {EventEmitter} from '@angular/core'; import {async, fakeAsync, tick} from '@angular/core/testing'; import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal'; import {AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, Validators} from '@angular/forms'; import {of} from 'rxjs'; +import {asyncValidator, asyncValidatorReturningObservable, currentStateOf, simpleAsyncValidator} from './util'; + (function() { function simpleValidator(c: AbstractControl): ValidationErrors|null { return c.get('one')!.value === 'correct' ? null : {'broken': true}; } -function asyncValidator(expected: string, timeouts = {}) { - return (c: AbstractControl) => { - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; - const res = c.value != expected ? {'async': true} : null; - - if (t == 0) { - resolve(res); - } else { - setTimeout(() => { - resolve(res); - }, t); - } - - return promise; - }; -} - -function simpleAsyncValidator({ - timeout = 0, - shouldFail, - customError = - { - async: true - } -}: {timeout?: number, shouldFail: boolean, customError?: any}) { - return (c: AbstractControl) => { - const res = shouldFail ? customError : null; - - if (timeout === 0) { - return of(res); - } - - let resolve: (result: any) => void = undefined!; - const promise = new Promise(res => { - resolve = res; - }); - - setTimeout(() => { - resolve(res); - }, timeout); - - return promise; - }; -} - -function currentStateOf(controls: AbstractControl[]): - {errors: any; pending: boolean; status: string;}[] { - return controls.map(c => ({errors: c.errors, pending: c.pending, status: c.status})); -} - -function asyncValidatorReturningObservable(c: AbstractControl) { - const e = new EventEmitter(); - Promise.resolve(null).then(() => { - e.emit({'async': true}); - }); - return e; -} - function otherObservableValidator() { return of({'other': true}); } diff --git a/packages/forms/test/util.ts b/packages/forms/test/util.ts new file mode 100644 index 0000000000..148f398a3e --- /dev/null +++ b/packages/forms/test/util.ts @@ -0,0 +1,87 @@ +/** + * @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 + */ + +import {EventEmitter} from '@angular/core'; +import {AbstractControl, AsyncValidatorFn, ValidationErrors} from '@angular/forms'; +import {of} from 'rxjs'; + +function createValidationPromise( + result: ValidationErrors|null, timeout: number): Promise { + return new Promise(resolve => { + if (timeout == 0) { + resolve(result); + } else { + setTimeout(() => { + resolve(result); + }, timeout); + } + }); +} + +/** + * Returns a promise-based async validator that emits, after a delay, either: + * - an error `{async: true}` if the control value does not match the expected value + * - or null, otherwise + * The delay is either: + * - defined in `timeouts` parameter, as the association to the control value + * - or 0ms otherwise + * + * @param expected The expected control value + * @param timeouts A dictionary associating a control value to when the validation will trigger for + * that value + */ +export function asyncValidator(expected: string, timeouts = {}): AsyncValidatorFn { + return (control: AbstractControl) => { + const timeout = (timeouts as any)[control.value] ?? 0; + const result = control.value != expected ? {async: true} : null; + return createValidationPromise(result, timeout); + }; +} + +/** + * Returns an async validator that emits null or a custom error after a specified delay. + * If the delay is set to 0ms, the validator emits synchronously. + * + * @param timeout Indicates when the validator will emit + * @param shouldFail When true, a validation error is emitted, otherwise null is emitted + * @param customError When supplied, overrides the default error `{async: true}` + */ +export function simpleAsyncValidator({ + timeout = 0, + shouldFail, + customError = + { + async: true + } +}: {timeout?: number, shouldFail: boolean, customError?: any}): AsyncValidatorFn { + const result = shouldFail ? customError : null; + return (c: AbstractControl) => + timeout === 0 ? of(result) : createValidationPromise(result, timeout); +} + +/** + * Returns the asynchronous validation state of each provided control + * @param controls A collection of controls + */ +export function currentStateOf(controls: AbstractControl[]): + {errors: any; pending: boolean; status: string;}[] { + return controls.map(c => ({errors: c.errors, pending: c.pending, status: c.status})); +} + +/** + * Returns an `EventEmitter` emitting the default error `{'async': true}` + * + * @param c The control instance + */ +export function asyncValidatorReturningObservable(c: AbstractControl): EventEmitter { + const e = new EventEmitter(); + Promise.resolve(null).then(() => { + e.emit({'async': true}); + }); + return e; +} From 3c1866b5bb1ffc3aabef1646e55b2f888a928caf Mon Sep 17 00:00:00 2001 From: Judy Bogart Date: Tue, 21 Jul 2020 11:00:09 -0700 Subject: [PATCH 014/133] docs: update api reference for router outlet directive (#38166) Incorporate more specific information about multiple outlets and how to target them, with link to tutorial example. PR Close #38166 --- .../router/src/directives/router_outlet.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/router/src/directives/router_outlet.ts b/packages/router/src/directives/router_outlet.ts index 1583acf2b8..904e5e1f76 100644 --- a/packages/router/src/directives/router_outlet.ts +++ b/packages/router/src/directives/router_outlet.ts @@ -27,6 +27,21 @@ import {PRIMARY_OUTLET} from '../shared'; * * ``` * + * Named outlets can be the targets of secondary routes. + * The `Route` object for a secondary route has an `outlet` property to identify the target outlet: + * + * `{path: , component: , outlet: }` + * + * Using named outlets and secondary routes, you can target multiple outlets in + * the same `RouterLink` directive. + * + * The router keeps track of separate branches in a navigation tree for each named outlet and + * generates a representation of that tree in the URL. + * The URL for a secondary route uses the following syntax to specify both the primary and secondary + * routes at the same time: + * + * `http://base-path/primary-route-path(outlet-name:route-path)` + * * A router outlet emits an activate event when a new component is instantiated, * and a deactivate event when a component is destroyed. * @@ -35,6 +50,11 @@ import {PRIMARY_OUTLET} from '../shared'; * (activate)='onActivate($event)' * (deactivate)='onDeactivate($event)'> * ``` + * + * @see [Routing tutorial](guide/router-tutorial-toh#named-outlets "Example of a named + * outlet and secondary route configuration"). + * @see `RouterLink` + * @see `Route` * @ngModule RouterModule * * @publicApi From 8bb8f98c2891671d077a229f0dc90b4862462573 Mon Sep 17 00:00:00 2001 From: Sonu Kapoor Date: Thu, 23 Jul 2020 10:50:08 -0400 Subject: [PATCH 015/133] docs: remove duplicate `https://` (#38199) This doc contained a duplicate `http://` before the domain name leading to an invalid link. This commit fixes this issue. PR Close #38199 --- docs/TRIAGE_AND_LABELS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TRIAGE_AND_LABELS.md b/docs/TRIAGE_AND_LABELS.md index 8251812ac3..aa23e674c4 100644 --- a/docs/TRIAGE_AND_LABELS.md +++ b/docs/TRIAGE_AND_LABELS.md @@ -175,7 +175,7 @@ If a PR is missing the `PR target: *` label, or if the label is set to "TBD" whe Before a PR can be merged it must be approved by the appropriate reviewer(s). -To ensure that the right people review each change, we set review requests using [PullApprove](https://https://docs.pullapprove.com/) (via `.pullapprove`) and require that each PR has at least one approval from an appropriate code owner. +To ensure that the right people review each change, we set review requests using [PullApprove](https://docs.pullapprove.com/) (via `.pullapprove`) and require that each PR has at least one approval from an appropriate code owner. If the PR author is a code owner themselves, the approval can come from _any_ repo collaborator (person with write access). In any case, the reviewer should actually look through the code and provide feedback if necessary. From 51aa4aa1c37bb4cd03b43d9f4aed5ccaf36f204d Mon Sep 17 00:00:00 2001 From: Bogdan Bogdanov Date: Wed, 6 May 2020 21:02:26 +0300 Subject: [PATCH 016/133] docs: update dynamic-component loading guide (#36959) The 'entryComponents' array is no longer a special case for dynamic component loading because of the Ivy compiler. PR Close #36959 --- aio/content/guide/dynamic-component-loader.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/aio/content/guide/dynamic-component-loader.md b/aio/content/guide/dynamic-component-loader.md index 43c565dd06..37a693f43c 100644 --- a/aio/content/guide/dynamic-component-loader.md +++ b/aio/content/guide/dynamic-component-loader.md @@ -131,22 +131,6 @@ The `createComponent()` method returns a reference to the loaded component. Use that reference to interact with the component by assigning to its properties or calling its methods. -{@a selector-references} - - -#### Selector references - -Generally, the Angular compiler generates a `ComponentFactory` -for any component referenced in a template. However, there are -no selector references in the templates for -dynamically loaded components since they load at runtime. - -To ensure that the compiler still generates a factory, -add dynamically loaded components to the `NgModule`'s `entryComponents` array: - - - - {@a common-interface} From b518b30dae56bcadbb6a30911311bfbe1b3806cc Mon Sep 17 00:00:00 2001 From: kreuzerk Date: Sat, 4 Jul 2020 21:25:21 +0200 Subject: [PATCH 017/133] docs: add Kevin Kreuzer to GDE page (#37929) This commit adds Kevin Kreuzer to the Angular GDE page along with a biography, his contributions, and a photograph. PR Close #37929 --- aio/content/images/bios/kevin-kreuzer.jpg | Bin 0 -> 26023 bytes aio/content/marketing/contributors.json | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 aio/content/images/bios/kevin-kreuzer.jpg diff --git a/aio/content/images/bios/kevin-kreuzer.jpg b/aio/content/images/bios/kevin-kreuzer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8fcc153eef228980527db1cfa70c780fce7cfa6e GIT binary patch literal 26023 zcmeFZby!qg*EoC_8j%LYn;2k_uA!0c?o7-48;KtWkB?);qrM7^K;`QG>YuHW<5_sn(8u+Bbfui9&`wRY&LLK>=>fa|0FVIWctij`2;l(`@P`uj3lM?dY&@!cm!E4;FchT75Qzc^z&0Fp z3GgqVf^he6Klp{J8JIxDRZXDAI0R6KeXRYzN)8@&NT{KchqnjD$-@(>q5|dCL)fF- zanC#e02daKk`We>5tf1qOUZ~z%D|-o6(=+bz)&%+f z!&|62NZ%2SxQIdm0BdN*PmLiDL$i+H=TYpxEEL6c2s;2kl<+UBM8SV*2{^<905PZw zfiPTDI3E9}g&?ov369|2L_C1(KwrpYBIJnId?LY7`+6eb&vtwuk@%1oI6jG_hcGD! zlO5rZBqr?B$Cs1>+Y^7mxsW5-zU1N`!T0kij`~vNQy#%5^QjK$gT7OMcL;;R7C;VR za4ZY(e{28gWjLGz?>G(s_|O9QK231+@dXRS_T@PV!ukc``|vRk2EBFv15yNX!SB9J zj>c9TDk3f;Dl8)={%gE{YXg7_YyfZ^{94Ey{5_&|;Pn8Y4z}PCJT18g0Kve|zrtct z!f+8`VG(H&_E$f9P(Ha6_UI0%*i#0SwaH8|ChZ*|){djtC!6n}0GI5)5Mdm>>pD z!2`|RJpUQ{r-^?<)YJQaMs;&LB8%}>y96rpPh$Kej4}1|L<$)rF&;i>d!*VWq`NoA zPi7czW8^QZ{-ORqcL&jrIy=}OG4}L9yY7#MgS`;a73l`@z!^*ANKVh6f$*ryAIy$k zA6gh36d6MV8XN&{G|FBTfp!50NthkF-;P9MJdyU^s7pu_dxR@ejvbr|2d`9I5onaR zA2|Qua_oE}5{IoGt{!N84-^JzjCAk;J9>cr3m5+vYG$5jl%o@9%|mLM2#gBa0~|JW zgewMlfPRQ$ibl9&E_$HdR6Km#!5)X4K>8*KZyz+`fX7dPj}$R+0=@t8KdNE{vK9QP zhXxwqr-$-IqV*7tpeYfcGKU;2oKW6KWrQoj-5yk22?I3&4f}^^DqsgBs4m*g5=Zh- z%_E9dNLN>n%W~`jBGP|!Q}+OEspsJUQau#g#M2e!ZR+6;I{ty&|Dc*K(jB9RaQ8vD zs)Cuz1C7&L$?d=rT3#LdIQ+X)o-NC_1W_e)4Ls0Pjem;-k)Mf?0Yng=HPLwQZm z6I^lL@KY2K_<<$Yi1xUMaz%o#eLye9wWuO5B79uEF$cP+BApOdeo;E| z5zvi62l+WcJTTt4X<&|WbO-q$|2Dk8$#&$Hho<;V1T}Yr9d4!`zWFz_41d|q*uxv) zjq3nrf`eI)_OM4{FyMqTj$L&C2PIJN&ll|zYs8BAP6R7y%j;?SP^Y>@7%U=l`ww0?#|9I-znJ&IjN zRy=fg$wTFoe7rq$!LdRkTve2gv{3oin0qie4>bEZTR>0yo!i0W5D+;q`Jo2tVBRu8 zI_~F`!*Kgkhkuwpzmrqh&)|o%0F0LVCAo<=!rcLZc7R^`ml>_QKjDvbI5d$l!U5$2 zjs_oC2p#&uFF)Onu|LB$7;L=J2#oh10ZqvjjYK&3DS3jz;^x(_DSG5=N?tx-h!=n# zl0RzS_fp)8BR@i*{vKX`jjyoaq1}%98~UIYMf z+euX1%}tJ7^pNY}dqXtx5()`sj^DE3p@q~O9FckmjLV;a_g|9kPha~v_-ekM9vB}q z^3YsI+0PW|<_WqJm`6d`#l>;xU?nA^FO)wu z`cUB!!cwL`SCD+T1tlowJ}Nk~9T47#{}r+d=%Zlz zHudlTW5K~Z`FBg#1M2%7kbk2sZu)b%BPa=GZk!axpn-Ae7&HR-sHZ2R<$ho;EzmY# ziy#W+AahaLUdrA<8X@2yChZ^~hP-GmAT0$hTHumm_7WH2;z$wui$D9Tf;Pv60$l%t zrG$frJxJrIf|RinLpq3v*@+7v?Cm54B#>hE0#a~ExPX*|2vSnoP8cpJ36AxFGzMVB z2bK=5zf%>|SVI}g4K8$~cz(yjS^r=hz_4=U4SySiUu5?~@LMAF<}NBRAOe$f53z6|1U0qX#!zDr04_QPh+gF+3gOyt<@ z4;DR02k1q#ha2=DM)?ZZ<2Eh@?9p(jx5r^4E^zTfF$jG9Df4^u{kt>WQKd4&{iTn{#|7pAY=VtofsUN1SKL~;k``ba7L*h!VKcxmI1h}Us z1Mb0qg^jO(!@-)GUH+dq{0lv%s(OSfo2xNnZv}TYHJHFl6c7Ny1Th4d{;6X5StBt)~NiHlREY1W`0Ek4yMTNycQ1bV7 z*Y_s{D4{ljvgM*v6b?{EKyXEJ+Fv^R*e4SwMc;L$R8!Y&NLE|(p z*@qwEV6>^R3fSHN!W51Na4!zFJAlV#97ASJMO~}5*%!I05joWZ=^4dFQDS# z>9=1nLgC=)J@jx>+YjT7bi+Wk-0eNko*v-lDbfMNJ&+IA3!pwg{I!#`-}-;?2R9A} z7jEYXHETKX`8a6@^T6QWTB_lW;!i$4A1e%~spJy!k2xceD zP>b$TNfZku88%Vmv_By}pM=RbR4or%IBhE7knCM|p*#HcNzV~3HA&=SGoON#`iR9I z^-IB{8s--(NXZyaar5v>Ny}(zXEd%`L6(di&n@fB5)mXzbhg#N^cU((=mc+WN*OPD}9O%Yl;M@4lWm zB?t(AX$hJH0_uqm!6$?eLI?;V!E093_*67(1W*wrT0&uVxDlNqkthd3ncmA-EW{9& zkf-v9f!I#mWWI~@LapjBdf`NAC-?P`1Qk)&097$F$0-A@k8t{6 zynQa=bP5g&k2ugPDLLgpwc7=U%9TH^IMlDM{-=gLKUEwVJyde>NXyTF3K$js{ReXJ z3i8JX)cE($9!TVn0*DU52XTLY{lA0vJ^|$T`{tttookz-knXIR9`7~PJEpx|ygYOn zy}Z%l6k$dxdGCtFqSa(>ck%d$i=DL=ylG+~KKW!S<#m6m89hb*lhYAedB)tPGSNO7 zce+cQlFwSx+_Y8i|FF{0AJ>1=BE6?|2o<5GMi!xb$B^d<%MFb?UECGgW`cJswKr3% zwCB$*CPB6DiIUp{8X2kN_MGi2ej=FF#hvWTDR@(-xOAjOaprc3_Q=dc>*A;TrRYed zylyA&Q;sIeMTN`GoTpNB?sU(X?F8*wbgzu9EXf6V^oJO8i(BY&o6#3}*F`E9-QyKF zbyF3T4dvrz`_AWzQuRYyss48Qr8^S$D(62T^k&TOR(ws;o91o34yrI?JY`yvv2&;F zw#&lTSNF@J_$D*vGsb7$aEtL~FherTbKg0Cx~7`n+dbtYR$`4cteCFJirz6R%R)Uc zw0OHRyexBEPfhaflOpd^<+qJc-j!y&DJI%wQ#P*{%i1<~PL`$2eBc|ug0Nvz&g(Ut zG|eC+i!k>-%Sm%nySTXccK`R|m--eW{2Kkl1ND^4!uSlQo#^Rgm?4o$_sGOq_K}K< z%c@G^@Yw z$>I;+EV8>vss)Bc;&h*$NZsiwS`in&XZF%R%h05xtQ7t7)XPh+*zZD@Cd zGVOlA<3r~xz?)(d1`cX$e@Xw_<;BX3XQgDNE=?t^A!l!>GkF_zd*#m5uEL`+wnp~b1ntsYpIvb&Ux3{j;sZXrraoOX0Wu=O3gu^N0 zeKX=;Ejs%q`lg?#*;tvccv*;_(<cBDC(E5)iy;Gi;qARcce+eXF1N?28hNwV786#?*>$@ELI=dH&fAw~u$ zZ|y*v7Hc`o6N~e1CjFc$g}rYw`vRRSN}@4l$(75k-#|ltjWDOH6!%G)5}SQ7J?fHH z;*8KQ880sOK6RYz!6d!e*_XT}ywNGj1s33xBLM>&9wEfr`-cYj(eL#JKMwFS@ly}v zf1Dv=Jz?-t|NZKTo7zw2C8KA@Zcc|aPO0tz7O!}pUH13+cKvoC=F3hi5%B;yv4;Lv z#*YFyk z*{7cyevmw0VDdNlx)#{F{HgZs^JjHpx8qMRFFVU`x~G6LImld6@C|Ie^jV0j!l17# z@>va)mCcTpJ5>u;pK0D~5aep#nb;O`jR7dlti(O*wuZYt(1Pr5@trKg!ISlc0}qXK zYfR1aSn!@RpLp6x>8H&%`q!v-PczqR?N`ck#~$Bny*z4-8~S(drH zapb{cuegRFiBI1j*e^u9+>|_{7l-{K*?C;ZP3=C*7Ug6f(`6l+^K1O8Bb^K9y{oha#HZ+TZ;ka zrmq&WtM^=NyMn}@B(f6gMttApc%+s+;W#St)a{Y4gMaD3h|WOeXViSj%YGN>!0xFJ ztE_Dq!w=n~s^y>C4i*t&7fd^)SfWx;*Yy>wgXQq_a1H;c15cLMY(KhueR zdAt_6L|t-yv*vk{-)7CH%T8;Y>#c9^6ncdXanDWVbCix%! z$InZQzh}ksjs6@oUvooJ;8kNgPfenAWrtt2WW`6Q?;hZ(w+CGN7$86FFm26J7I`6P zCF@4t_WN_35#gW1jXw>z$-@RD5}vPz?g8HCQ>)q&=3jk%IlhJbN>7%GG_GR#c2nJ| ztN;0ho%Wmc&pub?hW0x@{W^X=>PuqTU%nT4oE@~0>a~>~sm4~mrE;$}mjW~0ZskOJ z2y`)bs20SH)6E#XbVT}K1^b5i&Rr19)fW2E!+%0zTK5)lv`_%^2VNb|Ks+*Xh7p$ezK19KD3FQyVZ`d{@NR!95tZ&Fjx?EZ*;SweW8 zS*2zAZSmGEZlYB#_j17#`K8+V){Q4zPDg1R^KiBuIJZ1tLdx0g?T=99{Z5{;%l8?X z?qdC+;ANc0e$)cXPzAg_;H4OL{}C)qagY9y(R)CJqQd;6J>W375h+0SfPldtSE=1R zWT$0m$z9SNM>xWH90E`V#yzJbj(5n`ygL7RtC|$Q9b;xYZ#?qJH}LOyMCinVSMA(ljLq{noIo#Ts)8GxBb1n)OrNku8R8RVli_fJgVGY1<%92S)Y9;;Qn+gg+({| zS*31uF;S7`le5jzemvsnuWn!2tZRDi9IJSxOifmvXX>0&N|#g`t<6^t^>Mn2HWDaE zZ-CIhy=J^37+=tfzgw50C9lWRs$28n8QBXm&8d7?#vP4u=l;ld9m(%Q6sikZ^UcId zBV+^8Rj=jc8t$YNY{At7YWD!encV_iVO1oQkpzgR$!0X^Z@qu6{nTYf&KPwg&X<+^ z0{v%U%PVgUjPD1%Pf<<&RH_;6Kt+zc8m2q)R7y8Tq*tt0oYB4C%e7*0gOj2EQs$Fo zLJ9{|8vYL+Uh|w3wFd#O&WqN2x%s&zoFy~OBG)m0o)n!l*@~RJ=Ey<(l*)64WF2XZ zZ_JTk;Ye5PIox`F{(eus_U;aAigL01USAO8=%{&@B-KizJ)-gw| zi;5{)`E}Z8X^Wl8X{kLxeE0g6Li#ShaXP2P+1@ZtV(MiSd+u@6xb^p?ubMZ?N3@_7 z(K;?_LX)w_Qnc%rGM4)dLIRo#^%}%^NsX0J53qdnZ^SR~ZG;r!T^}t`(;PM<%CG2o z#|_DO2oH|O<1rAIgV*#qXSX3@A{IHr;y5#ZFe83SwSnVGTaZEaCN2)#R z_RcTMMwRkbstf&A-33N|g5=~oHZ8_xWz#6@x>iwoWqaiUqn<7bl-tvD_ikb0;T;^$ zI`xI`4r_6Z=MRO_lp<^NiOpPORWmS~BiL9vvu#Zn#z=ajc z#)r+lR_O8#Q@%x9iCYWOA4aX3iRCGLRNg@CsO)G6ZRKP8AlE-%cDeo-?)Z82P7D3m zGo~oxD$9*$Bvc7zO)4=uQ|8v^@dw?%UzI4n>V3N)s)rt~f6Tmsb-w+~Ss{W;M~Yk72vk9_sj^(~OubJ1@O3SH36RXFfHh^o~`6>KfPC z2YfA6$+1q_0jYs2Y4?=)h`jkV&sHA_X#LF3v2vjVc- z_lU8t_IM>N`wY~c?Jkle6=)r)ewcnry(lnb{L%TB@$YZ&of^Al84W9;PfIqIBc;dR z(U9EAAKmrjmV1c=?np@3b5dmAa_Cdx@@KBJpqcG4xIM+r@6GGg@;<6@8pTF%xA zM?sRNG?5`fHCvX_;FicI6*?E&?=Z@`n6LJUMW*2k#T<&~|B`3_^mVZZT{TduF~??n zq3+Wd-{QSnv10zSQ!*wniLc*$6luyv=igKnlj{r@CGd=nvs#s`HwPk5muJbD*{(1J zlclK0I7#)sE&*2kHkwT@`(GTC)diJY!Ek*3%Xdh5DV#>V)033#*gy8`tm{pX7fd3HP? zB_r6+_Y%9zh>GjSvJ@$_mC!e8ZW6|5s~$@cH9$x|mra>@pH7#ZVqB67(2OT7;P{lJ=Y-n5t+i}yFyy=~Nq12z1u=JyaG+Y$zq*{g zW7z0(c-aPui886GTqBa&#=`Mz<*ae0U#Sk`{b{Oh~K{ph(<(0v#0{h(J;@4i% z<>)EYli1Z3GvQ)LDBCyh{AENG9qwFGnkU@)h}F!^-f_~Iw3}tEK*fR_3dw|I6L~j71B_Dj{dDCMNcC~~?zIE!! zABA6fPR$A^KbR#2AVsmHR%BdJ{T9+u>P0CxwVi0|wMB#PJQIh9-@*U%7LLbaII(O8baOCt(lTAt^sA$%}HRDe4c!&=_2&A(bTrF>^H+bfLA*(r=4{? zoZ%7U3EjAFs$TQsZ@Io1G<7zgu%~Z(@z&4Z(NV6Wm^yzP!N4%ZQm5UjWWj23yYlf1 zrRec)pITY%F3l7srH#pg&Cp{Rd}ks~Gh+S7p4`y*zN33?;4QhjarXK0qDHc@A%etU zFRrVq&bz4{4sXQud9)(8w9e|$Q5GTK4~*Jrbg#um=M`4a1yuBFj0lX6TCNJb8hcGB z)nux4vMgPesU+R1^_5<4w5*xH`|~S1_xg9#Nwl6-j8wfaxvTJZt&xgNxrkdiYQWGZ?))6?D*(LQ1vq7cB1LWMcu_(I+^C@SR_w(?vyoA z<(ccv93;sN&-3+!*>nhD$pOr7`iY_k1nn{=r?qciv&xs@iq+|jlc)5~=6OKB2_JNi z9sgpDWqqbvqJKhC;<~{jlj64p&T3K@9)+$4kPaJ!N3`RK%-!rD?mHA2QXz)m0EUxVi%uMT8-Md6PS!7!IPSboG0_!X0}UB3~H3Jh%!ko zeR9fq_=`YL0=?;FhI@HeS3B$o2QqUv_&pU;OfASm?^kOKca)K@he`7%CMZ4o3OH1K zOqB|wCska;)7&nmAT^_F4jp!hJrB39+|o$+h-5?*UZG-=>SEj+NmRy6d62 znWbm+0qa`MUpdcx_+0Py?8oZcyPj@aXkBxs#GK4N zV}3t+`wi>5Z~EGbF>f;7F%$jCEx=^X09OvvQ&~m%WM!LZr2dme}+sWD&w|LoGIZ%t@3p z;vX6pWSS<8X?z)7t1-EHA*7=!!{-6^Rs#7|De66dO^$RA@Mu}qOcq*Pl1mH}4|q^V zf42Wa)w9-fYI{Ia16Qd+hVv=I@|;o92QM3(wW24fZEWmy_W&fKHQWVemLb6H9Oue4 zq;XH8C(+(K%f_s$h4nj+)^0bQJWFnsK#k|C^G{c)3zJx=8on04iC++Sji8^^U~OdcNy>2co2)R zIO}Wf0hbzkbNVcn`U*B0Pb)Ztv2E7dZn2QuS$EXtTe(_utKagyG&;^SN8wTO0+Vss zC#9jYajzrOwx@2`Zx^lj1Uz3ht{_`9D2Z$N)Iw6mF_|ddp=nVIZYnrgeA{kiKb1~L zyXv=FQtgXpy352QnXsj`c`SKTK$c!3o)Nmc$Wg1(%gV9foJyF|5Y1?a#iDLzw(P{M zLc1c~D9Jm?)O#kq5sw=((M>2S5w}RmE?44a_S6noUfQm=VZS@8z!_z*P9v;At+Fm| zU(Z{(#n&rszwc$^#xIxph9u{hQeTearkaGR!p~G56J(~za1L4 zc4`lJ?jNBi_ToYwK*mEl)yUTng!gVzJ({&uKXRoquzt|?t&3;gyoIvM=39%;+Z0kY zF9*yS6UGXLOex#^7%Db6wGBSdD_OK%(~ea&&2thnTw$dOsD1a`gPS7}r43UJ6uuW{ zA=ErI)~Fyg&7ykyqo6^w!r18`J+7U|J%G>}|7y-uF{|B;V`-EvLI&#&;*{U^fMeS> zith>y&%=$QGsGTv52rY39%m)}hW{;CZnM~O0nhY(&IybGUsd={O6TV89C#ukN`?Ek z!24$b;K>MhmUs+26=D-nGD3KTB;-A+o$sO=UVsWK8eZ@Wj?aDg`rXjaJw^pK@|_;y z&FMWLw!A5>pKeDgZl|#DqyEE{!4i$_U61-qbNWEu3*L$#;`gUy z?o~VKR-+qg3HcFsDX?V5%PpFm&32P#S#NeOrw4QT`Sf>II{WWZO31Enw+GSgvPO>w zTBVD2)?6nJ+aB4_FiT(94iq~%bhn8`VatB7X1nIt#?;i??UzBtJ!?&uIOfvh`mRb% zOBc?#8t@n-MzdWAuODfPe*gCRX+mYVs(=#zqTfhifAgo$FBxx2XglDTn#`O1woUk?ec?S+x_^lUToo^3J7+3*0Y&E(~YwM&uS z>^t|d2`ir4DjWzua)a=?sjcf_=32VJQ6EZLKUtbU)A`zUmGPv_R!Lp+U8$6MiYKD~ zs$1^*zU`$(>H&$)hicR^s0UqY;?66(&-?nSj%E!!n=pvKw!uHLYV)>2=XBGS!s^7t zT6XajxO6Q0?uoZ+sw#zp;`L1y;XByHHXpf|MWfxhu;p5pYvBQmBq zssEAXM-#c%m+OKa8(Vp0H7!eldqBk54UWNkZ$T%CUYcvP-gT^cHLHL%!EQ~=t}obE zZ3nLSZ|(u_)AegQq+2~U*0g=`*kjjDlc5aU>xvZ(wg{dK5Yb}AdCzyr(KYeRdJc^s zQ;DXbG;$jCB`4X&85z87r`sk%wO(Hh;?gZXf5+;RxG@!)Y9Uupc63yls+W09W(L~; z?v5#R2&!YVPq*4#oLO7jc+vE7&flf8b9N6{#4a}Nc2gvSBC#qPm&u;ad z0cF0VHG6#O+`M(`0}dWGT9wCn#;F%ew68kKCyMatqVgV|R(GVhP}#h+_106yZv8Vf zPtx0NmdNg*Ab-+%hnBc&c8xOIbiq=1lSn85r0pb71};f0YQ{I*x9;7S=en}>NN7v6 z@4DC09`F>#g6(Rs((SaRAD%()0hva7z})HwZ1$OII?A`5YuGN0J79Qa?aUcaxXh|Y z|Du>fh=+#CwMU)lH1(?ud1TJzf>vUu;e7J!34R$JmhhG~I<8o!N<#g#3nLUfp3~N0 z1d5zl^z5}ffimevpx&!BKm0U0c6n{%I_Z{Qbq4lh=0;vh?g29SS4NxKF+X zjhVZAA_?O>0TtV6BKA=KdWDP)hUYZ#2`vJ)a)#ge8nj}%=fjS54&ShfB#GP+JZ@5H z7>9m&uV!L;`|{dSGD0Y3yKA-@eSUXy8(T%XTQ<+pXd8FgHe_z9fIMw_IYeIK`Y3pm ziClEtTzYH9FNhy z_OuhZAKv%DkmtiU@9%~iR24&~l?`uSzmE6KA5vF!hlME5^BBRF@r5peM<@+$VxhB3 z#$;k|xiB@6L4te0t%aaHpfh7#n#GotZY$0Vd?>;BiR8xxsy#q59E>{=r!-QB=W3fu zX3|zv_5fMSW(91!(Z)pmc*~$|Q;7#_#{#o0ws9JB&Gu_2A@K%VNac4fr{1JI_zV}* z!)o1VKyVdGLYL`aYhQo&X6X9>xtmWXGz%DsXaE_x24%xZicSeyA_guUZpF0Wa#^K@ z>QbPGoWT`5=yTUf1BJ~a$l03}%XvxGfpv1m#54iYv(Ti?k8$Rp1 zIo>%Qw9D5yv3-kw4-gHrp>fXaq;0&)@!jpTLg&aHAP2fflLWRyLCk*VL$(z04JY45 z_lw7uvbpLhX>WRa7F>tJU_&$}@5WycP%VYr>$FOhSE2}BVsW}L3adPMZXrd}&i>QN z_S548rcp)x>uwvjs9s+M*Qk6G%?r=9vNK=5m=SU3EM z(YW!DUAX|;aIjQJ#YDUN`TV%>xS1oeHIqd6j74g-8rGzvlyu_S@^{7s3BzZQ^+p9* ztCC);DIaYmh+jdJz?HNg^Zbu<<=byTV~x6Qu3e0ydizfQTp1#kU5a%Gh418CpGBWPDX9 z|8~ain;GNW2J4ALm+3J-&&#ThcO@UNSPR0j(z;QF8+$;q=CwVbJ)gYVZA;P%-!JY| zVEMYaeP{^fOb%TlQ4geDMl=+!wSX>?p=A$nah7;=@A?-!Vf?IE^+(pVR%;718jUd* z6re!}{WZ%R)`5sKoI7uqjWmsAbQg$2a%kmU>ont(UA;8K*e_Mmc16tXD&+Vym+9L+GjC8!K?fZm7`fgBB{DD0xK+a}X=)OWfu)O+cQxaEsxO|rmg>qc zW?Rnm)$zK_bKw^!C}Vm*js?NxFO(DCCK(+_)liq_5}o2V6oHdFNiE$WXd(wEf44Hy zdqY7`&9M67C~7-%@J?KE^y3hjm70nVT~}%RVG%;dO3%^kp8U!S(?k&0(LD4TdrIl( zvH9th=e9w{h=S4ECHsrgNjuaI(u3^@33E-9mClme(+}nrFwDMVXY1dYl4;_xh;EvH z4%cnCItjC4Q>9&ax|!gisIEdVDn6s5a;Z72#a)SmR z1-|b_$a$5O%EZY^`A+5oSI2BMIxs51ZZlW%6JE$AXTa&|U+s%6?t8q6#L3 zLP!ay6bXVA2>?(kNC{}bUqx6VDGbmgpwfcIlR|0mpur@tP;jqV3l>U308zrR zpizS208LmtDM2Ve0FV%XqyQx-4cHOKB0N|LpizVwY64_h1fh5YRHQHn>1hD`U%WI4 z5=nqi5*Q6BG@j%@a-4*rS_BY~1wcSU0*hCKg(|`TkTp&WD2);Uc-DiX*2k^oYOzE0-AGs zOlM_t2McbcG7oj^^oS~KgY#;k&kwsD(}R@H*&USui!3l*m-U0`aRsKo2W$)Xv&9D0 zfIW_nDuC_a>U2E4qh_ZDgu%XRoJXr>s%#BBN!APF#d20*`V!|zHC_efnA<;Gl8SN0 z?=R5`)?aM6lqBRYSuTD7ouw=@F*4Pncv98oFIC*>%NwqKspe_ekv*5;yf zyqfGWIS%|67v7z=#!NA<%r)sy0K+s3w|8qEa6_CRd?!84KJykkp(W~=l%2dpZF;bn2FrLPQ+)7P6>}vE{IvqWA~JAg zWjy?3(q?vl*^;bUh*v|XX3nx(lqsiYQ+*O>>ERGDEe6uzxE$JzF%XOm--vlZIElXUs*Y7Qyo$riMy)hhkHqFx1DVhEE;HV?T){E=5qBcKierUUb#? z&a2z-?W34d>EnfoTxtC~>6{_;qwU=nJn4rhiLcX;ZI*kWg|BZlbHs z$<2#tzAQBImj{1DhVc8>4pYjHZspZ!F__aYG+=XlW?S1h66?FUpc6}$x^g|57w`rj zE9BGYk%X#Do(^tMrE0xsl{1yk$YW?2-qN`t<`7RM44tjo9@_(aVI<8AEQ7rTSBhyW zdF=DHX37fh1mv?ziz;aZUs*fMm4s! za5<{+%sdfDrB}KpL-S#@s>LK%F}R%KZf!g_ibzp7CLTh>NlqApncq6Im9GR94$@;X z$QgNbf);`Z9pWgryF z;X-oyI><8-z1Ud7~GT7x#*`z37>OS*^?{tEc(rOkx@w~yvkYa-{Q&3LW4msDe z$Dc*}!`iu-l$O89V`)uyR~+;py@jMO%P)V`zD&4b_!s}k6{Xh^WS_!|$%LEPO|mHx zxPLTY$lb21gdV%dkiUje(IfJN2X)%evXd;ak?AIai=yl?T}F;{(bwzkqdTez3_K;ur+ zCRMeI6;qMTsy#NOFSoE}P~@v_&iM_b8$4v`mzJ8H>E6`;@SS6iOcF<%D84~wx})(1 zx6SK{x00^GNwlO|HvH7K2oi`!S6t)3oKR(+aSOet87W z3$c(GA#SReOtjDAsUKPF^vVuh+y(gaQG;!)*;0=oa1IhoJ-COG@DVPI;vMU|YFpk) z!WiX|Wt4z&JOV2OvH*Pcy2%o(xCI8B`Ga;=bRMdhkDdets zDvfmvoSZ>Ynxm9jv4x`xSRE^KD;3yGAVQv!Nn@I>7~MkT>NQ-2SEYtRNh!V(Nrcwv zbTrAIXoM%luhuoZ48ks?0j(;hx;uLMP& zgvy3k2FZO;EsBI*|2{L^*+^4HG#x*ee$}tjmh|-bd8KS0YsJ-h0%2*{wz;Jk_5c|9 zZA)xt7(w(og;yGMFQ`0IZm{T7%W90~BN&q&k_Gs@)H~08Ej`4VMxi6SsybZ1jo+YY z^*YrUI{|Z+^xWWX4CzqU*Me<@Hk(AKDAtfZ?9xIVBu4)()6y9od4-R!@jhPo zzKk_!$~-B}!;rw=JezY}1nNTYI$`N!mjx}yvh>VOuSywPB{NOD0=33zjhJh_i`OaR zzjA4my>$ZSU6Uo=RS9rhrIRJZ7kb06-Ik!KMtHn6*slJxI5he0InDSOKBqS%cl58- zgZpHnSM?qFdX>Qc@Ul@hlTDS-EtQj{Kt1#lN7S)pc``+A1`W3Qx(3E826DTwo@XY$ zXF6F#cTQ?@0H%|Sks3|nX*!6I^!1j z+hA{*q4rT}(+W!|Qe~&Y6u_EE&ua8B_rq{vs+JRpTMkpGbY5LVw>@5XjHE6*L9H=S zSX2Ov`itf4<=_Awjz~&J#?I{5_GjPl!nD1E2GZ??jxRQAo=5Wu9wV$mha=PZ{-V7o zb?3x+iYwnj@q8!hro9E}E;%xiq`d7dq<@Fk%wJ8S|6zx?6T(gWt zaYR9isIH;D4LrpD24Tw#Av&p@7!{G3!86?no5jbWN0r(j7Cc4D}} z>}|4!+F`29DM5#t;_JnHt2ur)$qmv`Q*ldMcm$RsXo_5RMldS96MWsA}Na4lEwcCHmwK~ZV$ z&-#fs6VsX%gS8eZ?`Epd#wb%6-(gcUqPS|)7fk&$UJgbw%l%!uB{W9r^zpy0!b+*g zQw23$y`Y?3eE53&%E-GkhHT@)1P{wd$+bF?w<8_JEtYcn(pkglQYB<5PsE)qUw^HYhd zRE_e~Els;gJDMoOH zvRvn2s+n9{I;YMh-XDks+ZksMy0`cPR@5+W`Q zhjgx{xO?})hiE9+F^gN%!Oh{<HPfiK;-b0Dl}{f>YZhjLC(RQMVd0NkqPDzb-!rCL z)^S)$c`WQ+J3)}&z(aYQD+$~HSPHvwo;HW-@qL9kh_EhyZI;G(^Dw(y-71!)=EC;` z(KI3|LpUW~#4^-r_@e>M?Pm8&VZHqC^q}!!qU<8Kh+-H!#KWFIJHl^>$BXt8K^e}` z5b#sbeV*3MzYEiTq=@hIkRSo6yNdZHOx|o|MN)($i8!suLM&p8CL}*e+P8h?w(v6T z1Vl!otTGAa>6fKRw>~oDgt>lf=Iw3(i<7W2UP0mou19-7FYO0()(3KqI9o3x(nl>6 z57St#>{da$=wjLJ>?q9Q6s8g`zFwlZK%u`e_z{mX*CG4z_HJ)7@Xn=x9O64z3Y!EF zOLxnCUV97>e5?N_g$oo_n~m?cD8M!8m}WQgi#H4-1Q4pgIL05OfKqW{eaW5f$+_U3%SS;+2-rw z>11caJFM9QH1I@#TEJ+2>W$3M<9t3HWay&FaVneMw@VX`-sN)ZRGr#sbA*cWgF6Pm z@K(0TM=9lp(|7eXRflCMvG?vK!hSH-?oPu7C=?TV!1~0tfDMR-W-)ihEnR`Y;!JWe zPenp4flwBe$D;~Mx58&m#vWJSey?m9p3pXglHelbDlZ`r6G3FhE2=h#JI6&69*DrYM45 z`Xk!mv(Fm#%nYRv$UQL?1q&zNaC%B5q=42lRVjhGW0`LQ%N#nRXbMdt0zWF^?jMSPhv|g}NxLbNg)~bsz?)&LBDc zK?4^8V8}c?&B4HHLqw8Yr$1O&j!zB0zM)c;!=i%5W`UHkF!A12$eeUzu8I!T)X?l2 z@MuI7kkYG_Y#6#)@C4&(JPQBpF24Vzd4=~4`dH(=VfdqCVC6syO{Cjqcl3^AO1>NYV zuOv&i4P@>7RSIA{N0`_@Gd}7oL7hv-On&3DE{?k6r{145KtL!CY5<`gOvNkXWYY2{AVb$I@f96G+wdQC04C(Z zdim*Kz_ep8tPZ+aGE6kNSc*MCfTtp0$kcbF4-C#(2b4h}xMyBNAIV6FBRd{FIK7cS zpcq4>m>fYw-NI0W ztt3x-r~4Aj2tf_b&m-eA^G%pfkkOcljO&7#5LO~jphFY8`RusDlqHr@pd|uIB_S_D z!~`}^4;=VljA=^@jZ>p5(>G3Q%3$S-qk<|zeDy6nS!g*z0x!7Mwhc3%7I2t|DM}Bq75wJ2QixYVygB5fhFe&mZdWICzPZgE;>HK5GO8R(ayA zW_a=5+4TB3+?m)U!1^nE17pWa z1bXE}UB}}E5HVW>mNHzRoNf(SB9uxW6s7IT6SXkJWxzDbM&(Eoorvd3=s7fCvpkw2 zl`$$5H!@@92#BRw2$9HZr{=1LIh3JIXfR|uVI%uIR^=ZGA^KT)C?%X|jzxR_07N9< zq8|f>S78hdK1UN}4X9B&1KP{&^aOzj()aBF1PF&f$vm_5&#z!d4iHQoy6^n(%!XoR z1qi?j#39!fI@mI#%ZEAlANcP=Vosh|PvWRkQesm+!?=fPKwbfq!jl8~GgbgG$0?E+ zeedVR5O^dJ6fA2Z?_aDF}lh@#vBdPW3hRtogT5b;ZzRUnB#VSYz+R%J`S=f0sNE$h?^9jE0fh2;@3hh@^1I9*K_= zH-aTQoHZ$|c#ofEv(5rcjOMey@<*9DAe?vxJ^uh*-9Q&Bn4X~VKoBw_95_iW@BaWS zGC1Q{;#hh(&;8uEkYGSYlRf$0^bRhpH4^8T`0>OdT&KZtuPBJ09ppx0TdMpCdyyMBA3E|q*OjNVTW`nrV)nWjD=VoEIfkH<34h3 z9$@=uB>9dwEoY+W+16htt24s)0$@8r@czgq0D>9QPaIkM?7T>OA@~Q9Y95c0BPdz% zG5u098OTYH#JtLL#w>_ryNYlQ8Yj(Bp$XybczhmsK7}QSm0;kWipRGmz~&3bk;nK~ zE|yGLSC9s{6kiW!F z@F-#fh1byk0H4x;W03Dya{mCI*E6;~ge3m}KirO90Xm40{{WUY_KlGjA{h8yS#vN6 z01#$S^ZtEE@G?f&5eo+e86W~hW@cmv=;}9q47Z|I60k@N;kXiP$s|R92xTh3s>A?z zo0Rt*_Vd`Ws?j{q`>G?Fld;Yp{HKvkc9@InA69U~KO{XKQ2yuxlqZ0*K7sX37$=k@ zN5;I=_^}wsvSj}NRKFCmdW4IxMW#e9fuk~eX&HBM{{Yj+{170Z@@zAdX8=s+NCXhF vu2MKhG6o}oo}lWuqatJ2@U!pLAd3T#LzCU)mx!MoKSdmrmDOfd?<@b=# Date: Tue, 14 Jul 2020 08:38:44 -0400 Subject: [PATCH 018/133] build(forms): create sample forms app (#38044) This commit creates a sample forms test application to introduce the symbol tests. It serves as a guard to ensure that any future work on the forms package does not unintentionally increase the payload size. PR Close #38044 --- packages/core/test/bundling/forms/BUILD.bazel | 85 + .../bundling/forms/bundle.golden_symbols.json | 1736 +++++++++++++++++ .../test/bundling/forms/forms_e2e_spec.ts | 67 + packages/core/test/bundling/forms/index.html | 32 + packages/core/test/bundling/forms/index.ts | 138 ++ .../test/bundling/forms/treeshaking_spec.ts | 35 + 6 files changed, 2093 insertions(+) create mode 100644 packages/core/test/bundling/forms/BUILD.bazel create mode 100644 packages/core/test/bundling/forms/bundle.golden_symbols.json create mode 100644 packages/core/test/bundling/forms/forms_e2e_spec.ts create mode 100644 packages/core/test/bundling/forms/index.html create mode 100644 packages/core/test/bundling/forms/index.ts create mode 100644 packages/core/test/bundling/forms/treeshaking_spec.ts diff --git a/packages/core/test/bundling/forms/BUILD.bazel b/packages/core/test/bundling/forms/BUILD.bazel new file mode 100644 index 0000000000..5c0930c0d9 --- /dev/null +++ b/packages/core/test/bundling/forms/BUILD.bazel @@ -0,0 +1,85 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "jasmine_node_test", "ng_module", "ng_rollup_bundle", "ts_library") +load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test") +load("@npm//http-server:index.bzl", "http_server") + +ng_module( + name = "forms", + srcs = ["index.ts"], + tags = [ + "ivy-only", + ], + deps = [ + "//packages/core", + "//packages/forms", + "//packages/platform-browser", + ], +) + +ng_rollup_bundle( + name = "bundle", + entry_point = ":index.ts", + tags = [ + "ivy-only", + ], + deps = [ + ":forms", + "//packages/core", + "//packages/forms", + "//packages/platform-browser", + "@npm//rxjs", + ], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob(["*_spec.ts"]), + tags = [ + "ivy-only", + ], + deps = [ + "//packages:types", + "//packages/compiler", + "//packages/core", + "//packages/core/testing", + "//packages/private/testing", + ], +) + +jasmine_node_test( + name = "test", + data = [ + ":bundle.js", + ":bundle.min.js", + ":bundle.min.js.br", + ":bundle.min_debug.js", + ], + tags = [ + "ivy-only", + ], + deps = [":test_lib"], +) + +js_expected_symbol_test( + name = "symbol_test", + src = ":bundle.min_debug.js", + golden = ":bundle.golden_symbols.json", + tags = [ + "ivy-aot", + "ivy-only", + ], +) + +http_server( + name = "prodserver", + data = [ + "index.html", + ":bundle.min.js", + ":bundle.min_debug.js", + ], + tags = [ + "ivy-only", + ], +) diff --git a/packages/core/test/bundling/forms/bundle.golden_symbols.json b/packages/core/test/bundling/forms/bundle.golden_symbols.json new file mode 100644 index 0000000000..85063ccff6 --- /dev/null +++ b/packages/core/test/bundling/forms/bundle.golden_symbols.json @@ -0,0 +1,1736 @@ +[ + { + "name": "ALLOW_MULTIPLE_PLATFORMS" + }, + { + "name": "APPLICATION_MODULE_PROVIDERS" + }, + { + "name": "APP_BOOTSTRAP_LISTENER" + }, + { + "name": "APP_ID" + }, + { + "name": "APP_ID_RANDOM_PROVIDER" + }, + { + "name": "APP_INITIALIZER" + }, + { + "name": "AbstractControl" + }, + { + "name": "AbstractControlDirective" + }, + { + "name": "AbstractControlStatus" + }, + { + "name": "AbstractFormGroupDirective" + }, + { + "name": "AnonymousSubject" + }, + { + "name": "ApplicationInitStatus" + }, + { + "name": "ApplicationModule" + }, + { + "name": "ApplicationRef" + }, + { + "name": "BROWSER_MODULE_PROVIDERS" + }, + { + "name": "BUILTIN_ACCESSORS" + }, + { + "name": "BrowserDomAdapter" + }, + { + "name": "BrowserGetTestability" + }, + { + "name": "BrowserModule" + }, + { + "name": "CHECKBOX_VALUE_ACCESSOR" + }, + { + "name": "CIRCULAR" + }, + { + "name": "CLEAN_PROMISE" + }, + { + "name": "COMPONENT_REGEX" + }, + { + "name": "COMPOSITION_BUFFER_MODE" + }, + { + "name": "ChangeDetectionStrategy" + }, + { + "name": "CheckboxControlValueAccessor" + }, + { + "name": "CommonModule" + }, + { + "name": "Compiler" + }, + { + "name": "Compiler_compileModuleAndAllComponentsAsync" + }, + { + "name": "Compiler_compileModuleAndAllComponentsSync" + }, + { + "name": "Compiler_compileModuleAndAllComponentsSync__POST_R3__" + }, + { + "name": "Compiler_compileModuleAsync" + }, + { + "name": "Compiler_compileModuleSync" + }, + { + "name": "Compiler_compileModuleSync__POST_R3__" + }, + { + "name": "ComponentFactory" + }, + { + "name": "ComponentFactory" + }, + { + "name": "ComponentFactoryResolver" + }, + { + "name": "ComponentFactoryResolver" + }, + { + "name": "ComponentRef" + }, + { + "name": "ConnectableObservable" + }, + { + "name": "ConnectableSubscriber" + }, + { + "name": "Console" + }, + { + "name": "ControlContainer" + }, + { + "name": "DEFAULT_CURRENCY_CODE" + }, + { + "name": "DEFAULT_VALUE_ACCESSOR" + }, + { + "name": "DOCUMENT" + }, + { + "name": "DOCUMENT" + }, + { + "name": "DefaultDomRenderer2" + }, + { + "name": "DefaultIterableDiffer" + }, + { + "name": "DefaultIterableDifferFactory" + }, + { + "name": "DefaultKeyValueDiffer" + }, + { + "name": "DefaultKeyValueDifferFactory" + }, + { + "name": "DefaultValueAccessor" + }, + { + "name": "DomEventsPlugin" + }, + { + "name": "DomRendererFactory2" + }, + { + "name": "DomSharedStylesHost" + }, + { + "name": "EMAIL_REGEXP" + }, + { + "name": "EMPTY_ARRAY" + }, + { + "name": "EMPTY_ARRAY" + }, + { + "name": "EMPTY_ARRAY" + }, + { + "name": "EMPTY_OBJ" + }, + { + "name": "EMPTY_PAYLOAD" + }, + { + "name": "EVENT_MANAGER_PLUGINS" + }, + { + "name": "ElementRef" + }, + { + "name": "EmulatedEncapsulationDomRenderer2" + }, + { + "name": "ErrorHandler" + }, + { + "name": "EventEmitter" + }, + { + "name": "EventManager" + }, + { + "name": "EventManagerPlugin" + }, + { + "name": "FormArray" + }, + { + "name": "FormArrayName" + }, + { + "name": "FormBuilder" + }, + { + "name": "FormControl" + }, + { + "name": "FormControlName" + }, + { + "name": "FormErrorExamples_formControlName" + }, + { + "name": "FormErrorExamples_formGroupName" + }, + { + "name": "FormErrorExamples_ngModelGroup" + }, + { + "name": "FormGroup" + }, + { + "name": "FormGroupDirective" + }, + { + "name": "FormGroupName" + }, + { + "name": "FormsExampleModule" + }, + { + "name": "FormsModule" + }, + { + "name": "INJECTOR" + }, + { + "name": "INJECTOR_IMPL" + }, + { + "name": "INJECTOR_SCOPE" + }, + { + "name": "Inject" + }, + { + "name": "InjectFlags" + }, + { + "name": "InjectionToken" + }, + { + "name": "Injector" + }, + { + "name": "InnerSubscriber" + }, + { + "name": "IterableChangeRecord_" + }, + { + "name": "IterableDiffers" + }, + { + "name": "KeyEventsPlugin" + }, + { + "name": "KeyValueChangeRecord_" + }, + { + "name": "KeyValueDiffers" + }, + { + "name": "LOCALE_DATA" + }, + { + "name": "LOCALE_ID" + }, + { + "name": "LOCALE_ID" + }, + { + "name": "LifecycleHooksFeature" + }, + { + "name": "LocaleDataIndex" + }, + { + "name": "MODIFIER_KEYS" + }, + { + "name": "MODIFIER_KEY_GETTERS" + }, + { + "name": "MapOperator" + }, + { + "name": "MapSubscriber" + }, + { + "name": "MergeMapOperator" + }, + { + "name": "MergeMapSubscriber" + }, + { + "name": "ModuleWithComponentFactories" + }, + { + "name": "NAMESPACE_URIS" + }, + { + "name": "NEW_LINE" + }, + { + "name": "NG_ASYNC_VALIDATORS" + }, + { + "name": "NG_COMP_DEF" + }, + { + "name": "NG_DIR_DEF" + }, + { + "name": "NG_ELEMENT_ID" + }, + { + "name": "NG_FACTORY_DEF" + }, + { + "name": "NG_INJECTABLE_DEF" + }, + { + "name": "NG_INJECTOR_DEF" + }, + { + "name": "NG_INJ_DEF" + }, + { + "name": "NG_LOC_ID_DEF" + }, + { + "name": "NG_MODEL_WITH_FORM_CONTROL_WARNING" + }, + { + "name": "NG_MOD_DEF" + }, + { + "name": "NG_PIPE_DEF" + }, + { + "name": "NG_PROV_DEF" + }, + { + "name": "NG_PROV_DEF_FALLBACK" + }, + { + "name": "NG_VALIDATORS" + }, + { + "name": "NG_VALUE_ACCESSOR" + }, + { + "name": "NOT_FOUND" + }, + { + "name": "NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR" + }, + { + "name": "NOT_YET" + }, + { + "name": "NO_CHANGE" + }, + { + "name": "NULL_INJECTOR" + }, + { + "name": "NUMBER_VALUE_ACCESSOR" + }, + { + "name": "NgControl" + }, + { + "name": "NgControlStatus" + }, + { + "name": "NgControlStatusGroup" + }, + { + "name": "NgForOf" + }, + { + "name": "NgForOfContext" + }, + { + "name": "NgForm" + }, + { + "name": "NgLocaleLocalization" + }, + { + "name": "NgLocalization" + }, + { + "name": "NgModel" + }, + { + "name": "NgModelGroup" + }, + { + "name": "NgModuleFactory" + }, + { + "name": "NgModuleRef" + }, + { + "name": "NgModuleRef" + }, + { + "name": "NgOnChangesFeatureImpl" + }, + { + "name": "NgZone" + }, + { + "name": "NodeInjector" + }, + { + "name": "NodeInjectorFactory" + }, + { + "name": "NoopNgZone" + }, + { + "name": "NullInjector" + }, + { + "name": "NumberValueAccessor" + }, + { + "name": "ObjectUnsubscribedError" + }, + { + "name": "Observable" + }, + { + "name": "Optional" + }, + { + "name": "OuterSubscriber" + }, + { + "name": "PLATFORM_ID" + }, + { + "name": "PLATFORM_INITIALIZER" + }, + { + "name": "PlatformRef" + }, + { + "name": "Plural" + }, + { + "name": "R3Injector" + }, + { + "name": "RADIO_VALUE_ACCESSOR" + }, + { + "name": "RANGE_VALUE_ACCESSOR" + }, + { + "name": "REQUIRED_VALIDATOR" + }, + { + "name": "RadioControlRegistry" + }, + { + "name": "RadioControlValueAccessor" + }, + { + "name": "RangeValueAccessor" + }, + { + "name": "ReactiveErrors" + }, + { + "name": "ReactiveFormsComponent" + }, + { + "name": "ReactiveFormsComponent_div_14_Template" + }, + { + "name": "ReactiveFormsModule" + }, + { + "name": "RecordViewTuple" + }, + { + "name": "RefCountOperator" + }, + { + "name": "RefCountSubscriber" + }, + { + "name": "Renderer2" + }, + { + "name": "RendererFactory2" + }, + { + "name": "RendererStyleFlags2" + }, + { + "name": "RequiredValidator" + }, + { + "name": "RootComponent" + }, + { + "name": "RootViewRef" + }, + { + "name": "SCHEDULER" + }, + { + "name": "SELECT_MULTIPLE_VALUE_ACCESSOR" + }, + { + "name": "SELECT_VALUE_ACCESSOR" + }, + { + "name": "SERVER_TRANSITION_PROVIDERS" + }, + { + "name": "SWITCH_ELEMENT_REF_FACTORY" + }, + { + "name": "SWITCH_RENDERER2_FACTORY" + }, + { + "name": "SWITCH_TEMPLATE_REF_FACTORY" + }, + { + "name": "SWITCH_VIEW_CONTAINER_REF_FACTORY" + }, + { + "name": "SafeSubscriber" + }, + { + "name": "Sanitizer" + }, + { + "name": "SelectControlValueAccessor" + }, + { + "name": "SelectMultipleControlValueAccessor" + }, + { + "name": "Self" + }, + { + "name": "ShadowDomRenderer" + }, + { + "name": "SharedStylesHost" + }, + { + "name": "SimpleChange" + }, + { + "name": "SkipSelf" + }, + { + "name": "Subject" + }, + { + "name": "SubjectSubscriber" + }, + { + "name": "SubjectSubscription" + }, + { + "name": "Subscriber" + }, + { + "name": "Subscription" + }, + { + "name": "THROW_IF_NOT_FOUND" + }, + { + "name": "TRANSITION_ID" + }, + { + "name": "TemplateDrivenErrors" + }, + { + "name": "TemplateFormsComponent" + }, + { + "name": "TemplateFormsComponent_div_14_Template" + }, + { + "name": "TemplateRef" + }, + { + "name": "Testability" + }, + { + "name": "TestabilityRegistry" + }, + { + "name": "USE_VALUE" + }, + { + "name": "UnsubscriptionError" + }, + { + "name": "VERSION" + }, + { + "name": "Validators" + }, + { + "name": "Version" + }, + { + "name": "ViewContainerRef" + }, + { + "name": "ViewEncapsulation" + }, + { + "name": "ViewRef" + }, + { + "name": "_DOM" + }, + { + "name": "_DuplicateItemRecordList" + }, + { + "name": "_DuplicateMap" + }, + { + "name": "_NoopGetTestability" + }, + { + "name": "_NullComponentFactoryResolver" + }, + { + "name": "__extends" + }, + { + "name": "__forward_ref__" + }, + { + "name": "__global" + }, + { + "name": "__globalThis" + }, + { + "name": "__self" + }, + { + "name": "__window" + }, + { + "name": "_chromeNumKeyPadMap" + }, + { + "name": "_currentInjector" + }, + { + "name": "_enable_super_gross_mode_that_will_cause_bad_things" + }, + { + "name": "_global" + }, + { + "name": "_hasInvalidParent" + }, + { + "name": "_keyMap" + }, + { + "name": "_mergeErrors" + }, + { + "name": "_noControlError" + }, + { + "name": "_randomChar" + }, + { + "name": "_renderCompCount" + }, + { + "name": "_symbolIterator" + }, + { + "name": "_testabilityGetter" + }, + { + "name": "_throwError" + }, + { + "name": "addComponentLogic" + }, + { + "name": "addHostBindingsToExpandoInstructions" + }, + { + "name": "addRemoveViewFromContainer" + }, + { + "name": "addToArray" + }, + { + "name": "addToViewTree" + }, + { + "name": "allocLFrame" + }, + { + "name": "appendChild" + }, + { + "name": "applyNodes" + }, + { + "name": "applyProjectionRecursive" + }, + { + "name": "applyToElementOrContainer" + }, + { + "name": "applyView" + }, + { + "name": "attachPatchData" + }, + { + "name": "autoRegisterModuleById" + }, + { + "name": "baseElement" + }, + { + "name": "baseResolveDirective" + }, + { + "name": "bindingUpdated" + }, + { + "name": "bloomHasToken" + }, + { + "name": "callHook" + }, + { + "name": "callHooks" + }, + { + "name": "checkStable" + }, + { + "name": "classIndexOf" + }, + { + "name": "cleanUpView" + }, + { + "name": "coerceToAsyncValidator" + }, + { + "name": "coerceToValidator" + }, + { + "name": "collectStylingFromDirectives" + }, + { + "name": "collectStylingFromTAttrs" + }, + { + "name": "composeAsyncValidators" + }, + { + "name": "composeValidators" + }, + { + "name": "computeStaticStyling" + }, + { + "name": "concatStringsWithSpace" + }, + { + "name": "config" + }, + { + "name": "connectableObservableDescriptor" + }, + { + "name": "controlNameBinding" + }, + { + "name": "controlPath" + }, + { + "name": "createDirectivesInstances" + }, + { + "name": "createElementRef" + }, + { + "name": "createInjectorWithoutInjectorInstances" + }, + { + "name": "createLContainer" + }, + { + "name": "createLFrame" + }, + { + "name": "createLView" + }, + { + "name": "createPlatformFactory" + }, + { + "name": "createTNode" + }, + { + "name": "createTView" + }, + { + "name": "decoratePreventDefault" + }, + { + "name": "deepForEach" + }, + { + "name": "defaultErrorLogger" + }, + { + "name": "defaultIterableDiffers" + }, + { + "name": "defaultKeyValueDiffers" + }, + { + "name": "defaultScheduler" + }, + { + "name": "destroyLView" + }, + { + "name": "detachMovedView" + }, + { + "name": "detachView" + }, + { + "name": "detectChangesInRootView" + }, + { + "name": "detectChangesInternal" + }, + { + "name": "diPublicInInjector" + }, + { + "name": "domRendererFactory3" + }, + { + "name": "elementCreate" + }, + { + "name": "empty" + }, + { + "name": "enterDI" + }, + { + "name": "enterView" + }, + { + "name": "executeCheckHooks" + }, + { + "name": "executeInitAndCheckHooks" + }, + { + "name": "executeListenerWithErrorHandling" + }, + { + "name": "executeTemplate" + }, + { + "name": "executeViewQueryFn" + }, + { + "name": "extendStatics" + }, + { + "name": "extractDirectiveDef" + }, + { + "name": "extractPipeDef" + }, + { + "name": "fillProperties" + }, + { + "name": "findAttrIndexInNode" + }, + { + "name": "findStylingValue" + }, + { + "name": "flattenStyles" + }, + { + "name": "flattenUnsubscriptionErrors" + }, + { + "name": "forkJoinInternal" + }, + { + "name": "formArrayNameProvider" + }, + { + "name": "formControlBinding" + }, + { + "name": "formDirectiveProvider" + }, + { + "name": "formDirectiveProvider" + }, + { + "name": "formGroupNameProvider" + }, + { + "name": "forwardRef" + }, + { + "name": "from" + }, + { + "name": "fromArray" + }, + { + "name": "generateExpandoInstructionBlock" + }, + { + "name": "generateInitialInputs" + }, + { + "name": "generatePropertyAliases" + }, + { + "name": "getBeforeNodeForView" + }, + { + "name": "getCheckNoChangesMode" + }, + { + "name": "getClosureSafeProperty" + }, + { + "name": "getComponentDef" + }, + { + "name": "getComponentLViewByIndex" + }, + { + "name": "getConstant" + }, + { + "name": "getContainerRenderParent" + }, + { + "name": "getDOM" + }, + { + "name": "getDebugContext" + }, + { + "name": "getFactoryDef" + }, + { + "name": "getFirstLContainer" + }, + { + "name": "getInjectableDef" + }, + { + "name": "getInjectorDef" + }, + { + "name": "getInjectorIndex" + }, + { + "name": "getIsParent" + }, + { + "name": "getLCleanup" + }, + { + "name": "getLContainer" + }, + { + "name": "getLView" + }, + { + "name": "getLViewParent" + }, + { + "name": "getLocaleData" + }, + { + "name": "getNativeByTNode" + }, + { + "name": "getNearestLContainer" + }, + { + "name": "getNextLContainer" + }, + { + "name": "getNgModuleDef" + }, + { + "name": "getNodeInjectable" + }, + { + "name": "getNullInjector" + }, + { + "name": "getOrCreateInjectable" + }, + { + "name": "getOrCreateNodeInjectorForNode" + }, + { + "name": "getOrCreateTComponentView" + }, + { + "name": "getOrCreateTNode" + }, + { + "name": "getOriginalError" + }, + { + "name": "getOwnDefinition" + }, + { + "name": "getParentInjectorIndex" + }, + { + "name": "getParentInjectorLocation" + }, + { + "name": "getParentInjectorView" + }, + { + "name": "getParentInjectorViewOffset" + }, + { + "name": "getParentState" + }, + { + "name": "getPlatform" + }, + { + "name": "getPreviousIndex" + }, + { + "name": "getPreviousOrParentTNode" + }, + { + "name": "getPromiseCtor" + }, + { + "name": "getSelectedIndex" + }, + { + "name": "getSelectedTNode" + }, + { + "name": "getSimpleChangesStore" + }, + { + "name": "getSymbolIterator" + }, + { + "name": "getSymbolIterator" + }, + { + "name": "getTNode" + }, + { + "name": "getTStylingRangeNext" + }, + { + "name": "getTStylingRangePrev" + }, + { + "name": "getTView" + }, + { + "name": "growHostVarsSpace" + }, + { + "name": "handleError" + }, + { + "name": "hasParentInjector" + }, + { + "name": "hasTagAndTypeMatch" + }, + { + "name": "hasValidLength" + }, + { + "name": "hostReportError" + }, + { + "name": "identity" + }, + { + "name": "includeViewProviders" + }, + { + "name": "incrementInitPhaseFlags" + }, + { + "name": "indexOf" + }, + { + "name": "inheritContentQueries" + }, + { + "name": "inheritHostBindings" + }, + { + "name": "inheritViewQuery" + }, + { + "name": "initTNodeFlags" + }, + { + "name": "injectArgs" + }, + { + "name": "injectInjectorOnly" + }, + { + "name": "injectRootLimpMode" + }, + { + "name": "injectableDefOrInjectorDefFactory" + }, + { + "name": "insertBloom" + }, + { + "name": "instructionState" + }, + { + "name": "invertObject" + }, + { + "name": "invokeHostBindingsInCreationMode" + }, + { + "name": "isAnimationProp" + }, + { + "name": "isArray" + }, + { + "name": "isArrayLike" + }, + { + "name": "isComponentDef" + }, + { + "name": "isComponentHost" + }, + { + "name": "isContentQueryHost" + }, + { + "name": "isCssClassMatching" + }, + { + "name": "isDirectiveHost" + }, + { + "name": "isEmptyInputValue" + }, + { + "name": "isForwardRef" + }, + { + "name": "isFunction" + }, + { + "name": "isInlineTemplate" + }, + { + "name": "isJsObject" + }, + { + "name": "isLContainer" + }, + { + "name": "isLView" + }, + { + "name": "isListLikeIterable" + }, + { + "name": "isNodeMatchingSelector" + }, + { + "name": "isNodeMatchingSelectorList" + }, + { + "name": "isObject" + }, + { + "name": "isOptionsObj" + }, + { + "name": "isPositive" + }, + { + "name": "isPresent" + }, + { + "name": "isProceduralRenderer" + }, + { + "name": "isPromise" + }, + { + "name": "isPromise" + }, + { + "name": "isPropertyUpdated" + }, + { + "name": "isRootView" + }, + { + "name": "isScheduler" + }, + { + "name": "isStylingMatch" + }, + { + "name": "isStylingValuePresent" + }, + { + "name": "isTypeProvider" + }, + { + "name": "isValueProvider" + }, + { + "name": "iterator" + }, + { + "name": "keyValDiff" + }, + { + "name": "keyValueArrayGet" + }, + { + "name": "keyValueArrayIndexOf" + }, + { + "name": "keyValueArraySet" + }, + { + "name": "leaveDI" + }, + { + "name": "leaveView" + }, + { + "name": "leaveViewLight" + }, + { + "name": "localeEn" + }, + { + "name": "makeParamDecorator" + }, + { + "name": "makeRecord" + }, + { + "name": "map" + }, + { + "name": "markAsComponentHost" + }, + { + "name": "markDuplicates" + }, + { + "name": "markViewDirty" + }, + { + "name": "maybeUnwrapEmpty" + }, + { + "name": "maybeUnwrapFn" + }, + { + "name": "maybeWrapInNotSelector" + }, + { + "name": "mergeAll" + }, + { + "name": "mergeHostAttribute" + }, + { + "name": "mergeHostAttrs" + }, + { + "name": "modelGroupProvider" + }, + { + "name": "modules" + }, + { + "name": "multiFactoryAdd" + }, + { + "name": "multiProvidersFactoryResolver" + }, + { + "name": "multiResolve" + }, + { + "name": "multiViewProvidersFactoryResolver" + }, + { + "name": "nativeAppendChild" + }, + { + "name": "nativeAppendOrInsertBefore" + }, + { + "name": "nativeInsertBefore" + }, + { + "name": "nativeParentNode" + }, + { + "name": "nextBindingIndex" + }, + { + "name": "nextNgElementId" + }, + { + "name": "ngOnChangesSetInput" + }, + { + "name": "noSideEffects" + }, + { + "name": "noop" + }, + { + "name": "noop" + }, + { + "name": "normalizeAsyncValidator" + }, + { + "name": "normalizeValidator" + }, + { + "name": "observable" + }, + { + "name": "onEnter" + }, + { + "name": "onLeave" + }, + { + "name": "optionsReducer" + }, + { + "name": "pickAsyncValidators" + }, + { + "name": "pickValidators" + }, + { + "name": "pipeFromArray" + }, + { + "name": "platformBrowser" + }, + { + "name": "platformCore" + }, + { + "name": "promise" + }, + { + "name": "providerToFactory" + }, + { + "name": "readPatchedLView" + }, + { + "name": "refCount" + }, + { + "name": "refreshComponent" + }, + { + "name": "refreshContentQueries" + }, + { + "name": "refreshView" + }, + { + "name": "registerDestroyHooksIfSupported" + }, + { + "name": "registerPostOrderHooks" + }, + { + "name": "rememberChangeHistoryAndInvokeOnChangesHook" + }, + { + "name": "remove" + }, + { + "name": "removeDir" + }, + { + "name": "removeFromArray" + }, + { + "name": "renderComponent" + }, + { + "name": "renderComponentOrTemplate" + }, + { + "name": "renderStringify" + }, + { + "name": "renderView" + }, + { + "name": "resetPreOrderHookFlags" + }, + { + "name": "resolveDirectives" + }, + { + "name": "resolveForwardRef" + }, + { + "name": "resolveProvider" + }, + { + "name": "resolvedPromise" + }, + { + "name": "resolvedPromise" + }, + { + "name": "rxSubscriber" + }, + { + "name": "saveNameToExportMap" + }, + { + "name": "saveResolvedLocalsInData" + }, + { + "name": "scheduleArray" + }, + { + "name": "scheduleMicroTask" + }, + { + "name": "searchTokensOnInjector" + }, + { + "name": "selectIndexInternal" + }, + { + "name": "selectValueAccessor" + }, + { + "name": "setBindingRootForHostBindings" + }, + { + "name": "setCheckNoChangesMode" + }, + { + "name": "setCurrentDirectiveIndex" + }, + { + "name": "setCurrentInjector" + }, + { + "name": "setCurrentQueryIndex" + }, + { + "name": "setDirectiveInputsWhichShadowsStyling" + }, + { + "name": "setIncludeViewProviders" + }, + { + "name": "setInjectImplementation" + }, + { + "name": "setInputsForProperty" + }, + { + "name": "setInputsFromAttrs" + }, + { + "name": "setLocaleId" + }, + { + "name": "setPreviousOrParentTNode" + }, + { + "name": "setSelectedIndex" + }, + { + "name": "setTStylingRangeNext" + }, + { + "name": "setTStylingRangeNextDuplicate" + }, + { + "name": "setTStylingRangePrevDuplicate" + }, + { + "name": "setUpAttributes" + }, + { + "name": "setUpControl" + }, + { + "name": "setUpFormContainer" + }, + { + "name": "shareSubjectFactory" + }, + { + "name": "shouldSearchParent" + }, + { + "name": "stringify" + }, + { + "name": "stringifyCSSSelector" + }, + { + "name": "stringifyForError" + }, + { + "name": "subscribeTo" + }, + { + "name": "subscribeToArray" + }, + { + "name": "syncPendingControls" + }, + { + "name": "throwMixedMultiProviderError" + }, + { + "name": "throwMultipleComponentError" + }, + { + "name": "toObservable" + }, + { + "name": "toRefArray" + }, + { + "name": "toTStylingRange" + }, + { + "name": "trackByIdentity" + }, + { + "name": "u" + }, + { + "name": "unimplemented" + }, + { + "name": "unwrapRNode" + }, + { + "name": "updateControl" + }, + { + "name": "updateMicroTaskStatus" + }, + { + "name": "updateTransplantedViewCount" + }, + { + "name": "viewAttachedToChangeDetector" + }, + { + "name": "wrapListener" + }, + { + "name": "writeDirectClass" + }, + { + "name": "writeDirectStyle" + }, + { + "name": "ɵAbstractFormGroupDirective_BaseFactory" + }, + { + "name": "ɵInternalFormsSharedModule" + }, + { + "name": "ɵNgNoValidate" + }, + { + "name": "ɵɵInheritDefinitionFeature" + }, + { + "name": "ɵɵNgOnChangesFeature" + }, + { + "name": "ɵɵProvidersFeature" + }, + { + "name": "ɵɵadvance" + }, + { + "name": "ɵɵattribute" + }, + { + "name": "ɵɵclassProp" + }, + { + "name": "ɵɵdefineComponent" + }, + { + "name": "ɵɵdefineDirective" + }, + { + "name": "ɵɵdefineInjectable" + }, + { + "name": "ɵɵdefineInjector" + }, + { + "name": "ɵɵdefineNgModule" + }, + { + "name": "ɵɵdirectiveInject" + }, + { + "name": "ɵɵelement" + }, + { + "name": "ɵɵelementEnd" + }, + { + "name": "ɵɵelementStart" + }, + { + "name": "ɵɵgetFactoryOf" + }, + { + "name": "ɵɵgetInheritedFactory" + }, + { + "name": "ɵɵinject" + }, + { + "name": "ɵɵlistener" + }, + { + "name": "ɵɵnextContext" + }, + { + "name": "ɵɵproperty" + }, + { + "name": "ɵɵtemplate" + }, + { + "name": "ɵɵtext" + } +] \ No newline at end of file diff --git a/packages/core/test/bundling/forms/forms_e2e_spec.ts b/packages/core/test/bundling/forms/forms_e2e_spec.ts new file mode 100644 index 0000000000..eacb295420 --- /dev/null +++ b/packages/core/test/bundling/forms/forms_e2e_spec.ts @@ -0,0 +1,67 @@ +/** + * @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 + */ + +import '@angular/compiler'; +import {ɵwhenRendered as whenRendered} from '@angular/core'; +import {withBody} from '@angular/private/testing'; +import * as path from 'path'; + +const PACKAGE = 'angular/packages/core/test/bundling/forms'; +const BUNDLES = ['bundle.js', 'bundle.min_debug.js', 'bundle.min.js']; + +describe('functional test for forms', () => { + BUNDLES.forEach((bundle) => { + describe(`using ${bundle} bundle`, () => { + it('should render template form', withBody('', async () => { + require(path.join(PACKAGE, bundle)); + await (window as any).waitForApp; + + // Template forms + const templateFormsComponent = (window as any).templateFormsComponent; + await whenRendered(templateFormsComponent); + + const templateForm = document.querySelector('app-template-forms')!; + + // Check for inputs + const iputs = templateForm.querySelectorAll('input'); + expect(iputs.length).toBe(5); + + // Check for button + const templateButtons = templateForm.querySelectorAll('button'); + expect(templateButtons.length).toBe(1); + expect(templateButtons[0]).toBeDefined(); + + // Make sure button click works + const templateFormSpy = spyOn(templateFormsComponent, 'addCity'); + templateButtons[0].click(); + expect(templateFormSpy).toHaveBeenCalled(); + + // Reactive forms + const reactiveFormsComponent = (window as any).reactiveFormsComponent; + await whenRendered(reactiveFormsComponent); + + const reactiveForm = document.querySelector('app-reactive-forms')!; + + // Check for inputs + const inputs = reactiveForm.querySelectorAll('input'); + expect(inputs.length).toBe(5); + + // Check for button + const reactiveButtons = reactiveForm.querySelectorAll('button'); + expect(reactiveButtons.length).toBe(1); + expect(reactiveButtons[0]).toBeDefined(); + + // Make sure button click works + const reactiveFormSpy = spyOn(reactiveFormsComponent, 'addCity').and.callThrough(); + reactiveButtons[0].click(); + expect(reactiveFormSpy).toHaveBeenCalled(); + expect(reactiveFormsComponent.addresses.length).toBe(2); + })); + }); + }); +}); diff --git a/packages/core/test/bundling/forms/index.html b/packages/core/test/bundling/forms/index.html new file mode 100644 index 0000000000..0703f0dbb5 --- /dev/null +++ b/packages/core/test/bundling/forms/index.html @@ -0,0 +1,32 @@ + + + + + Angular Forms Example + + + + + + + + + + diff --git a/packages/core/test/bundling/forms/index.ts b/packages/core/test/bundling/forms/index.ts new file mode 100644 index 0000000000..4a2e8d1294 --- /dev/null +++ b/packages/core/test/bundling/forms/index.ts @@ -0,0 +1,138 @@ +/** + * @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 + */ +import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core'; +import {FormArray, FormBuilder, FormControl, FormGroup, FormsModule, NgForm, ReactiveFormsModule, Validators} from '@angular/forms'; +import {BrowserModule, platformBrowser} from '@angular/platform-browser'; + +@Component({ + selector: 'app-template-forms', + template: ` +
+
+
+ First Name: + +
+
+ Last Name: + +
+
+ Subscribe: + +
+ +
Disabled:
+ +
+ City +
+ + +
+
`, +}) +class TemplateFormsComponent { + name = {first: 'Nancy', last: 'Drew', subscribed: true}; + addresses = [{city: 'Toronto'}]; + constructor() { + // We use this reference in our test + (window as any).templateFormsComponent = this; + } + + addCity() { + this.addresses.push(({city: ''})); + } +} + +@Component({ + selector: 'app-reactive-forms', + template: ` +
+
+ First Name: + +
+
+ Last Name: + +
+ +
+ Subscribe: + +
+ +
Disabled:
+
+
+
City:
+
+
+ +
`, +}) +class ReactiveFormsComponent { + profileForm!: FormGroup; + addresses!: FormArray; + + get itemControls() { + return (this.profileForm.get('addresses') as FormArray).controls; + } + + constructor(private formBuilder: FormBuilder) { + // We use this reference in our test + (window as any).reactiveFormsComponent = this; + } + + ngOnInit() { + this.profileForm = new FormGroup({ + firstName: new FormControl('', Validators.required), + lastName: new FormControl(''), + addresses: new FormArray([]), + subscribed: new FormControl(), + disabledInput: new FormControl({value: '', disabled: true}), + }); + + this.addCity(); + } + + createItem(): FormGroup { + return this.formBuilder.group({ + city: '', + }); + } + + addCity(): void { + this.addresses = this.profileForm.get('addresses') as FormArray; + this.addresses.push(this.createItem()); + } +} + +@Component({ + selector: 'app-root', + template: ` + + + ` +}) +class RootComponent { +} + +@NgModule({ + declarations: [RootComponent, TemplateFormsComponent, ReactiveFormsComponent], + imports: [BrowserModule, FormsModule, ReactiveFormsModule] +}) +class FormsExampleModule { + ngDoBootstrap(app: any) { + app.bootstrap(RootComponent); + } +} + +(window as any).waitForApp = platformBrowser().bootstrapModuleFactory( + new NgModuleFactory(FormsExampleModule), {ngZone: 'noop'}); diff --git a/packages/core/test/bundling/forms/treeshaking_spec.ts b/packages/core/test/bundling/forms/treeshaking_spec.ts new file mode 100644 index 0000000000..546a88ee0a --- /dev/null +++ b/packages/core/test/bundling/forms/treeshaking_spec.ts @@ -0,0 +1,35 @@ +/** + * @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 + */ + +import '@angular/compiler'; +import * as fs from 'fs'; +import * as path from 'path'; + +const UTF8 = { + encoding: 'utf-8' +}; +const PACKAGE = 'angular/packages/core/test/bundling/forms'; + +describe('treeshaking with uglify', () => { + let content: string; + // We use the debug version as otherwise symbols/identifiers would be mangled (and the test would + // always pass) + const contentPath = require.resolve(path.join(PACKAGE, 'bundle.min_debug.js')); + beforeAll(() => { + content = fs.readFileSync(contentPath, UTF8); + }); + + it('should drop unused TypeScript helpers', () => { + expect(content).not.toContain('__asyncGenerator'); + }); + + it('should not contain rxjs from commonjs distro', () => { + expect(content).not.toContain('commonjsGlobal'); + expect(content).not.toContain('createCommonjsModule'); + }); +}); From d0060dcfdc11a589337485ce32c2e81c45128ca3 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Mon, 20 Jul 2020 11:53:26 -0700 Subject: [PATCH 019/133] refactor(dev-infra): Add support for groups in the conditions evaluator (#38164) Conditions can refer to the groups array that is a list of the preceding groups. This commit adds support to the verification for those conditions. This commit also adds some tests to the parsing and condition matching to ensure everything works as expected. PR Close #38164 --- dev-infra/pullapprove/BUILD.bazel | 24 ++++++ dev-infra/pullapprove/condition_evaluator.ts | 69 ++++----------- dev-infra/pullapprove/group.ts | 35 +++++--- dev-infra/pullapprove/parse-yaml.ts | 10 +++ dev-infra/pullapprove/pullapprove_arrays.ts | 89 ++++++++++++++++++++ dev-infra/pullapprove/utils.ts | 24 ++++++ dev-infra/pullapprove/verify.spec.ts | 88 +++++++++++++++++++ dev-infra/pullapprove/verify.ts | 10 +-- dev-infra/utils/BUILD.bazel | 1 + 9 files changed, 280 insertions(+), 70 deletions(-) create mode 100644 dev-infra/pullapprove/pullapprove_arrays.ts create mode 100644 dev-infra/pullapprove/utils.ts create mode 100644 dev-infra/pullapprove/verify.spec.ts diff --git a/dev-infra/pullapprove/BUILD.bazel b/dev-infra/pullapprove/BUILD.bazel index f73a2aaeca..4cd28756f0 100644 --- a/dev-infra/pullapprove/BUILD.bazel +++ b/dev-infra/pullapprove/BUILD.bazel @@ -1,4 +1,5 @@ load("@npm_bazel_typescript//:index.bzl", "ts_library") +load("//tools:defaults.bzl", "jasmine_node_test") ts_library( name = "pullapprove", @@ -8,6 +9,8 @@ ts_library( "group.ts", "logging.ts", "parse-yaml.ts", + "pullapprove_arrays.ts", + "utils.ts", "verify.ts", ], module_name = "@angular/dev-infra-private/pullapprove", @@ -25,3 +28,24 @@ ts_library( "@npm//yargs", ], ) + +ts_library( + name = "pullapprove_test_lib", + testonly = True, + srcs = glob( + ["*.spec.ts"], + ), + visibility = ["//visibility:private"], + deps = [ + ":pullapprove", + "@npm//@types/jasmine", + "@npm//@types/node", + "@npm//typescript", + ], +) + +jasmine_node_test( + name = "pullapprove_test", + srcs = [":pullapprove_test_lib"], + visibility = ["//visibility:private"], +) diff --git a/dev-infra/pullapprove/condition_evaluator.ts b/dev-infra/pullapprove/condition_evaluator.ts index 340e0b84a2..b64eb7a5a8 100644 --- a/dev-infra/pullapprove/condition_evaluator.ts +++ b/dev-infra/pullapprove/condition_evaluator.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {IMinimatch, Minimatch} from 'minimatch'; - -/** Map that holds patterns and their corresponding Minimatch globs. */ -const patternCache = new Map(); +import {PullApproveGroup} from './group'; +import {PullApproveGroupArray, PullApproveStringArray} from './pullapprove_arrays'; +import {getOrCreateGlob} from './utils'; /** * Context that is provided to conditions. Conditions can use various helpers @@ -18,30 +17,34 @@ const patternCache = new Map(); */ const conditionContext = { 'len': (value: any[]) => value.length, - 'contains_any_globs': (files: PullApproveArray, patterns: string[]) => { + 'contains_any_globs': (files: PullApproveStringArray, patterns: string[]) => { // Note: Do not always create globs for the same pattern again. This method // could be called for each source file. Creating glob's is expensive. return files.some(f => patterns.some(pattern => getOrCreateGlob(pattern).match(f))); - } + }, }; /** * Converts a given condition to a function that accepts a set of files. The returned * function can be called to check if the set of files matches the condition. */ -export function convertConditionToFunction(expr: string): (files: string[]) => boolean { - // Creates a dynamic function with the specified expression. The first parameter will - // be `files` as that corresponds to the supported `files` variable that can be accessed - // in PullApprove condition expressions. The followed parameters correspond to other - // context variables provided by PullApprove for conditions. - const evaluateFn = new Function('files', ...Object.keys(conditionContext), ` +export function convertConditionToFunction(expr: string): ( + files: string[], groups: PullApproveGroup[]) => boolean { + // Creates a dynamic function with the specified expression. + // The first parameter will be `files` as that corresponds to the supported `files` variable that + // can be accessed in PullApprove condition expressions. The second parameter is the list of + // PullApproveGroups that are accessible in the condition expressions. The followed parameters + // correspond to other context variables provided by PullApprove for conditions. + const evaluateFn = new Function('files', 'groups', ...Object.keys(conditionContext), ` return (${transformExpressionToJs(expr)}); `); // Create a function that calls the dynamically constructed function which mimics // the condition expression that is usually evaluated with Python in PullApprove. - return files => { - const result = evaluateFn(new PullApproveArray(...files), ...Object.values(conditionContext)); + return (files, groups) => { + const result = evaluateFn( + new PullApproveStringArray(...files), new PullApproveGroupArray(...groups), + ...Object.values(conditionContext)); // If an array is returned, we consider the condition as active if the array is not // empty. This matches PullApprove's condition evaluation that is based on Python. if (Array.isArray(result)) { @@ -59,41 +62,3 @@ export function convertConditionToFunction(expr: string): (files: string[]) => b function transformExpressionToJs(expression: string): string { return expression.replace(/not\s+/g, '!'); } - -/** - * Superset of a native array. The superset provides methods which mimic the - * list data structure used in PullApprove for files in conditions. - */ -class PullApproveArray extends Array { - constructor(...elements: string[]) { - super(...elements); - - // Set the prototype explicitly because in ES5, the prototype is accidentally - // lost due to a limitation in down-leveling. - // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work. - Object.setPrototypeOf(this, PullApproveArray.prototype); - } - - /** Returns a new array which only includes files that match the given pattern. */ - include(pattern: string): PullApproveArray { - return new PullApproveArray(...this.filter(s => getOrCreateGlob(pattern).match(s))); - } - - /** Returns a new array which only includes files that did not match the given pattern. */ - exclude(pattern: string): PullApproveArray { - return new PullApproveArray(...this.filter(s => !getOrCreateGlob(pattern).match(s))); - } -} - -/** - * Gets a glob for the given pattern. The cached glob will be returned - * if available. Otherwise a new glob will be created and cached. - */ -function getOrCreateGlob(pattern: string) { - if (patternCache.has(pattern)) { - return patternCache.get(pattern)!; - } - const glob = new Minimatch(pattern, {dot: true}); - patternCache.set(pattern, glob); - return glob; -} diff --git a/dev-infra/pullapprove/group.ts b/dev-infra/pullapprove/group.ts index a27ef876cf..45de9eac23 100644 --- a/dev-infra/pullapprove/group.ts +++ b/dev-infra/pullapprove/group.ts @@ -6,14 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ -import {error} from '../utils/console'; +import {error, warn} from '../utils/console'; import {convertConditionToFunction} from './condition_evaluator'; import {PullApproveGroupConfig} from './parse-yaml'; +import {PullApproveGroupStateDependencyError} from './pullapprove_arrays'; /** A condition for a group. */ interface GroupCondition { expression: string; - checkFn: (files: string[]) => boolean; + checkFn: (files: string[], groups: PullApproveGroup[]) => boolean; matchedFiles: Set; } @@ -39,7 +40,9 @@ export class PullApproveGroup { /** List of conditions for the group. */ conditions: GroupCondition[] = []; - constructor(public groupName: string, config: PullApproveGroupConfig) { + constructor( + public groupName: string, config: PullApproveGroupConfig, + readonly precedingGroups: PullApproveGroup[] = []) { this._captureConditions(config); } @@ -78,18 +81,30 @@ export class PullApproveGroup { testFile(filePath: string): boolean { return this.conditions.every(({matchedFiles, checkFn, expression}) => { try { - const matchesFile = checkFn([filePath]); + const matchesFile = checkFn([filePath], this.precedingGroups); if (matchesFile) { matchedFiles.add(filePath); } return matchesFile; } catch (e) { - const errMessage = `Condition could not be evaluated: \n\n` + - `From the [${this.groupName}] group:\n` + - ` - ${expression}` + - `\n\n${e.message} ${e.stack}\n\n`; - error(errMessage); - process.exit(1); + // In the case of a condition that depends on the state of groups we want to just + // warn that the verification can't accurately evaluate the condition and then + // continue processing. Other types of errors fail the verification, as conditions + // should otherwise be able to execute without throwing. + if (e instanceof PullApproveGroupStateDependencyError) { + const errMessage = `Condition could not be evaluated: \n` + + `${e.message}\n` + + `From the [${this.groupName}] group:\n` + + ` - ${expression}`; + warn(errMessage); + } else { + const errMessage = `Condition could not be evaluated: \n\n` + + `From the [${this.groupName}] group:\n` + + ` - ${expression}` + + `\n\n${e.message} ${e.stack}\n\n`; + error(errMessage); + process.exit(1); + } } }); } diff --git a/dev-infra/pullapprove/parse-yaml.ts b/dev-infra/pullapprove/parse-yaml.ts index c0856d1763..fc0289ecce 100644 --- a/dev-infra/pullapprove/parse-yaml.ts +++ b/dev-infra/pullapprove/parse-yaml.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {parse as parseYaml} from 'yaml'; +import {PullApproveGroup} from './group'; export interface PullApproveGroupConfig { conditions?: string[]; @@ -33,3 +34,12 @@ export interface PullApproveConfig { export function parsePullApproveYaml(rawYaml: string): PullApproveConfig { return parseYaml(rawYaml, {merge: true}) as PullApproveConfig; } + +/** Parses all of the groups defined in the pullapprove yaml. */ +export function getGroupsFromYaml(pullApproveYamlRaw: string): PullApproveGroup[] { + /** JSON representation of the pullapprove yaml file. */ + const pullApprove = parsePullApproveYaml(pullApproveYamlRaw); + return Object.entries(pullApprove.groups).reduce((groups, [groupName, group]) => { + return groups.concat(new PullApproveGroup(groupName, group, groups)); + }, [] as PullApproveGroup[]); +} diff --git a/dev-infra/pullapprove/pullapprove_arrays.ts b/dev-infra/pullapprove/pullapprove_arrays.ts new file mode 100644 index 0000000000..46cec6d522 --- /dev/null +++ b/dev-infra/pullapprove/pullapprove_arrays.ts @@ -0,0 +1,89 @@ +/** + * @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 + */ +import {PullApproveGroup} from './group'; +import {getOrCreateGlob} from './utils'; + +export class PullApproveGroupStateDependencyError extends Error { + constructor(message?: string) { + super(message); + // Set the prototype explicitly because in ES5, the prototype is accidentally + // lost due to a limitation in down-leveling. + // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work. + Object.setPrototypeOf(this, PullApproveGroupStateDependencyError.prototype); + // Error names are displayed in their stack but can't be set in the constructor. + this.name = PullApproveGroupStateDependencyError.name; + } +} + +/** + * Superset of a native array. The superset provides methods which mimic the + * list data structure used in PullApprove for files in conditions. + */ +export class PullApproveStringArray extends Array { + constructor(...elements: string[]) { + super(...elements); + + // Set the prototype explicitly because in ES5, the prototype is accidentally + // lost due to a limitation in down-leveling. + // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work. + Object.setPrototypeOf(this, PullApproveStringArray.prototype); + } + /** Returns a new array which only includes files that match the given pattern. */ + include(pattern: string): PullApproveStringArray { + return new PullApproveStringArray(...this.filter(s => getOrCreateGlob(pattern).match(s))); + } + + /** Returns a new array which only includes files that did not match the given pattern. */ + exclude(pattern: string): PullApproveStringArray { + return new PullApproveStringArray(...this.filter(s => !getOrCreateGlob(pattern).match(s))); + } +} + +/** + * Superset of a native array. The superset provides methods which mimic the + * list data structure used in PullApprove for groups in conditions. + */ +export class PullApproveGroupArray extends Array { + constructor(...elements: PullApproveGroup[]) { + super(...elements); + + // Set the prototype explicitly because in ES5, the prototype is accidentally + // lost due to a limitation in down-leveling. + // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work. + Object.setPrototypeOf(this, PullApproveGroupArray.prototype); + } + + include(pattern: string): PullApproveGroupArray { + return new PullApproveGroupArray(...this.filter(s => s.groupName.match(pattern))); + } + + /** Returns a new array which only includes files that did not match the given pattern. */ + exclude(pattern: string): PullApproveGroupArray { + return new PullApproveGroupArray(...this.filter(s => s.groupName.match(pattern))); + } + + get pending() { + throw new PullApproveGroupStateDependencyError(); + } + + get active() { + throw new PullApproveGroupStateDependencyError(); + } + + get inactive() { + throw new PullApproveGroupStateDependencyError(); + } + + get rejected() { + throw new PullApproveGroupStateDependencyError(); + } + + get names() { + return this.map(g => g.groupName); + } +} diff --git a/dev-infra/pullapprove/utils.ts b/dev-infra/pullapprove/utils.ts new file mode 100644 index 0000000000..8e93f07a9f --- /dev/null +++ b/dev-infra/pullapprove/utils.ts @@ -0,0 +1,24 @@ +/** + * @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 + */ +import {IMinimatch, Minimatch} from 'minimatch'; + +/** Map that holds patterns and their corresponding Minimatch globs. */ +const patternCache = new Map(); + +/** + * Gets a glob for the given pattern. The cached glob will be returned + * if available. Otherwise a new glob will be created and cached. + */ +export function getOrCreateGlob(pattern: string) { + if (patternCache.has(pattern)) { + return patternCache.get(pattern)!; + } + const glob = new Minimatch(pattern, {dot: true}); + patternCache.set(pattern, glob); + return glob; +} diff --git a/dev-infra/pullapprove/verify.spec.ts b/dev-infra/pullapprove/verify.spec.ts new file mode 100644 index 0000000000..f3ddb62088 --- /dev/null +++ b/dev-infra/pullapprove/verify.spec.ts @@ -0,0 +1,88 @@ +/** + * @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 + */ +import {PullApproveGroup} from './group'; +import {getGroupsFromYaml} from './parse-yaml'; + +describe('group parsing', () => { + it('gets group name', () => { + const groupName = 'fw-migrations'; + const groups = getGroupsFromYaml(` + groups: + ${groupName}: + type: optional + `); + expect(groups[0].groupName).toBe(groupName); + }); + + it('gets correct number of groups', () => { + const groups = getGroupsFromYaml(` + groups: + fw-migrations: + type: optional + fw-core: + type: optional + `); + expect(groups.length).toBe(2); + }); + + it('gets preceding groups', () => { + const groups = getGroupsFromYaml(` + groups: + fw-migrations: + type: optional + fw-core: + type: optional + dev-infra: + type: optional + `); + const fwMigrations = getGroupByName(groups, 'fw-migrations')!; + const fwCore = getGroupByName(groups, 'fw-core')!; + const devInfra = getGroupByName(groups, 'dev-infra')!; + expect(getGroupNames(fwMigrations.precedingGroups)).toEqual([]); + expect(getGroupNames(fwCore.precedingGroups)).toEqual([fwMigrations.groupName]); + expect(getGroupNames(devInfra.precedingGroups)).toEqual([ + fwMigrations.groupName, fwCore.groupName + ]); + }); + + it('matches file conditions', () => { + const groups = getGroupsFromYaml(` + groups: + fw-core: + conditions: + - contains_any_globs(files, ['packages/core/**']) + `); + const fwCore = getGroupByName(groups, 'fw-core')!; + expect(fwCore.testFile('packages/core/test.ts')).toBe(true); + expect(fwCore.testFile('some/other/location/test.ts')).toBe(false); + }); + + it('allows conditions based on groups', () => { + const groups = getGroupsFromYaml(` + groups: + fw-migrations: + conditions: + - len(groups) > 0 + fw-core: + conditions: + - len(groups.active) > 0 + `); + const fwMigrations = getGroupByName(groups, 'fw-migrations')!; + expect(() => fwMigrations.testFile('any')).not.toThrow(); + const fwCore = getGroupByName(groups, 'fw-core')!; + expect(() => fwCore.testFile('any')).not.toThrow(); + }); +}); + +function getGroupByName(groups: PullApproveGroup[], name: string): PullApproveGroup|undefined { + return groups.find(g => g.groupName === name); +} + +function getGroupNames(groups: PullApproveGroup[]) { + return groups.map(g => g.groupName); +} diff --git a/dev-infra/pullapprove/verify.ts b/dev-infra/pullapprove/verify.ts index f349edeeef..d8302c2caa 100644 --- a/dev-infra/pullapprove/verify.ts +++ b/dev-infra/pullapprove/verify.ts @@ -11,10 +11,8 @@ import {resolve} from 'path'; import {getRepoBaseDir} from '../utils/config'; import {debug, info} from '../utils/console'; import {allFiles} from '../utils/repo-files'; - -import {PullApproveGroup} from './group'; import {logGroup, logHeader} from './logging'; -import {parsePullApproveYaml} from './parse-yaml'; +import {getGroupsFromYaml} from './parse-yaml'; export function verify() { /** Full path to PullApprove config file */ @@ -23,12 +21,8 @@ export function verify() { const REPO_FILES = allFiles(); /** The pull approve config file. */ const pullApproveYamlRaw = readFileSync(PULL_APPROVE_YAML_PATH, 'utf8'); - /** JSON representation of the pullapprove yaml file. */ - const pullApprove = parsePullApproveYaml(pullApproveYamlRaw); /** All of the groups defined in the pullapprove yaml. */ - const groups = Object.entries(pullApprove.groups).map(([groupName, group]) => { - return new PullApproveGroup(groupName, group); - }); + const groups = getGroupsFromYaml(pullApproveYamlRaw); /** * PullApprove groups without conditions. These are skipped in the verification * as those would always be active and cause zero unmatched files. diff --git a/dev-infra/utils/BUILD.bazel b/dev-infra/utils/BUILD.bazel index f1bdc1976d..248aaddacf 100644 --- a/dev-infra/utils/BUILD.bazel +++ b/dev-infra/utils/BUILD.bazel @@ -16,6 +16,7 @@ ts_library( "@npm//@types/node", "@npm//@types/shelljs", "@npm//chalk", + "@npm//inquirer", "@npm//shelljs", "@npm//tslib", "@npm//typed-graphqlify", From dd09fb5348e30dd761d3571749c2db0d78c31d8a Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 22 Jul 2020 20:13:21 +0300 Subject: [PATCH 020/133] build(docs-infra): upgrade cli command docs sources to b0b27361d (#38182) Updating [angular#master](https://github.com/angular/angular/tree/master) from [cli-builds#master](https://github.com/angular/cli-builds/tree/master). ## Relevant changes in [commit range](https://github.com/angular/cli-builds/compare/b76099083...b0b27361d): **Modified** - help/update.json PR Close #38182 --- aio/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/package.json b/aio/package.json index 09fd239d3b..d47ce00c4b 100644 --- a/aio/package.json +++ b/aio/package.json @@ -23,7 +23,7 @@ "build-local-with-viewengine": "yarn ~~build", "prebuild-local-with-viewengine-ci": "node scripts/switch-to-viewengine && yarn setup-local-ci", "build-local-with-viewengine-ci": "yarn ~~build --progress=false", - "extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js b76099083", + "extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js b0b27361d", "lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint", "test": "yarn check-env && ng test", "pree2e": "yarn check-env && yarn update-webdriver", From ee22aa592e28b96d3b072d7e36cff31085426bb7 Mon Sep 17 00:00:00 2001 From: Saif Date: Wed, 22 Jul 2020 23:30:08 +0300 Subject: [PATCH 021/133] fix(docs-infra): correctly display copy button in IE11 (#38186) Fix button top portion was clipped in IE11 by setting overflow to visible Fixes #37816 PR Close #38186 --- aio/src/styles/2-modules/_code.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/aio/src/styles/2-modules/_code.scss b/aio/src/styles/2-modules/_code.scss index 9065bda550..5e87cec15a 100644 --- a/aio/src/styles/2-modules/_code.scss +++ b/aio/src/styles/2-modules/_code.scss @@ -108,6 +108,7 @@ aio-code pre { top: -7px; right: -19px; padding: 0; + overflow: visible; // This is required for the button to be displayed correctly in IE11. color: $blue-grey-200; background-color: transparent; From 88d4b269b5176c3288012a7d04211b1d1daa7f27 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 22 Jul 2020 22:23:55 +0300 Subject: [PATCH 022/133] build(docs-infra): simplify `ExampleZipper` by removing `PackageJsonCustomizer` (#38192) Previously, `ExampleZipper` (the tool used for creating ZIP archives from our docs examples) used the `PackageJsonCustomizer` to generate `package.json` files for each example type. This had the following drawbacks: - The generated files had to be kept up-to-date with the corresponding boilerplate files in `aio/tools/examples/shared/boilerplate/` and there was no easy way to find out when the files got out-of-sync. - The `PackageJsonCustomizer` logic was non-trivial and difficult to reason about. - The same information was duplicated in the boilerplate files and the customizer configuration files. This setup was useful when we used a single `package.json` file for all docs examples. Now, however, each example type can have its own boilerplate `package.json` file, including scripts and dependencies relevant to the example type. Therefore, it is no longer necessary to generate `package.json` files for ZIP archives. This commit eliminates the drawbacks mentioned above and simplifies the `ExampleZipper` tool by removing `PackageJsonCustomizer` and re-using the boilerplate `package.json` files for ZIP archives. The changes in this commit also fix some ZIP archives that were previously broken (for example due to missing dependencies). PR Close #38192 --- aio/tools/RELEASE.md | 8 --- aio/tools/example-zipper/README.md | 31 +++------ .../customizer/package-json/base.json | 34 ---------- .../customizer/package-json/cli-ajs.json | 23 ------- .../customizer/package-json/cli.json | 19 ------ .../customizer/package-json/elements.json | 22 ------- .../package-json/getting-started.json | 19 ------ .../customizer/package-json/i18n.json | 21 ------- .../customizer/package-json/package.json | 19 ------ .../package-json/packageJsonCustomizer.js | 63 ------------------- .../customizer/package-json/schematics.json | 24 ------- .../customizer/package-json/systemjs.json | 60 ------------------ .../customizer/package-json/testing.json | 20 ------ .../customizer/package-json/universal.json | 29 --------- aio/tools/example-zipper/exampleZipper.js | 7 +-- .../shared/boilerplate/cli-ajs/package.json | 1 + .../shared/boilerplate/cli/package.json | 1 + .../shared/boilerplate/elements/package.json | 1 + .../shared/boilerplate/i18n/package.json | 1 + .../boilerplate/service-worker/package.json | 1 + .../shared/boilerplate/systemjs/package.json | 36 +++++------ .../shared/boilerplate/universal/package.json | 1 + 22 files changed, 32 insertions(+), 409 deletions(-) delete mode 100644 aio/tools/example-zipper/customizer/package-json/base.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/cli-ajs.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/cli.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/elements.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/getting-started.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/i18n.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/package.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/packageJsonCustomizer.js delete mode 100644 aio/tools/example-zipper/customizer/package-json/schematics.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/systemjs.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/testing.json delete mode 100644 aio/tools/example-zipper/customizer/package-json/universal.json diff --git a/aio/tools/RELEASE.md b/aio/tools/RELEASE.md index a45f16bec3..419d2597db 100644 --- a/aio/tools/RELEASE.md +++ b/aio/tools/RELEASE.md @@ -14,11 +14,3 @@ There, select all the packages that are updated on the new Angular release. **2)** Changes to the tsconfig.json? There are several files in `/aio/tools/examples/shared/boilerplate/*/tsconfig.json` (based on the example type). ---- -> NOTE(gkalpak): -> There are some `package.json` files in `/aio/tools/examples/shared/boilerplate/*`. -> AFAICT, they are copied over to the examples (based on the example type), but they are neither -> used for installing dependencies (which come from `/aio/tools/examples/shared/package.json`) nor -> used in zips (since they are overwritten by `/aio/tools/example-zipper/customizer`). -> For all stackblitz live-examples, `/aio/tools/examples/shared/boilerplate/cli/package.json` seems -> to be used. diff --git a/aio/tools/example-zipper/README.md b/aio/tools/example-zipper/README.md index 3979c8698f..344f68fe2b 100644 --- a/aio/tools/example-zipper/README.md +++ b/aio/tools/example-zipper/README.md @@ -23,33 +23,18 @@ to flag an example as something to stackblitz or zip. For example: The zipper will use this information for creating new zips. -## Three kinds of examples +## Two kinds of examples -The majority of examples in AIO use `CLI`, with some additionally using `Webpack` and upgrade usiing `SystemJS`. This -tool is able to differentiate between them. +There are mainly two kinds of AIO docs examples: The ones based on the Angular CLI and the ones based on SystemJS. +The majority of the examples are CLI-based with only some of the `ngUpgrade` examples using SystemJS. -The boilerplate uses a `package.json` that contains packages and scripts to run any kind of example. -Using that `package.json` in the zips would confuse the users. +Some of the CLI-based examples require small tweaks to the default layout/configuration (for example, to add support for Angular elements, i18n, universal, etc.). +These example types have separate boilerplate directories with the files that are different from the default `cli` boilerplate. -Thanks to the `package.json` customizer, we can create a new `package.json` on the fly that would -only contain the packages and scripts needed to run that example. +There are appropriate `package.json` files for each type of example in the boilerplate directories. +If there is no special `package.json` file for an example type, the one from the `cli` boilerplate directory will be used instead. -The `exampleZipper.js` won't include any `System.js` related files for `CLI` or `Webpack` projects. - -### The package.json customizer - -Given a `type`, this tool will populate a `package.json` file customized for that type. - -Here you find a: - -* **base.json** - All the common scripts and packages -* **cli.json** - Extra scripts and packages for the CLI -* **universal.json** - Extra scripts and packages for universal -* **i18n.json** - Extra scripts and packages for i18n -* **systemjs.json** - All the System.js related packages but it also contains the remainder scripts - that are not in the other files. - -The tool will also give some standard names to the scripts. +The `exampleZipper.js` won't include any `System.js` related files for CLI-based projects. ## The zipper.json diff --git a/aio/tools/example-zipper/customizer/package-json/base.json b/aio/tools/example-zipper/customizer/package-json/base.json deleted file mode 100644 index ba3f3d49c5..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/base.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "scripts": [ - { "name": "lint" } - ], - "dependencies": [ - "@angular/animations", - "@angular/common", - "@angular/compiler", - "@angular/core", - "@angular/forms", - "@angular/platform-browser", - "@angular/platform-browser-dynamic", - "@angular/router", - "@angular/upgrade", - "angular-in-memory-web-api", - "rxjs", - "zone.js" - ], - "devDependencies": [ - "@angular/compiler-cli", - "@types/jasmine", - "@types/node", - "jasmine-core", - "karma", - "karma-chrome-launcher", - "karma-cli", - "karma-jasmine", - "karma-jasmine-html-reporter", - "lodash", - "protractor", - "tslint", - "typescript" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/cli-ajs.json b/aio/tools/example-zipper/customizer/package-json/cli-ajs.json deleted file mode 100644 index ef9c75606a..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/cli-ajs.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "scripts": [ - { "name": "ng", "command": "ng" }, - { "name": "build", "command": "ng build" }, - { "name": "start", "command": "ng serve" }, - { "name": "test", "command": "ng test" }, - { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e" } - ], - "dependencies": [ - "angular", - "angular-route" - ], - "devDependencies": [ - "@angular/cli", - "@types/angular", - "@types/angular-route", - "@types/jasminewd2", - "jasmine-spec-reporter", - "karma-coverage-istanbul-reporter", - "ts-node" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/cli.json b/aio/tools/example-zipper/customizer/package-json/cli.json deleted file mode 100644 index ebbbbc1341..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/cli.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "scripts": [ - { "name": "ng", "command": "ng" }, - { "name": "build", "command": "ng build" }, - { "name": "start", "command": "ng serve" }, - { "name": "test", "command": "ng test" }, - { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e" } - ], - "dependencies": [], - "devDependencies": [ - "@angular-devkit/build-angular", - "@angular/cli", - "@types/jasminewd2", - "jasmine-spec-reporter", - "karma-coverage-istanbul-reporter", - "ts-node" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/elements.json b/aio/tools/example-zipper/customizer/package-json/elements.json deleted file mode 100644 index 1732dc9ae8..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/elements.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "scripts": [ - { "name": "ng", "command": "ng" }, - { "name": "build", "command": "ng build" }, - { "name": "start", "command": "ng serve" }, - { "name": "test", "command": "ng test" }, - { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e" } - ], - "dependencies": [ - "@angular/elements", - "@webcomponents/custom-elements" - ], - "devDependencies": [ - "@angular-devkit/build-angular", - "@angular/cli", - "@types/jasminewd2", - "jasmine-spec-reporter", - "karma-coverage-istanbul-reporter", - "ts-node" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/getting-started.json b/aio/tools/example-zipper/customizer/package-json/getting-started.json deleted file mode 100644 index ebbbbc1341..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/getting-started.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "scripts": [ - { "name": "ng", "command": "ng" }, - { "name": "build", "command": "ng build" }, - { "name": "start", "command": "ng serve" }, - { "name": "test", "command": "ng test" }, - { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e" } - ], - "dependencies": [], - "devDependencies": [ - "@angular-devkit/build-angular", - "@angular/cli", - "@types/jasminewd2", - "jasmine-spec-reporter", - "karma-coverage-istanbul-reporter", - "ts-node" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/i18n.json b/aio/tools/example-zipper/customizer/package-json/i18n.json deleted file mode 100644 index f3434be8d1..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/i18n.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "scripts": [ - { "name": "start", "command": "ng serve" }, - { "name": "start:fr", "command": "ng serve --configuration=fr" }, - { "name": "build", "command": "ng build" }, - { "name": "build:fr", "command": "ng build --configuration=production-fr" }, - { "name": "test", "command": "ng test" }, - { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e" }, - { "name": "extract", "command": "ng xi18n --output-path=locale" } - ], - "dependencies": [], - "devDependencies": [ - "@angular-devkit/build-angular", - "@angular/cli", - "@types/jasminewd2", - "jasmine-spec-reporter", - "karma-coverage-istanbul-reporter", - "ts-node" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/package.json b/aio/tools/example-zipper/customizer/package-json/package.json deleted file mode 100644 index a4afa9a41e..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "angular-io-example", - "version": "1.0.0", - "private": true, - "description": "Example project from an angular.io guide.", - "scripts": { - - }, - "keywords": [], - "author": "", - "license": "MIT", - "dependencies": { - - }, - "devDependencies": { - - }, - "repository": {} -} diff --git a/aio/tools/example-zipper/customizer/package-json/packageJsonCustomizer.js b/aio/tools/example-zipper/customizer/package-json/packageJsonCustomizer.js deleted file mode 100644 index 2255244dcc..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/packageJsonCustomizer.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; - -const path = require('canonical-path'); -const fs = require('fs'); - -const examplesPath = path.resolve(__dirname, '../../../examples'); -const packageFolder = path.resolve(__dirname); - -class PackageJsonCustomizer { - constructor() { - this.dependenciesPackageJson = this.readJson(path.join(examplesPath, '/shared/package.json')); - this.scriptsPackageJson = this.readJson(path.join(examplesPath, '/shared/boilerplate/systemjs/package.json')); - this.basePackageJson = this.readJson(`${packageFolder}/base.json`); - this.templatePackageJson = this.readJson(`${packageFolder}/package.json`, false); - } - - generate(type = 'systemjs') { - let packageJson = JSON.parse(this.templatePackageJson); - let rules = require(`${packageFolder}/${type}.json`); - - this._mergeJSON(rules, this.basePackageJson); - - rules.scripts.forEach((r) => { - const scriptName = r.name; - const script = this.scriptsPackageJson.scripts[scriptName]; - const finalName = r.rename ? r.rename : r.name; - const finalScript = r.command ? r.command : script; - packageJson.scripts[finalName] = finalScript; - }); - - rules.dependencies.forEach((name) => { - const version = this.dependenciesPackageJson.dependencies[name]; - packageJson.dependencies[name] = version; - }); - - rules.devDependencies.forEach((name) => { - const version = this.dependenciesPackageJson.devDependencies[name]; - packageJson.devDependencies[name] = version; - }); - - return JSON.stringify(packageJson, null, 2); - } - - _mergeJSON(json1, json2) { - var result = json1; - for (var prop in json2) - { - if (json2.hasOwnProperty(prop)) - { - result[prop] = (result[prop].concat(json2[prop])).sort(); - } - } - return result; - } - - readJson(jsonFile, parse = true) { - const contents = fs.readFileSync(jsonFile, 'utf8'); - - return parse ? JSON.parse(contents) : contents; - } -} - -module.exports = PackageJsonCustomizer; diff --git a/aio/tools/example-zipper/customizer/package-json/schematics.json b/aio/tools/example-zipper/customizer/package-json/schematics.json deleted file mode 100644 index 947a1917c2..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/schematics.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "scripts": [ - { "name": "ng", "command": "ng" }, - { "name": "build", "command": "ng build" }, - { "name": "build:lib", "command": "ng build my-lib" }, - { "name": "start", "command": "ng serve" }, - { "name": "test", "command": "ng test" }, - { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e" } - ], - "dependencies": [], - "devDependencies": [ - "@angular-devkit/build-angular", - "@angular-devkit/build-ng-packagr", - "@angular/cli", - "@types/jasminewd2", - "jasmine-spec-reporter", - "karma-coverage-istanbul-reporter", - "ng-packagr", - "tsickle", - "tslib", - "ts-node" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/systemjs.json b/aio/tools/example-zipper/customizer/package-json/systemjs.json deleted file mode 100644 index 3deb16df02..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/systemjs.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "scripts": [ - { "name": "build" }, - { "name": "build:watch" }, - { "name": "serve" }, - { "name": "prestart" }, - { "name": "start" }, - { "name": "pretest" }, - { "name": "test" }, - { "name": "pretest:once" }, - { "name": "test:once" }, - { "name": "build:upgrade" }, - { "name": "serve:upgrade" }, - { "name": "build:aot" }, - { "name": "serve:aot" }, - { "name": "copy-dist-files" } - ], - "dependencies": [ - "@angular/animations", - "@angular/common", - "@angular/compiler", - "@angular/core", - "@angular/forms", - "@angular/platform-browser", - "@angular/platform-browser-dynamic", - "@angular/router", - "@angular/upgrade", - "core-js", - "rxjs", - "systemjs", - "tslib", - "zone.js" - ], - "devDependencies": [ - "@angular/compiler-cli", - "@types/angular", - "@types/angular-animate", - "@types/angular-mocks", - "@types/angular-resource", - "@types/angular-route", - "@types/jasmine", - "@types/jasminewd2", - "@types/node", - "concurrently", - "http-server", - "jasmine-core", - "karma", - "karma-chrome-launcher", - "karma-jasmine", - "karma-jasmine-html-reporter", - "lite-server", - "protractor", - "rollup", - "rollup-plugin-commonjs", - "rollup-plugin-node-resolve", - "rollup-plugin-uglify", - "tslint", - "typescript" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/testing.json b/aio/tools/example-zipper/customizer/package-json/testing.json deleted file mode 100644 index 37c304b4cc..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/testing.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "scripts": [ - { "name": "ng", "command": "ng" }, - { "name": "build", "command": "ng build" }, - { "name": "start", "command": "ng serve" }, - { "name": "test", "command": "ng test" }, - { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e" } - ], - "dependencies": [], - "devDependencies": [ - "@angular-devkit/build-angular", - "@angular/cli", - "@types/jasminewd2", - "jasmine-marbles", - "jasmine-spec-reporter", - "karma-coverage-istanbul-reporter", - "ts-node" - ] -} diff --git a/aio/tools/example-zipper/customizer/package-json/universal.json b/aio/tools/example-zipper/customizer/package-json/universal.json deleted file mode 100644 index 7a69d07ec4..0000000000 --- a/aio/tools/example-zipper/customizer/package-json/universal.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "scripts": [ - { "name": "ng", "command": "ng" }, - { "name": "build", "command": "ng build" }, - { "name": "start", "command": "ng serve" }, - { "name": "test", "command": "ng test" }, - { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e" }, - { "name": "dev:ssr", "command": "ng run angular.io-example:serve-ssr" }, - { "name": "build:ssr", "command": "ng build --prod && ng run angular.io-example:server:production" }, - { "name": "serve:ssr", "command": "node dist/server/main.js" }, - { "name": "prerender", "command": "ng run angular.io-example:prerender" } - ], - "dependencies": [ - "@angular/platform-server", - "@nguniversal/express-engine", - "express" - ], - "devDependencies": [ - "@angular-devkit/build-angular", - "@angular/cli", - "@nguniversal/builders", - "@types/express", - "@types/jasminewd2", - "jasmine-spec-reporter", - "karma-coverage-istanbul-reporter", - "ts-node" - ] -} diff --git a/aio/tools/example-zipper/exampleZipper.js b/aio/tools/example-zipper/exampleZipper.js index 7245e2fa63..a3830bf0fc 100644 --- a/aio/tools/example-zipper/exampleZipper.js +++ b/aio/tools/example-zipper/exampleZipper.js @@ -6,7 +6,6 @@ const archiver = require('archiver'); const fs = require('fs-extra'); const globby = require('globby'); -const PackageJsonCustomizer = require('./customizer/package-json/packageJsonCustomizer'); const regionExtractor = require('../transforms/examples-package/services/region-parser'); const EXAMPLE_CONFIG_NAME = 'example-config.json'; @@ -17,7 +16,6 @@ class ExampleZipper { this.examplesSystemjsConfig = path.join(__dirname, '../examples/shared/boilerplate/systemjs/src/systemjs.config.js'); this.examplesSystemjsLoaderConfig = path.join(__dirname, '../examples/shared/boilerplate/systemjs/src/systemjs-angular-loader.js'); this.exampleTsconfig = path.join(__dirname, '../examples/shared/boilerplate/systemjs/src/tsconfig.json'); - this.customizer = new PackageJsonCustomizer(); let gpathStackblitz = path.join(sourceDirName, '**/*stackblitz.json'); let gpathZipper = path.join(sourceDirName, '**/zipper.json'); @@ -91,6 +89,7 @@ class ExampleZipper { 'bs-config.json', 'karma.conf.js', 'karma-test-shim.js', + 'package.json', 'tsconfig.*', 'tslint.*', 'e2e/protractor.conf.js', @@ -100,8 +99,6 @@ class ExampleZipper { 'src/test.ts', 'src/environments/**/*', 'src/testing/**/*', - // Only ignore root package.json - '!package.json' ]; var alwaysExcludes = [ '!**/bs-config.e2e.json', @@ -167,8 +164,6 @@ class ExampleZipper { zip.append(output, { name: relativePath } ); }); - // we need the package.json from _examples root, not the _boilerplate one - zip.append(this.customizer.generate(exampleType), { name: 'package.json' }); // also a systemjs config if (exampleType === 'systemjs') { zip.append(fs.readFileSync(this.examplesSystemjsConfig, 'utf8'), { name: 'src/systemjs.config.js' }); diff --git a/aio/tools/examples/shared/boilerplate/cli-ajs/package.json b/aio/tools/examples/shared/boilerplate/cli-ajs/package.json index 4a07637f76..d025bc756a 100644 --- a/aio/tools/examples/shared/boilerplate/cli-ajs/package.json +++ b/aio/tools/examples/shared/boilerplate/cli-ajs/package.json @@ -1,6 +1,7 @@ { "name": "angular.io-example", "version": "0.0.0", + "description": "Example project from an angular.io guide.", "license": "MIT", "scripts": { "ng": "ng", diff --git a/aio/tools/examples/shared/boilerplate/cli/package.json b/aio/tools/examples/shared/boilerplate/cli/package.json index 693114462e..fbdd7de6f8 100644 --- a/aio/tools/examples/shared/boilerplate/cli/package.json +++ b/aio/tools/examples/shared/boilerplate/cli/package.json @@ -1,6 +1,7 @@ { "name": "angular.io-example", "version": "0.0.0", + "description": "Example project from an angular.io guide.", "license": "MIT", "scripts": { "ng": "ng", diff --git a/aio/tools/examples/shared/boilerplate/elements/package.json b/aio/tools/examples/shared/boilerplate/elements/package.json index 6d412b59a2..a0b396cf8a 100644 --- a/aio/tools/examples/shared/boilerplate/elements/package.json +++ b/aio/tools/examples/shared/boilerplate/elements/package.json @@ -1,6 +1,7 @@ { "name": "angular.io-example", "version": "0.0.0", + "description": "Example project from an angular.io guide.", "license": "MIT", "scripts": { "ng": "ng", diff --git a/aio/tools/examples/shared/boilerplate/i18n/package.json b/aio/tools/examples/shared/boilerplate/i18n/package.json index 6d10e0759b..d0944e64c4 100644 --- a/aio/tools/examples/shared/boilerplate/i18n/package.json +++ b/aio/tools/examples/shared/boilerplate/i18n/package.json @@ -1,6 +1,7 @@ { "name": "angular.io-example", "version": "0.0.0", + "description": "Example project from an angular.io guide.", "license": "MIT", "scripts": { "ng": "ng", diff --git a/aio/tools/examples/shared/boilerplate/service-worker/package.json b/aio/tools/examples/shared/boilerplate/service-worker/package.json index e88091c1ea..9c30e90cea 100644 --- a/aio/tools/examples/shared/boilerplate/service-worker/package.json +++ b/aio/tools/examples/shared/boilerplate/service-worker/package.json @@ -1,6 +1,7 @@ { "name": "angular.io-example", "version": "0.0.0", + "description": "Example project from an angular.io guide.", "license": "MIT", "scripts": { "ng": "ng", diff --git a/aio/tools/examples/shared/boilerplate/systemjs/package.json b/aio/tools/examples/shared/boilerplate/systemjs/package.json index b73fefce44..1a609f6626 100644 --- a/aio/tools/examples/shared/boilerplate/systemjs/package.json +++ b/aio/tools/examples/shared/boilerplate/systemjs/package.json @@ -1,8 +1,8 @@ { - "name": "angular-examples", - "version": "1.0.0", - "private": true, - "description": "Example package.json, only contains needed scripts for examples. See _examples/package.json for master package.json.", + "name": "angular.io-example", + "version": "0.0.0", + "description": "Example project from an angular.io guide.", + "license": "MIT", "scripts": { "build": "tsc -p src/", "build:watch": "tsc -p src/ -w", @@ -25,26 +25,25 @@ "serve:aot": "lite-server -c bs-config.aot.json", "copy-dist-files": "node ./copy-dist-files.js" }, - "keywords": [], - "author": "", - "license": "MIT", + "private": true, "dependencies": { - "@angular/animations": "~9.0.3", - "@angular/common": "~9.0.3", - "@angular/compiler": "~9.0.3", - "@angular/core": "~9.0.3", - "@angular/forms": "~9.0.3", - "@angular/platform-browser": "~9.0.3", - "@angular/platform-browser-dynamic": "~9.0.3", - "@angular/router": "~9.0.3", - "@angular/upgrade": "~9.0.3", + "@angular/animations": "~9.1.4", + "@angular/common": "~9.1.4", + "@angular/compiler": "~9.1.4", + "@angular/core": "~9.1.4", + "@angular/forms": "~9.1.4", + "@angular/platform-browser": "~9.1.4", + "@angular/platform-browser-dynamic": "~9.1.4", + "@angular/router": "~9.1.4", + "@angular/upgrade": "~9.1.4", "core-js": "^2.5.4", "rxjs": "~6.5.4", "tslib": "^1.10.0", "zone.js": "~0.10.3" }, "devDependencies": { - "@angular/compiler-cli": "~9.0.3", + "@angular/compiler-cli": "~9.1.4", + "@angular/language-service": "~9.1.4", "@types/angular": "1.6.47", "@types/angular-animate": "1.5.10", "@types/angular-mocks": "1.6.0", @@ -68,6 +67,5 @@ "rollup-plugin-terser": "^5.3.0", "tslint": "~5.18.0", "typescript": "~3.7.5" - }, - "repository": {} + } } diff --git a/aio/tools/examples/shared/boilerplate/universal/package.json b/aio/tools/examples/shared/boilerplate/universal/package.json index d7ce74c331..9b503635f9 100644 --- a/aio/tools/examples/shared/boilerplate/universal/package.json +++ b/aio/tools/examples/shared/boilerplate/universal/package.json @@ -1,6 +1,7 @@ { "name": "angular.io-example", "version": "0.0.0", + "description": "Example project from an angular.io guide.", "license": "MIT", "scripts": { "ng": "ng", From 307db74e93d7e9cae8b7a6e243f6be88d7a54e48 Mon Sep 17 00:00:00 2001 From: k-ta-yamada Date: Fri, 24 Jul 2020 23:15:19 +0900 Subject: [PATCH 023/133] docs: fixed that class attribute is not closed (#38219) PR Close #38219 --- aio/content/guide/lifecycle-hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/lifecycle-hooks.md b/aio/content/guide/lifecycle-hooks.md index 46d06fbb98..27396d9e16 100644 --- a/aio/content/guide/lifecycle-hooks.md +++ b/aio/content/guide/lifecycle-hooks.md @@ -569,7 +569,7 @@ which can only be reached by querying for them via the property decorated with {@a no-unidirectional-flow-worries} -
No need to wait for content updates
From cce5583e754dff10cb051015a25145cc56d2a54e Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Thu, 23 Jul 2020 16:30:31 -0700 Subject: [PATCH 024/133] docs(core): Fix incorrectly rendered code example in structural directives guide (#38207) The code example was missing a close brace and also incorrectly rendered the template div as an actual div in the page DOM. PR Close #38207 --- aio/content/guide/structural-directives.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aio/content/guide/structural-directives.md b/aio/content/guide/structural-directives.md index 8b2bd6bc2d..b49c5199d8 100644 --- a/aio/content/guide/structural-directives.md +++ b/aio/content/guide/structural-directives.md @@ -879,12 +879,14 @@ export type LoadingState = Loaded | Loading; export class IfLoadedDirective { @Input('ifLoaded') set state(state: LoadingState) {} static ngTemplateGuard_state(dir: IfLoadedDirective, expr: LoadingState): expr is Loaded { return true; }; +} + export interface Person { name: string; } @Component({ - template: `
{{ state.data }}
`, + template: `<div *ifLoaded="state">{{ state.data }}</div>`, }) export class AppComponent { state: LoadingState; From 6e2db228afce0308187d95383babe13a78ccbe56 Mon Sep 17 00:00:00 2001 From: Andrii Date: Fri, 24 Jul 2020 06:44:28 +0300 Subject: [PATCH 025/133] docs: Fix link by removing a space (#38214) PR Close #38214 --- aio/content/guide/interpolation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/interpolation.md b/aio/content/guide/interpolation.md index 411829328a..524cc42f90 100644 --- a/aio/content/guide/interpolation.md +++ b/aio/content/guide/interpolation.md @@ -170,6 +170,6 @@ If an idempotent expression returns a string or a number, it returns the same st
-There is one exception to this behavior that applies to `*ngFor`. `*ngFor` has `trackBy` functionality that can deal with referential inequality of objects when iterating over them. See [*ngFor with `trackBy`](guide/built-in-directives #ngfor-with-trackby) for details. +There is one exception to this behavior that applies to `*ngFor`. `*ngFor` has `trackBy` functionality that can deal with referential inequality of objects when iterating over them. See [*ngFor with `trackBy`](guide/built-in-directives#ngfor-with-trackby) for details.
From 253337dc0a30193531d09d45a79e7680e0b4a2a9 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Fri, 19 Jul 2019 23:14:28 +0900 Subject: [PATCH 026/133] feat(zone.js): move MutationObserver/FileReader to different module (#31657) Separate `EventTarget`, `FileReader`, `MutationObserver` and `IntersectionObserver` patches into different module. So the user can disable those modules separately. PR Close #31657 --- packages/zone.js/MODULE.md | 4 ++++ packages/zone.js/lib/browser/browser.ts | 11 ++++++++++- packages/zone.js/lib/zone.configurations.api.ts | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/zone.js/MODULE.md b/packages/zone.js/MODULE.md index 4bd378307e..e58fab2b7e 100644 --- a/packages/zone.js/MODULE.md +++ b/packages/zone.js/MODULE.md @@ -36,6 +36,10 @@ Below is the full list of currently supported modules. |requestAnimationFrame|requestAnimationFrame will be patched as Zone MacroTask|__Zone_disable_requestAnimationFrame = true| |blocking|alert/prompt/confirm will be patched as Zone.run|__Zone_disable_blocking = true| |EventTarget|target.addEventListener will be patched as Zone aware EventTask|__Zone_disable_EventTarget = true| +|MutationObserver|MutationObserver will be patched as Zone aware operation|__Zone_disable_MutationObserver = true| +|IntersectionObserver|Intersection will be patched as Zone aware operation|__Zone_disable_Intersection = true| +|FileReader|FileReader will be patched as Zone aware operation|__Zone_disable_FileReader = true| +|canvas|HTMLCanvasElement.toBlob will be patched as Zone aware operation|__Zone_disable_canvas = true| |IE BrowserTools check|in IE, browser tool will not use zone patched eventListener|__Zone_disable_IE_check = true| |CrossContext check|in webdriver, enable check event listener is cross context|__Zone_enable_cross_context_check = true| |XHR|XMLHttpRequest will be patched as Zone aware MacroTask|__Zone_disable_XHR = true| diff --git a/packages/zone.js/lib/browser/browser.ts b/packages/zone.js/lib/browser/browser.ts index 0e56beaaec..3023e1941d 100644 --- a/packages/zone.js/lib/browser/browser.ts +++ b/packages/zone.js/lib/browser/browser.ts @@ -12,7 +12,7 @@ import {findEventTasks} from '../common/events'; import {patchTimer} from '../common/timers'; -import {patchClass, patchMethod, patchPrototype, scheduleMacroTaskWithCurrentZone, ZONE_SYMBOL_ADD_EVENT_LISTENER, ZONE_SYMBOL_REMOVE_EVENT_LISTENER, zoneSymbol} from '../common/utils'; +import {patchClass, patchMethod, patchPrototype, scheduleMacroTaskWithCurrentZone, ZONE_SYMBOL_ADD_EVENT_LISTENER, ZONE_SYMBOL_REMOVE_EVENT_LISTENER, zoneSymbol,} from '../common/utils'; import {patchCustomElements} from './custom-elements'; import {eventTargetPatch, patchEvent} from './event-target'; @@ -59,9 +59,18 @@ Zone.__load_patch('EventTarget', (global: any, Zone: ZoneType, api: _ZonePrivate if (XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype) { api.patchEventTarget(global, [XMLHttpRequestEventTarget.prototype]); } +}); + +Zone.__load_patch('MutationObserver', (global: any, Zone: ZoneType, api: _ZonePrivate) => { patchClass('MutationObserver'); patchClass('WebKitMutationObserver'); +}); + +Zone.__load_patch('IntersectionObserver', (global: any, Zone: ZoneType, api: _ZonePrivate) => { patchClass('IntersectionObserver'); +}); + +Zone.__load_patch('FileReader', (global: any, Zone: ZoneType, api: _ZonePrivate) => { patchClass('FileReader'); }); diff --git a/packages/zone.js/lib/zone.configurations.api.ts b/packages/zone.js/lib/zone.configurations.api.ts index 4c6cfe98c3..c53c81dd2b 100644 --- a/packages/zone.js/lib/zone.configurations.api.ts +++ b/packages/zone.js/lib/zone.configurations.api.ts @@ -325,6 +325,21 @@ interface ZoneGlobalConfigurations { */ __Zone_disable_EventTarget?: boolean; + /** + * Disable the monkey patch of the browser `FileReader` APIs. + */ + __Zone_disable_FileReader?: boolean; + + /** + * Disable the monkey patch of the browser `MutationObserver` APIs. + */ + __Zone_disable_MutationObserver?: boolean; + + /** + * Disable the monkey patch of the browser `IntersectionObserver` APIs. + */ + __Zone_disable_IntersectionObserver?: boolean; + /** * Disable the monkey patch of the browser onProperty APIs(such as onclick). * From a71f114ba4fae70d53a10a789f91ac93012597dd Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Wed, 1 Jul 2020 06:11:14 +0900 Subject: [PATCH 027/133] fix(zone.js): clearTimeout/clearInterval should call on object global (#37858) Close #37333 `clearTimeout` is patched by `zone.js`, and it finally calls the native delegate of `clearTimeout`, the current implemention only call `clearNative(id)`, but it should call on object `global` like `clearNative.call(global, id)`. Otherwise in some env, it will throw error `clearTimeout called on an object that does not implement interface Window` PR Close #37858 --- packages/zone.js/lib/common/timers.ts | 2 +- .../zone.js/test/common/setTimeout.spec.ts | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/zone.js/lib/common/timers.ts b/packages/zone.js/lib/common/timers.ts index 50889dbc0e..e1d68b5a6d 100644 --- a/packages/zone.js/lib/common/timers.ts +++ b/packages/zone.js/lib/common/timers.ts @@ -55,7 +55,7 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam } function clearTask(task: Task) { - return clearNative!((task.data).handleId); + return clearNative!.call(window, (task.data).handleId); } setNative = diff --git a/packages/zone.js/test/common/setTimeout.spec.ts b/packages/zone.js/test/common/setTimeout.spec.ts index ed62e586ae..7d4f31eb6a 100644 --- a/packages/zone.js/test/common/setTimeout.spec.ts +++ b/packages/zone.js/test/common/setTimeout.spec.ts @@ -6,7 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ +import {patchTimer} from '../../lib/common/timers'; import {isNode, zoneSymbol} from '../../lib/common/utils'; + declare const global: any; const wtfMock = global.wtfMock; @@ -56,6 +58,25 @@ describe('setTimeout', function() { }); }); + it('should call native clearTimeout with the correct context', function() { + // since clearTimeout has been patched already, we can not test `clearTimeout` directly + // we will fake another API patch to test + let context: any = null; + const fakeGlobal = { + setTimeout: function() { + return 1; + }, + clearTimeout: function(id: number) { + context = this; + } + }; + patchTimer(fakeGlobal, 'set', 'clear', 'Timeout') + const cancelId = fakeGlobal.setTimeout(); + const m = fakeGlobal.clearTimeout; + m.call({}, cancelId); + expect(context).toBe(fakeGlobal); + }); + it('should allow cancelation of fns registered with setTimeout after invocation', function(done) { const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'}); testZone.run(() => { From 1822cbcd46653ef1507a5a5be831431f2560b289 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Wed, 1 Jul 2020 07:15:22 +0900 Subject: [PATCH 028/133] fix(zone.js): patch nodejs EventEmtter.prototype.off (#37863) Close #35473 zone.js nodejs patch should also patch `EventEmitter.prototype.off` as `removeListener`. So `off` can correctly remove the listeners added by `EventEmitter.prototype.addListener` PR Close #37863 --- packages/zone.js/lib/node/events.ts | 2 ++ packages/zone.js/test/node/events.spec.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/zone.js/lib/node/events.ts b/packages/zone.js/lib/node/events.ts index 2366332c5c..f09f8a4216 100644 --- a/packages/zone.js/lib/node/events.ts +++ b/packages/zone.js/lib/node/events.ts @@ -16,6 +16,7 @@ Zone.__load_patch('EventEmitter', (global: any) => { const EE_REMOVE_ALL_LISTENER = 'removeAllListeners'; const EE_LISTENERS = 'listeners'; const EE_ON = 'on'; + const EE_OFF = 'off'; const compareTaskCallbackVsDelegate = function(task: any, delegate: any) { // same callback, same capture, same event name, just return @@ -47,6 +48,7 @@ Zone.__load_patch('EventEmitter', (global: any) => { }); if (result && result[0]) { obj[EE_ON] = obj[EE_ADD_LISTENER]; + obj[EE_OFF] = obj[EE_REMOVE_LISTENER]; } } diff --git a/packages/zone.js/test/node/events.spec.ts b/packages/zone.js/test/node/events.spec.ts index f50b0eb02f..9cab424169 100644 --- a/packages/zone.js/test/node/events.spec.ts +++ b/packages/zone.js/test/node/events.spec.ts @@ -66,6 +66,18 @@ describe('nodejs EventEmitter', () => { emitter.emit('test2', 'test value'); }); }); + it('should remove listeners by calling off properly', () => { + zoneA.run(() => { + emitter.on('test', shouldNotRun); + emitter.on('test2', shouldNotRun); + emitter.off('test', shouldNotRun); + }); + zoneB.run(() => { + emitter.off('test2', shouldNotRun); + emitter.emit('test', 'test value'); + emitter.emit('test2', 'test value'); + }); + }); it('remove listener should return event emitter', () => { zoneA.run(() => { emitter.on('test', shouldNotRun); From 6a3e68b606a5a3d5864dac01d12f4b3ed5ff4ac2 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sat, 4 Jul 2020 08:54:19 +0900 Subject: [PATCH 029/133] docs(zone.js): update zone.js bundle doc since the APF change (#37919) Since the PR #36540 change the zone.js bundles to Angular Package Format, the bundle name/location are changed, so this PR updated the `README.md` doc for the zone bundles. Also add the recent added new bundles `zone-patch-message-port` doc. PR Close #37919 --- packages/zone.js/README.md | 59 ++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/packages/zone.js/README.md b/packages/zone.js/README.md index 8094355611..d356f4eabc 100644 --- a/packages/zone.js/README.md +++ b/packages/zone.js/README.md @@ -54,39 +54,50 @@ For more details, please see [MODULE.md](MODULE.md). ## Bundles -There are several bundles under `dist` folder. + +Starting with `v0.11.0`, `zone.js` uses `Angular Package Format` for bundle distribution. +(For backwards compatibility, all bundles can still be accessed from `dist` folder.) |Bundle|Summary| |---|---| -|zone.js|the default bundle, contains the most used APIs such as `setTimeout/Promise/EventTarget...`, also this bundle supports all evergreen and legacy (IE/Legacy Firefox/Legacy Safari) Browsers| -|zone-evergreen.js|the bundle for evergreen browsers, doesn't include the `patch` for `legacy` browsers such as `IE` or old versions of `Firefox/Safari`| -|zone-legacy.js|the patch bundle for legacy browsers, only includes the `patch` for `legacy` browsers such as `IE` or old versions of `Firefox/Safari`. This bundle must be loaded after `zone-evergreen.js`, **`zone.js`=`zone-evergreen.js` + `zone-legacy.js`**| -|zone-testing.js|the bundle for zone testing support, including `jasmine/mocha` support and `async/fakeAsync/sync` test utilities| -|zone-externs.js|the API definitions for `closure compiler`| +|`zone.js`| The default bundle. Contains the most used APIs such as `setTimeout/Promise/EventTarget...`, it also supports differential loading by importing this bundle using `import zone.js`. In legacy browsers it includes some additional patches such as `registerElement` and `EventTarget` like APIs.| +|`zone-testing.js`| The bundle for zone testing support of `jasmine` / `mocha` / `jest`. Also includes test utility functions `async` / `fakeAsync` / `sync`.| +|`zone-node.js`|The NodeJS support bundle.| +|`zone-mix.js`|A mixed bundle which supports both browser and NodeJS. Useful for mixed environment such as Electron.| +|`zone-externs.js`|the API definitions for `closure compiler`.| -And here are the additional optional patches not included in the main zone.js bundles +Additional optional patches not included in the `zone.js` bundles which extend functionality. +The additional bundles can be found under `zone.js/plugins` folder. +To use these bundles, add the following code after importing zone.js bundle. + +``` +import 'zone.js'; +// For example, import canvas patch +import 'zone.js/plugins/zone-patch-canvas'; +``` |Patch|Summary| |---|---| -|webapis-media-query.js|patch for `MediaQuery APIs`| -|webapis-notification.js|patch for `Notification APIs`| -|webapis-rtc-peer-connection.js|patch for `RTCPeerConnection APIs`| -|webapis-shadydom.js|patch for `Shady DOM APIs`| -|zone-bluebird.js|patch for `Bluebird APIs`| -|zone-error.js|patch for `Error Global Object`, supports remove `Zone StackTrace`| -|zone-patch-canvas.js|patch for `Canvas API`| -|zone-patch-cordova.js|patch for `Cordova API`| -|zone-patch-electron.js|patch for `Electron API`| -|zone-patch-fetch.js|patch for `Fetch API`| -|zone-patch-jsonp.js|utility for `jsonp API`| -|zone-patch-resize-observer.js|patch for `ResizeObserver API`| -|zone-patch-rxjs.js|patch for `rxjs API`| -|zone-patch-rxjs-fake-async.js|patch for `rxjs fakeasync test`| -|zone-patch-socket-io.js|patch for `socket-io`| -|zone-patch-user-media.js|patch for `UserMedia API`| +|`webapis-media-query.js`|patch for `MediaQuery APIs`| +|`webapis-notification.js`|patch for `Notification APIs`| +|`webapis-rtc-peer-connection.js`|patch for `RTCPeerConnection APIs`| +|`webapis-shadydom.js`|patch for `Shady DOM APIs`| +|`zone-bluebird.js`|patch for `Bluebird APIs`| +|`zone-error.js`|patch for `Error Global Object`, supports adding zone information to stack frame, and also removing unrelated stack frames from `zone.js` internally| +|`zone-patch-canvas.js`|patch for `Canvas API`| +|`zone-patch-cordova.js`|patch for `Cordova API`| +|`zone-patch-electron.js`|patch for `Electron API`| +|`zone-patch-fetch.js`|patch for `Fetch API`| +|`zone-patch-jsonp.js`|helper utility for `jsonp API`| +|`zone-patch-resize-observer.js`|patch for `ResizeObserver API`| +|`zone-patch-rxjs.js`|patch for `rxjs API`| +|`zone-patch-rxjs-fake-async.js`|patch for `rxjs fakeasync test`| +|`zone-patch-socket-io.js`|patch for `socket-io`| +|`zone-patch-user-media.js`|patch for `UserMedia API`| +|`zone-patch-message-port.js`|patch for `MessagePort API`| ## Promise A+ test passed [![Promises/A+ 1.1 compliant](https://promisesaplus.com/assets/logo-small.png)](https://promisesaplus.com/) ## License -MIT +MIT \ No newline at end of file From c91cdea0cd682a801d996010ba783458ef950e88 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Sat, 27 Jun 2020 12:47:54 -0700 Subject: [PATCH 030/133] refactor(dev-infra): create anchors/aliases for excluded always active groups (#37798) global-approvers, global-docs-approvers, and required-minimum-review groups are always active. It's useful to have aliases for getting groups that are active/pending/rejected while excluding these few. PR Close #37798 --- .pullapprove.yml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.pullapprove.yml b/.pullapprove.yml index 78a460640a..6eea6693f8 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -67,6 +67,23 @@ version: 3 # Meta field that goes unused by PullApprove to allow for defining aliases to be # used throughout the config. meta: + # The following groups have no file based conditions and will be initially `active` on all PRs + # - `global-approvers` + # - `global-docs-approvers` + # - `required-minimum-review` + # + # By checking the number of active/pending/rejected groups when these are excluded, we can determine + # if any other groups are matched. + # + # Also note that the ordering of groups matters in this file. The only groups visible to the current + # one are those that appear above it. + no-groups-above-this-pending: &no-groups-above-this-pending + len(groups.pending.exclude("required-minimum-review").exclude("global-approvers").exclude("global-docs-approvers")) == 0 + no-groups-above-this-rejected: &no-groups-above-this-rejected + len(groups.rejected.exclude("required-minimum-review").exclude("global-approvers").exclude("global-docs-approvers")) == 0 + no-groups-above-this-active: &no-groups-above-this-active + len(groups.active.exclude("required-minimum-review").exclude("global-approvers").exclude("global-docs-approvers")) == 0 + can-be-global-approved: &can-be-global-approved "\"global-approvers\" not in groups.approved" can-be-global-docs-approved: &can-be-global-docs-approved "\"global-docs-approvers\" not in groups.approved" defaults: &defaults @@ -1258,14 +1275,7 @@ groups: # `global-approvers` can still approve PRs that match this `fallback` rule, # but that should be an exception and not an expectation. conditions: - # The following groups have no file based conditions and will be initially `active` on all PRs - # - `global-approvers` - # - `global-docs-approvers` - # - `required-minimum-review` - # - # By checking the number of active groups when these are excluded, we can determine - # if any other groups are matched. - - len(groups.active.exclude("required-minimum-review").exclude("global-approvers").exclude("global-docs-approvers")) == 0 + - *no-groups-above-this-active # When any of the `global-*` groups is approved, they cause other groups to deactivate. # In those cases, the condition above would evaluate to `true` while in reality, only a global # approval has been provided. To ensure we don't activate the fallback group in such cases, From 9821443150ecb42f515041b60fedb21e3d3a0a0e Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Sat, 27 Jun 2020 12:53:43 -0700 Subject: [PATCH 031/133] feat(dev-infra): add phased review to groups requiring final sign-off after initial review (#37798) The size-tracking, public-api, and circular-dependencies groups can get a lot of PRs to review. In addition, the members of these groups do not always have the necessary context to fully review the PR in question. This change ensures that the owners in the groups where the changes are being made have approve the changes (ie, the aren't pending or rejected) before requesting final sign-off from these three critical groups. PR Close #37798 --- .pullapprove.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.pullapprove.yml b/.pullapprove.yml index 6eea6693f8..7ecae6ed4c 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -1160,6 +1160,8 @@ groups: public-api: <<: *defaults conditions: + - *no-groups-above-this-pending + - *no-groups-above-this-rejected - *can-be-global-approved - > contains_any_globs(files, [ @@ -1190,6 +1192,8 @@ groups: size-tracking: <<: *defaults conditions: + - *no-groups-above-this-pending + - *no-groups-above-this-rejected - *can-be-global-approved - > contains_any_globs(files, [ @@ -1214,6 +1218,8 @@ groups: circular-dependencies: <<: *defaults conditions: + - *no-groups-above-this-pending + - *no-groups-above-this-rejected - *can-be-global-approved - > contains_any_globs(files, [ From 67e3ecc7e31a8bb8a48a614deabda9dd5932f919 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Thu, 23 Jul 2020 15:53:46 -0700 Subject: [PATCH 032/133] fix(dev-infra): Ensure conditions with groups do not fail verification (#37798) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are a few changes in this PR to ensure conditions that are based on groups (i.e. `- groups.pending.length == 0`) do not fail the verify task: * Remove the warning when a condition is encountered that depends on the `groups` state. The warning will otherwise be printed once for every file that triggers the execution of the condition (400,000+ times) * Add an `unverifiable` flag to `GroupCondition` interface and set it to true when an error is encountered due to attempting to get the state of `groups` in a condition * Ignore any unverifiable conditions when gathering unmatched conditions. These should not be considered `unmatched` for verification purposes. * Print the unverifiable conditions by group in the results Sample output: ``` ┌──────────────────────────────────────────────────────────────────────────────┐ │ PullApprove results by group │ └──────────────────────────────────────────────────────────────────────────────┘ Groups skipped (4 groups) Matched conditions by Group (37 groups) Unmatched conditions by Group (0 groups) Unverifiable conditions by Group (3 groups) [public-api] len(groups.pending.exclude("required-minimum-review")... len(groups.rejected.exclude("required-minimum-review")... [size-tracking] len(groups.pending.exclude("required-minimum-review")... len(groups.rejected.exclude("required-minimum-review")... [circular-dependencies] len(groups.pending.exclude("required-minimum-review")... len(groups.rejected.exclude("required-minimum-review")... ``` PR Close #37798 --- dev-infra/pullapprove/group.ts | 27 ++++++++++++++++----------- dev-infra/pullapprove/logging.ts | 19 ++++++++++++++----- dev-infra/pullapprove/verify.ts | 9 +++++++-- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/dev-infra/pullapprove/group.ts b/dev-infra/pullapprove/group.ts index 45de9eac23..3aaeaf1cf6 100644 --- a/dev-infra/pullapprove/group.ts +++ b/dev-infra/pullapprove/group.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {error, warn} from '../utils/console'; +import {error} from '../utils/console'; import {convertConditionToFunction} from './condition_evaluator'; import {PullApproveGroupConfig} from './parse-yaml'; import {PullApproveGroupStateDependencyError} from './pullapprove_arrays'; @@ -16,6 +16,7 @@ interface GroupCondition { expression: string; checkFn: (files: string[], groups: PullApproveGroup[]) => boolean; matchedFiles: Set; + unverifiable: boolean; } /** Result of testing files against the group. */ @@ -25,6 +26,7 @@ export interface PullApproveGroupResult { matchedCount: number; unmatchedConditions: GroupCondition[]; unmatchedCount: number; + unverifiableConditions: GroupCondition[]; } // Regular expression that matches conditions for the global approval. @@ -61,6 +63,7 @@ export class PullApproveGroup { expression, checkFn: convertConditionToFunction(expression), matchedFiles: new Set(), + unverifiable: false, }); } catch (e) { error(`Could not parse condition in group: ${this.groupName}`); @@ -79,7 +82,8 @@ export class PullApproveGroup { * the pull approve group's conditions. */ testFile(filePath: string): boolean { - return this.conditions.every(({matchedFiles, checkFn, expression}) => { + return this.conditions.every((condition) => { + const {matchedFiles, checkFn, expression} = condition; try { const matchesFile = checkFn([filePath], this.precedingGroups); if (matchesFile) { @@ -87,16 +91,14 @@ export class PullApproveGroup { } return matchesFile; } catch (e) { - // In the case of a condition that depends on the state of groups we want to just - // warn that the verification can't accurately evaluate the condition and then + // In the case of a condition that depends on the state of groups we want to + // ignore that the verification can't accurately evaluate the condition and then // continue processing. Other types of errors fail the verification, as conditions // should otherwise be able to execute without throwing. if (e instanceof PullApproveGroupStateDependencyError) { - const errMessage = `Condition could not be evaluated: \n` + - `${e.message}\n` + - `From the [${this.groupName}] group:\n` + - ` - ${expression}`; - warn(errMessage); + condition.unverifiable = true; + // Return true so that `this.conditions.every` can continue evaluating. + return true; } else { const errMessage = `Condition could not be evaluated: \n\n` + `From the [${this.groupName}] group:\n` + @@ -111,13 +113,16 @@ export class PullApproveGroup { /** Retrieve the results for the Group, all matched and unmatched conditions. */ getResults(): PullApproveGroupResult { - const matchedConditions = this.conditions.filter(c => !!c.matchedFiles.size); - const unmatchedConditions = this.conditions.filter(c => !c.matchedFiles.size); + const matchedConditions = this.conditions.filter(c => c.matchedFiles.size > 0); + const unmatchedConditions = + this.conditions.filter(c => c.matchedFiles.size === 0 && !c.unverifiable); + const unverifiableConditions = this.conditions.filter(c => c.unverifiable); return { matchedConditions, matchedCount: matchedConditions.length, unmatchedConditions, unmatchedCount: unmatchedConditions.length, + unverifiableConditions, groupName: this.groupName, }; } diff --git a/dev-infra/pullapprove/logging.ts b/dev-infra/pullapprove/logging.ts index ea289b7739..573516e23e 100644 --- a/dev-infra/pullapprove/logging.ts +++ b/dev-infra/pullapprove/logging.ts @@ -9,14 +9,23 @@ import {info} from '../utils/console'; import {PullApproveGroupResult} from './group'; +type ConditionGrouping = keyof Pick< + PullApproveGroupResult, 'matchedConditions'|'unmatchedConditions'|'unverifiableConditions'>; + /** Create logs for each pullapprove group result. */ -export function logGroup(group: PullApproveGroupResult, matched = true, printMessageFn = info) { - const conditions = matched ? group.matchedConditions : group.unmatchedConditions; +export function logGroup( + group: PullApproveGroupResult, conditionsToPrint: ConditionGrouping, printMessageFn = info) { + const conditions = group[conditionsToPrint]; printMessageFn.group(`[${group.groupName}]`); if (conditions.length) { - conditions.forEach(matcher => { - const count = matcher.matchedFiles.size; - printMessageFn(`${count} ${count === 1 ? 'match' : 'matches'} - ${matcher.expression}`); + conditions.forEach(groupCondition => { + const count = groupCondition.matchedFiles.size; + if (conditionsToPrint === 'unverifiableConditions') { + printMessageFn(`${groupCondition.expression}`); + } else { + printMessageFn( + `${count} ${count === 1 ? 'match' : 'matches'} - ${groupCondition.expression}`); + } }); printMessageFn.groupEnd(); } diff --git a/dev-infra/pullapprove/verify.ts b/dev-infra/pullapprove/verify.ts index d8302c2caa..1674083383 100644 --- a/dev-infra/pullapprove/verify.ts +++ b/dev-infra/pullapprove/verify.ts @@ -84,11 +84,16 @@ export function verify() { info.groupEnd(); const matchedGroups = resultsByGroup.filter(group => !group.unmatchedCount); info.group(`Matched conditions by Group (${matchedGroups.length} groups)`); - matchedGroups.forEach(group => logGroup(group, true, debug)); + matchedGroups.forEach(group => logGroup(group, 'matchedConditions', debug)); info.groupEnd(); const unmatchedGroups = resultsByGroup.filter(group => group.unmatchedCount); info.group(`Unmatched conditions by Group (${unmatchedGroups.length} groups)`); - unmatchedGroups.forEach(group => logGroup(group, false)); + unmatchedGroups.forEach(group => logGroup(group, 'unmatchedConditions')); + info.groupEnd(); + const unverifiableConditionsInGroups = + resultsByGroup.filter(group => group.unverifiableConditions.length > 0); + info.group(`Unverifiable conditions by Group (${unverifiableConditionsInGroups.length} groups)`); + unverifiableConditionsInGroups.forEach(group => logGroup(group, 'unverifiableConditions')); info.groupEnd(); // Provide correct exit code based on verification success. From 4dfc3fe6a29df0183aea9b946379157f3418a6e5 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Fri, 24 Jul 2020 15:41:44 -0700 Subject: [PATCH 033/133] refactor(router): extract Router config utils to a separate file (#38229) This commit refactors Router package to move config utils to a separate file for better organization and to resolve the problem with circular dependency issue. Resolves #38212. PR Close #38229 --- packages/router/src/config.ts | 106 ------------------ packages/router/src/router.ts | 3 +- packages/router/src/router_config_loader.ts | 3 +- packages/router/src/utils/config.ts | 115 ++++++++++++++++++++ packages/router/test/config.spec.ts | 2 +- 5 files changed, 120 insertions(+), 109 deletions(-) create mode 100644 packages/router/src/utils/config.ts diff --git a/packages/router/src/config.ts b/packages/router/src/config.ts index 8b6f2ab078..044cdcd796 100644 --- a/packages/router/src/config.ts +++ b/packages/router/src/config.ts @@ -9,9 +9,7 @@ import {NgModuleFactory, NgModuleRef, Type} from '@angular/core'; import {Observable} from 'rxjs'; -import {EmptyOutletComponent} from './components/empty_outlet'; import {ActivatedRouteSnapshot} from './router_state'; -import {PRIMARY_OUTLET} from './shared'; import {UrlSegment, UrlSegmentGroup} from './url_tree'; @@ -490,107 +488,3 @@ export interface Route { export class LoadedRouterConfig { constructor(public routes: Route[], public module: NgModuleRef) {} } - -export function validateConfig(config: Routes, parentPath: string = ''): void { - // forEach doesn't iterate undefined values - for (let i = 0; i < config.length; i++) { - const route: Route = config[i]; - const fullPath: string = getFullPath(parentPath, route); - validateNode(route, fullPath); - } -} - -function validateNode(route: Route, fullPath: string): void { - if (!route) { - throw new Error(` - Invalid configuration of route '${fullPath}': Encountered undefined route. - The reason might be an extra comma. - - Example: - const routes: Routes = [ - { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, - { path: 'dashboard', component: DashboardComponent },, << two commas - { path: 'detail/:id', component: HeroDetailComponent } - ]; - `); - } - if (Array.isArray(route)) { - throw new Error(`Invalid configuration of route '${fullPath}': Array cannot be specified`); - } - if (!route.component && !route.children && !route.loadChildren && - (route.outlet && route.outlet !== PRIMARY_OUTLET)) { - throw new Error(`Invalid configuration of route '${ - fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`); - } - if (route.redirectTo && route.children) { - throw new Error(`Invalid configuration of route '${ - fullPath}': redirectTo and children cannot be used together`); - } - if (route.redirectTo && route.loadChildren) { - throw new Error(`Invalid configuration of route '${ - fullPath}': redirectTo and loadChildren cannot be used together`); - } - if (route.children && route.loadChildren) { - throw new Error(`Invalid configuration of route '${ - fullPath}': children and loadChildren cannot be used together`); - } - if (route.redirectTo && route.component) { - throw new Error(`Invalid configuration of route '${ - fullPath}': redirectTo and component cannot be used together`); - } - if (route.path && route.matcher) { - throw new Error( - `Invalid configuration of route '${fullPath}': path and matcher cannot be used together`); - } - if (route.redirectTo === void 0 && !route.component && !route.children && !route.loadChildren) { - throw new Error(`Invalid configuration of route '${ - fullPath}'. One of the following must be provided: component, redirectTo, children or loadChildren`); - } - if (route.path === void 0 && route.matcher === void 0) { - throw new Error(`Invalid configuration of route '${ - fullPath}': routes must have either a path or a matcher specified`); - } - if (typeof route.path === 'string' && route.path.charAt(0) === '/') { - throw new Error(`Invalid configuration of route '${fullPath}': path cannot start with a slash`); - } - if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) { - const exp = - `The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`; - throw new Error(`Invalid configuration of route '{path: "${fullPath}", redirectTo: "${ - route.redirectTo}"}': please provide 'pathMatch'. ${exp}`); - } - if (route.pathMatch !== void 0 && route.pathMatch !== 'full' && route.pathMatch !== 'prefix') { - throw new Error(`Invalid configuration of route '${ - fullPath}': pathMatch can only be set to 'prefix' or 'full'`); - } - if (route.children) { - validateConfig(route.children, fullPath); - } -} - -function getFullPath(parentPath: string, currentRoute: Route): string { - if (!currentRoute) { - return parentPath; - } - if (!parentPath && !currentRoute.path) { - return ''; - } else if (parentPath && !currentRoute.path) { - return `${parentPath}/`; - } else if (!parentPath && currentRoute.path) { - return currentRoute.path; - } else { - return `${parentPath}/${currentRoute.path}`; - } -} - -/** - * Makes a copy of the config and adds any default required properties. - */ -export function standardizeConfig(r: Route): Route { - const children = r.children && r.children.map(standardizeConfig); - const c = children ? {...r, children} : {...r}; - if (!c.component && (children || c.loadChildren) && (c.outlet && c.outlet !== PRIMARY_OUTLET)) { - c.component = EmptyOutletComponent; - } - return c; -} diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index 7809e17892..83b915fa3c 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -11,7 +11,7 @@ import {Compiler, Injectable, Injector, isDevMode, NgModuleFactoryLoader, NgModu import {BehaviorSubject, EMPTY, Observable, of, Subject, SubscriptionLike} from 'rxjs'; import {catchError, filter, finalize, map, switchMap, tap} from 'rxjs/operators'; -import {QueryParamsHandling, Route, Routes, standardizeConfig, validateConfig} from './config'; +import {QueryParamsHandling, Route, Routes} from './config'; import {createRouterState} from './create_router_state'; import {createUrlTree} from './create_url_tree'; import {Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, NavigationTrigger, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events'; @@ -28,6 +28,7 @@ import {ActivatedRoute, createEmptyState, RouterState, RouterStateSnapshot} from import {isNavigationCancelingError, navigationCancelingError, Params} from './shared'; import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy'; import {containsTree, createEmptyUrlTree, UrlSerializer, UrlTree} from './url_tree'; +import {standardizeConfig, validateConfig} from './utils/config'; import {Checks, getAllRouteGuards} from './utils/preactivation'; import {isUrlTree} from './utils/type_guards'; diff --git a/packages/router/src/router_config_loader.ts b/packages/router/src/router_config_loader.ts index d9c014f345..4f2bda6975 100644 --- a/packages/router/src/router_config_loader.ts +++ b/packages/router/src/router_config_loader.ts @@ -10,8 +10,9 @@ import {Compiler, InjectionToken, Injector, NgModuleFactory, NgModuleFactoryLoad import {from, Observable, of} from 'rxjs'; import {map, mergeMap} from 'rxjs/operators'; -import {LoadChildren, LoadedRouterConfig, Route, standardizeConfig} from './config'; +import {LoadChildren, LoadedRouterConfig, Route} from './config'; import {flatten, wrapIntoObservable} from './utils/collection'; +import {standardizeConfig} from './utils/config'; /** * The [DI token](guide/glossary/#di-token) for a router configuration. diff --git a/packages/router/src/utils/config.ts b/packages/router/src/utils/config.ts new file mode 100644 index 0000000000..961ee3c54f --- /dev/null +++ b/packages/router/src/utils/config.ts @@ -0,0 +1,115 @@ +/** + * @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 + */ + +import {EmptyOutletComponent} from '../components/empty_outlet'; +import {Route, Routes} from '../config'; +import {PRIMARY_OUTLET} from '../shared'; + +export function validateConfig(config: Routes, parentPath: string = ''): void { + // forEach doesn't iterate undefined values + for (let i = 0; i < config.length; i++) { + const route: Route = config[i]; + const fullPath: string = getFullPath(parentPath, route); + validateNode(route, fullPath); + } +} + +function validateNode(route: Route, fullPath: string): void { + if (!route) { + throw new Error(` + Invalid configuration of route '${fullPath}': Encountered undefined route. + The reason might be an extra comma. + + Example: + const routes: Routes = [ + { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: DashboardComponent },, << two commas + { path: 'detail/:id', component: HeroDetailComponent } + ]; + `); + } + if (Array.isArray(route)) { + throw new Error(`Invalid configuration of route '${fullPath}': Array cannot be specified`); + } + if (!route.component && !route.children && !route.loadChildren && + (route.outlet && route.outlet !== PRIMARY_OUTLET)) { + throw new Error(`Invalid configuration of route '${ + fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`); + } + if (route.redirectTo && route.children) { + throw new Error(`Invalid configuration of route '${ + fullPath}': redirectTo and children cannot be used together`); + } + if (route.redirectTo && route.loadChildren) { + throw new Error(`Invalid configuration of route '${ + fullPath}': redirectTo and loadChildren cannot be used together`); + } + if (route.children && route.loadChildren) { + throw new Error(`Invalid configuration of route '${ + fullPath}': children and loadChildren cannot be used together`); + } + if (route.redirectTo && route.component) { + throw new Error(`Invalid configuration of route '${ + fullPath}': redirectTo and component cannot be used together`); + } + if (route.path && route.matcher) { + throw new Error( + `Invalid configuration of route '${fullPath}': path and matcher cannot be used together`); + } + if (route.redirectTo === void 0 && !route.component && !route.children && !route.loadChildren) { + throw new Error(`Invalid configuration of route '${ + fullPath}'. One of the following must be provided: component, redirectTo, children or loadChildren`); + } + if (route.path === void 0 && route.matcher === void 0) { + throw new Error(`Invalid configuration of route '${ + fullPath}': routes must have either a path or a matcher specified`); + } + if (typeof route.path === 'string' && route.path.charAt(0) === '/') { + throw new Error(`Invalid configuration of route '${fullPath}': path cannot start with a slash`); + } + if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) { + const exp = + `The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`; + throw new Error(`Invalid configuration of route '{path: "${fullPath}", redirectTo: "${ + route.redirectTo}"}': please provide 'pathMatch'. ${exp}`); + } + if (route.pathMatch !== void 0 && route.pathMatch !== 'full' && route.pathMatch !== 'prefix') { + throw new Error(`Invalid configuration of route '${ + fullPath}': pathMatch can only be set to 'prefix' or 'full'`); + } + if (route.children) { + validateConfig(route.children, fullPath); + } +} + +function getFullPath(parentPath: string, currentRoute: Route): string { + if (!currentRoute) { + return parentPath; + } + if (!parentPath && !currentRoute.path) { + return ''; + } else if (parentPath && !currentRoute.path) { + return `${parentPath}/`; + } else if (!parentPath && currentRoute.path) { + return currentRoute.path; + } else { + return `${parentPath}/${currentRoute.path}`; + } +} + +/** + * Makes a copy of the config and adds any default required properties. + */ +export function standardizeConfig(r: Route): Route { + const children = r.children && r.children.map(standardizeConfig); + const c = children ? {...r, children} : {...r}; + if (!c.component && (children || c.loadChildren) && (c.outlet && c.outlet !== PRIMARY_OUTLET)) { + c.component = EmptyOutletComponent; + } + return c; +} diff --git a/packages/router/test/config.spec.ts b/packages/router/test/config.spec.ts index fb4bb03d1e..76d32f3f5a 100644 --- a/packages/router/test/config.spec.ts +++ b/packages/router/test/config.spec.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {validateConfig} from '../src/config'; import {PRIMARY_OUTLET} from '../src/shared'; +import {validateConfig} from '../src/utils/config'; describe('config', () => { describe('validateConfig', () => { From 12960034455b693939dca39b4031cdf01bf70c1b Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Thu, 23 Jul 2020 15:53:53 +0200 Subject: [PATCH 034/133] docs: add ng-add save option (#38198) PR Close #38198 --- .../projects/my-lib/package.json | 11 +++++++++-- aio/content/guide/schematics-for-libraries.md | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/aio/content/examples/schematics-for-libraries/projects/my-lib/package.json b/aio/content/examples/schematics-for-libraries/projects/my-lib/package.json index 3607e2c0ce..a53579e4dd 100644 --- a/aio/content/examples/schematics-for-libraries/projects/my-lib/package.json +++ b/aio/content/examples/schematics-for-libraries/projects/my-lib/package.json @@ -16,5 +16,12 @@ "@angular/core": "^7.2.0" }, // #docregion collection - "schematics": "./schematics/collection.json" -} \ No newline at end of file + "schematics": "./schematics/collection.json", +// #enddocregion collection +// #docregion ng-add + "ng-add": { + "save": "devDependencies" + } +// #enddocregion ng-add +// #docregion collection +} diff --git a/aio/content/guide/schematics-for-libraries.md b/aio/content/guide/schematics-for-libraries.md index 1590c4ea87..e2520c5520 100644 --- a/aio/content/guide/schematics-for-libraries.md +++ b/aio/content/guide/schematics-for-libraries.md @@ -57,6 +57,20 @@ The task uses the user's preferred package manager to add the library to the pro In this example, the function receives the current `Tree` and returns it without any modifications. If you need to, you can do additional setup when your package is installed, such as generating files, updating configuration, or any other initial setup your library requires. +### Define dependency type + +Use the `save` option of `ng-add` to configure if the library should be added to the `dependencies`, the `devDepedencies`, or not saved at all in the project's `package.json` configuration file. + + + + +Possible values are: + + * `false` - Don't add the package to package.json + * `true` - Add the package to the dependencies + * `"dependencies"` - Add the package to the dependencies + * `"devDependencies"` - Add the package to the devDependencies + ## Building your schematics To bundle your schematics together with your library, you must configure the library to build the schematics separately, then add them to the bundle. From 2fdc18b42c72c8784a98344e4a5e1b0bc289cf28 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Thu, 23 Jul 2020 18:43:04 -0700 Subject: [PATCH 035/133] refactor(compiler): separate compilation and transform phases (#38213) This commit splits the transformation into 2 separate steps: Ivy compilation and actual transformation of corresponding TS nodes. This is needed to have all `o.Expression`s generated before any TS transforms happen. This allows `ConstantPool` to properly identify expressions that can be shared across multiple components declared in the same file. Resolves #38203. PR Close #38213 --- .../src/ngtsc/transform/src/transform.ts | 157 +++++++++++------- 1 file changed, 99 insertions(+), 58 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 53ad1e0378..c587ebcc15 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -14,6 +14,7 @@ import {Decorator, ReflectionHost} from '../../reflection'; import {ImportManager, translateExpression, translateStatement} from '../../translator'; import {visit, VisitListEntryResult, Visitor} from '../../util/src/visitor'; +import {CompileResult} from './api'; import {TraitCompiler} from './compilation'; import {addImports} from './utils'; @@ -43,12 +44,15 @@ export function ivyTransformFactory( }; } -class IvyVisitor extends Visitor { - constructor( - private compilation: TraitCompiler, private reflector: ReflectionHost, - private importManager: ImportManager, private defaultImportRecorder: DefaultImportRecorder, - private isClosureCompilerEnabled: boolean, private isCore: boolean, - private constantPool: ConstantPool) { +/** + * Visits all classes, performs Ivy compilation where Angular decorators are present and collects + * result in a Map that associates a ts.ClassDeclaration with Ivy compilation results. This visitor + * does NOT perform any TS transformations. + */ +class IvyCompilationVisitor extends Visitor { + public classCompilationMap = new Map(); + + constructor(private compilation: TraitCompiler, private constantPool: ConstantPool) { super(); } @@ -56,56 +60,78 @@ class IvyVisitor extends Visitor { VisitListEntryResult { // Determine if this class has an Ivy field that needs to be added, and compile the field // to an expression if so. - const res = this.compilation.compile(node, this.constantPool); - - if (res !== null) { - // There is at least one field to add. - const statements: ts.Statement[] = []; - const members = [...node.members]; - - res.forEach(field => { - // Translate the initializer for the field into TS nodes. - const exprNode = translateExpression( - field.initializer, this.importManager, this.defaultImportRecorder, - ts.ScriptTarget.ES2015); - - // Create a static property declaration for the new field. - const property = ts.createProperty( - undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], field.name, undefined, - undefined, exprNode); - - if (this.isClosureCompilerEnabled) { - // Closure compiler transforms the form `Service.ɵprov = X` into `Service$ɵprov = X`. To - // prevent this transformation, such assignments need to be annotated with @nocollapse. - // Note that tsickle is typically responsible for adding such annotations, however it - // doesn't yet handle synthetic fields added during other transformations. - ts.addSyntheticLeadingComment( - property, ts.SyntaxKind.MultiLineCommentTrivia, '* @nocollapse ', - /* hasTrailingNewLine */ false); - } - - field.statements - .map( - stmt => translateStatement( - stmt, this.importManager, this.defaultImportRecorder, ts.ScriptTarget.ES2015)) - .forEach(stmt => statements.push(stmt)); - - members.push(property); - }); - - // Replace the class declaration with an updated version. - node = ts.updateClassDeclaration( - node, - // Remove the decorator which triggered this compilation, leaving the others alone. - maybeFilterDecorator(node.decorators, this.compilation.decoratorsFor(node)), - node.modifiers, node.name, node.typeParameters, node.heritageClauses || [], - // Map over the class members and remove any Angular decorators from them. - members.map(member => this._stripAngularDecorators(member))); - return {node, after: statements}; + const result = this.compilation.compile(node, this.constantPool); + if (result !== null) { + this.classCompilationMap.set(node, result); } - return {node}; } +} + +/** + * Visits all classes and performs transformation of corresponding TS nodes based on the Ivy + * compilation results (provided as an argument). + */ +class IvyTransformationVisitor extends Visitor { + constructor( + private compilation: TraitCompiler, + private classCompilationMap: Map, + private reflector: ReflectionHost, private importManager: ImportManager, + private defaultImportRecorder: DefaultImportRecorder, + private isClosureCompilerEnabled: boolean, private isCore: boolean) { + super(); + } + + visitClassDeclaration(node: ts.ClassDeclaration): + VisitListEntryResult { + // If this class is not registered in the map, it means that it doesn't have Angular decorators, + // thus no further processing is required. + if (!this.classCompilationMap.has(node)) return {node}; + + // There is at least one field to add. + const statements: ts.Statement[] = []; + const members = [...node.members]; + + this.classCompilationMap.get(node)!.forEach(field => { + // Translate the initializer for the field into TS nodes. + const exprNode = translateExpression( + field.initializer, this.importManager, this.defaultImportRecorder, + ts.ScriptTarget.ES2015); + + // Create a static property declaration for the new field. + const property = ts.createProperty( + undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], field.name, undefined, + undefined, exprNode); + + if (this.isClosureCompilerEnabled) { + // Closure compiler transforms the form `Service.ɵprov = X` into `Service$ɵprov = X`. To + // prevent this transformation, such assignments need to be annotated with @nocollapse. + // Note that tsickle is typically responsible for adding such annotations, however it + // doesn't yet handle synthetic fields added during other transformations. + ts.addSyntheticLeadingComment( + property, ts.SyntaxKind.MultiLineCommentTrivia, '* @nocollapse ', + /* hasTrailingNewLine */ false); + } + + field.statements + .map( + stmt => translateStatement( + stmt, this.importManager, this.defaultImportRecorder, ts.ScriptTarget.ES2015)) + .forEach(stmt => statements.push(stmt)); + + members.push(property); + }); + + // Replace the class declaration with an updated version. + node = ts.updateClassDeclaration( + node, + // Remove the decorator which triggered this compilation, leaving the others alone. + maybeFilterDecorator(node.decorators, this.compilation.decoratorsFor(node)), node.modifiers, + node.name, node.typeParameters, node.heritageClauses || [], + // Map over the class members and remove any Angular decorators from them. + members.map(member => this._stripAngularDecorators(member))); + return {node, after: statements}; + } /** * Return all decorators on a `Declaration` which are from @angular/core, or an empty set if none @@ -224,11 +250,26 @@ function transformIvySourceFile( const constantPool = new ConstantPool(); const importManager = new ImportManager(importRewriter); - // Recursively scan through the AST and perform any updates requested by the IvyCompilation. - const visitor = new IvyVisitor( - compilation, reflector, importManager, defaultImportRecorder, isClosureCompilerEnabled, - isCore, constantPool); - let sf = visit(file, visitor, context); + // The transformation process consists of 2 steps: + // + // 1. Visit all classes, perform compilation and collect the results. + // 2. Perform actual transformation of required TS nodes using compilation results from the first + // step. + // + // This is needed to have all `o.Expression`s generated before any TS transforms happen. This + // allows `ConstantPool` to properly identify expressions that can be shared across multiple + // components declared in the same file. + + // Step 1. Go though all classes in AST, perform compilation and collect the results. + const compilationVisitor = new IvyCompilationVisitor(compilation, constantPool); + visit(file, compilationVisitor, context); + + // Step 2. Scan through the AST again and perform transformations based on Ivy compilation + // results obtained at Step 1. + const transformationVisitor = new IvyTransformationVisitor( + compilation, compilationVisitor.classCompilationMap, reflector, importManager, + defaultImportRecorder, isClosureCompilerEnabled, isCore); + let sf = visit(file, transformationVisitor, context); // Generate the constant statements first, as they may involve adding additional imports // to the ImportManager. From 8fdfa7b60481e1afc4b98ae0e7178a5e1c1b38ee Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Thu, 23 Jul 2020 18:47:33 -0700 Subject: [PATCH 036/133] refactor(compiler): allow strings with certain length to be included into `ConstantPool` (#38213) Prior to this commit, the `ConstantPool` ignored all primitive values. It turned out that it's beneficial to include strings above certain length to the pool as well. This commit updates the `ConstantPool` logic to allow such strings to be shared across multiple instances if needed. For instance, this is helpful for component styles that might be reused across multiple components in the same file. PR Close #38213 --- packages/compiler/src/constant_pool.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/constant_pool.ts b/packages/compiler/src/constant_pool.ts index 5ab77da0ad..24e800c95d 100644 --- a/packages/compiler/src/constant_pool.ts +++ b/packages/compiler/src/constant_pool.ts @@ -36,6 +36,13 @@ export const enum DefinitionKind { */ const KEY_CONTEXT = {}; +/** + * Generally all primitive values are excluded from the `ConstantPool`, but there is an exclusion + * for strings that reach a certain length threshold. This constant defines the length threshold for + * strings. + */ +const POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS = 50; + /** * A node that is a place-holder that allows the node to be replaced when the actual * node is known. @@ -96,7 +103,8 @@ export class ConstantPool { private nextNameIndex = 0; getConstLiteral(literal: o.Expression, forceShared?: boolean): o.Expression { - if (literal instanceof o.LiteralExpr || literal instanceof FixupExpression) { + if ((literal instanceof o.LiteralExpr && !isLongStringExpr(literal)) || + literal instanceof FixupExpression) { // Do no put simple literals into the constant pool or try to produce a constant for a // reference to a constant. return literal; @@ -305,3 +313,8 @@ function invalid(this: o.ExpressionVisitor, arg: o.Expression|o.Statement): n function isVariable(e: o.Expression): e is o.ReadVarExpr { return e instanceof o.ReadVarExpr; } + +function isLongStringExpr(expr: o.LiteralExpr): boolean { + return typeof expr.value === 'string' && + expr.value.length > POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS; +} \ No newline at end of file From 8e5969bb52c791599c437e90248f7314785c5869 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Thu, 23 Jul 2020 18:51:55 -0700 Subject: [PATCH 037/133] fix(compiler): share identical stylesheets between components in the same file (#38213) Prior to this commit, duplicated styles defined in multiple components in the same file were not shared between components, thus causing extra payload size. This commit updates compiler logic to use `ConstantPool` for the styles (while generating the `styles` array on component def), which enables styles sharing when needed (when duplicates styles are present). Resolves #38204. PR Close #38213 --- goldens/size-tracking/aio-payloads.json | 2 +- .../src/ngtsc/transform/src/transform.ts | 8 ++- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 62 +++++++++++++++++++ packages/compiler/src/constant_pool.ts | 2 +- .../compiler/src/render3/view/compiler.ts | 2 +- 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index 9d240aea2f..b91c2891d6 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 2987, - "main-es2015": 450596, + "main-es2015": 448419, "polyfills-es2015": 52630 } } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index c587ebcc15..99933680d0 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -86,13 +86,15 @@ class IvyTransformationVisitor extends Visitor { VisitListEntryResult { // If this class is not registered in the map, it means that it doesn't have Angular decorators, // thus no further processing is required. - if (!this.classCompilationMap.has(node)) return {node}; + if (!this.classCompilationMap.has(node)) { + return {node}; + } // There is at least one field to add. const statements: ts.Statement[] = []; const members = [...node.members]; - this.classCompilationMap.get(node)!.forEach(field => { + for (const field of this.classCompilationMap.get(node)!) { // Translate the initializer for the field into TS nodes. const exprNode = translateExpression( field.initializer, this.importManager, this.defaultImportRecorder, @@ -120,7 +122,7 @@ class IvyTransformationVisitor extends Visitor { .forEach(stmt => statements.push(stmt)); members.push(property); - }); + } // Replace the class declaration with an updated version. node = ts.updateClassDeclaration( diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 9ad1a3a678..e5fac6559c 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -6623,6 +6623,68 @@ export const Foo = Foo__PRE_R3__; const jsContents = env.getContents('test.js'); expect(jsContents).toContain('styles: ["h1[_ngcontent-%COMP%] {font-size: larger}"]'); }); + + it('should share same styles declared in different components in the same file', () => { + env.write('test.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'comp-a', + template: 'Comp A', + styles: [ + 'span { font-size: larger; }', + 'div { background: url(/some-very-very-long-path.png); }', + 'img { background: url(/a/some-very-very-long-path.png); }' + ] + }) + export class CompA {} + + @Component({ + selector: 'comp-b', + template: 'Comp B', + styles: [ + 'span { font-size: larger; }', + 'div { background: url(/some-very-very-long-path.png); }', + 'img { background: url(/b/some-very-very-long-path.png); }' + ] + }) + export class CompB {} + `); + + env.driveMain(); + const jsContents = env.getContents('test.js'); + + // Verify that long styles present in both components are extracted to a separate var. + expect(jsContents) + .toContain( + '_c0 = "div[_ngcontent-%COMP%] { background: url(/some-very-very-long-path.png); }";'); + + expect(jsContents) + .toContain( + 'styles: [' + + // This style is present in both components, but was not extracted into a separate + // var since it doesn't reach length threshold (50 chars) in `ConstantPool`. + '"span[_ngcontent-%COMP%] { font-size: larger; }", ' + + // Style that is present in both components, but reaches length threshold - + // extracted to a separate var. + '_c0, ' + + // Style that is unique to this component, but that reaches length threshold - + // remains a string in the `styles` array. + '"img[_ngcontent-%COMP%] { background: url(/a/some-very-very-long-path.png); }"]'); + + expect(jsContents) + .toContain( + 'styles: [' + + // This style is present in both components, but was not extracted into a separate + // var since it doesn't reach length threshold (50 chars) in `ConstantPool`. + '"span[_ngcontent-%COMP%] { font-size: larger; }", ' + + // Style that is present in both components, but reaches length threshold - + // extracted to a separate var. + '_c0, ' + + // Style that is unique to this component, but that reaches length threshold - + // remains a string in the `styles` array. + '"img[_ngcontent-%COMP%] { background: url(/b/some-very-very-long-path.png); }"]'); + }); }); describe('non-exported classes', () => { diff --git a/packages/compiler/src/constant_pool.ts b/packages/compiler/src/constant_pool.ts index 24e800c95d..b083a2c1ed 100644 --- a/packages/compiler/src/constant_pool.ts +++ b/packages/compiler/src/constant_pool.ts @@ -316,5 +316,5 @@ function isVariable(e: o.Expression): e is o.ReadVarExpr { function isLongStringExpr(expr: o.LiteralExpr): boolean { return typeof expr.value === 'string' && - expr.value.length > POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS; + expr.value.length >= POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS; } \ No newline at end of file diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index f0c305fe19..7adf55fb43 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -231,7 +231,7 @@ export function compileComponentFromMetadata( const styleValues = meta.encapsulation == core.ViewEncapsulation.Emulated ? compileStyles(meta.styles, CONTENT_ATTR, HOST_ATTR) : meta.styles; - const strings = styleValues.map(str => o.literal(str)); + const strings = styleValues.map(str => constantPool.getConstLiteral(o.literal(str))); definitionMap.set('styles', o.literalArr(strings)); } else if (meta.encapsulation === core.ViewEncapsulation.Emulated) { // If there is no style, don't generate css selectors on elements From f74cfadf641b1a395b69511b00167bfb984765a0 Mon Sep 17 00:00:00 2001 From: Changyu Geng Date: Tue, 7 Jul 2020 09:15:44 +0800 Subject: [PATCH 038/133] docs: clarify the description of pipes (#37950) This commit clarifies some of the language regarding pipes in the pipes guide. This commit also specifies the term transforming rather than formatting. PR Close #37950 --- aio/content/guide/pipes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aio/content/guide/pipes.md b/aio/content/guide/pipes.md index a28f0dd646..0d28ab4aa4 100644 --- a/aio/content/guide/pipes.md +++ b/aio/content/guide/pipes.md @@ -1,7 +1,7 @@ # Transforming Data Using Pipes -Use [pipes](guide/glossary#pipe "Definition of a pipe") to transform and format strings, currency amounts, dates, and other display data. -Pipes are simple functions you can use in [template expressions](/guide/glossary#template-expression "Definition of template expression") to accept an input value and return a transformed value. +Use [pipes](guide/glossary#pipe "Definition of a pipe") to transform strings, currency amounts, dates, and other data for display. +Pipes are simple functions you can use in [template expressions](/guide/glossary#template-expression "Definition of template expression") to accept an input value and return a transformed value. Pipes are useful because you can use them throughout your application, while only declaring each pipe once. For example, you would use a pipe to show a date as **April 15, 1988** rather than the raw string format.
@@ -63,7 +63,7 @@ function. {@a parameterizing-a-pipe} -## Formatting data with parameters and chained pipes +## Transforming data with parameters and chained pipes Use optional parameters to fine-tune a pipe's output. For example, you can use the [`CurrencyPipe`](api/common/CurrencyPipe "API reference") with a country code such as EUR as a parameter. From b65b6163f78c77dc2e11dade82d09757253cb939 Mon Sep 17 00:00:00 2001 From: Omar Hasan Date: Sun, 26 Jul 2020 17:12:36 +0200 Subject: [PATCH 039/133] docs: fix breaking URL for RxJS marble testing (#38209) When checking this URL for the `RxJS marble testing` Ive found it pointing to `Page not found` PR Close #38209 --- aio/content/guide/testing-components-scenarios.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/testing-components-scenarios.md b/aio/content/guide/testing-components-scenarios.md index cdd66ad27e..7ddb1af097 100644 --- a/aio/content/guide/testing-components-scenarios.md +++ b/aio/content/guide/testing-components-scenarios.md @@ -779,7 +779,7 @@ which reports router activity, is a _hot_ observable. RxJS marble testing is a rich subject, beyond the scope of this guide. Learn about it on the web, starting with the -[official documentation](https://github.com/ReactiveX/rxjs/blob/master/doc/writing-marble-tests.md). +[official documentation](https://rxjs.dev/guide/testing/marble-testing).
From 2b6ab57d789b181387b4f781931178ee1c3fc70b Mon Sep 17 00:00:00 2001 From: Kapunahele Wong Date: Tue, 9 Jun 2020 14:33:02 -0400 Subject: [PATCH 040/133] docs: add template ref var to glossary (#36743) There is not an entry in the glossary for template reference variable. To clarify for site visitors, we are adding one here. PR Close #36743 --- aio/content/guide/glossary.md | 13 +++++++++++++ aio/content/guide/template-reference-variables.md | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/aio/content/guide/glossary.md b/aio/content/guide/glossary.md index 38b9a73207..e2ad149975 100644 --- a/aio/content/guide/glossary.md +++ b/aio/content/guide/glossary.md @@ -939,6 +939,19 @@ A TypeScript-like syntax that Angular evaluates within a [data binding](#data-bi Read about how to write template expressions in the [template expressions](guide/interpolation#template-expressions) section of the [Interpolation](guide/interpolation) guide. +{@a template-reference-variable} + +## template reference variable + +A variable defined in a template that references an instance associated with an element, such as a directive instance, component instance, template as in `TemplateRef`, or DOM element. +After declaring a template reference variable on an element in a template, +you can access values from that variable elsewhere within the same template. +The following example defines a template reference variable named `#phone`. + + + +For more information, see the [Template reference variable](guide/template-reference-variables) guide. + {@a token} ## token diff --git a/aio/content/guide/template-reference-variables.md b/aio/content/guide/template-reference-variables.md index 144bbfcf1e..1f3fd5db28 100644 --- a/aio/content/guide/template-reference-variables.md +++ b/aio/content/guide/template-reference-variables.md @@ -19,6 +19,13 @@ Here, a ` `, - host: { - '[@state]': 'state', - }, animations: [ trigger('state', [ state('opened', style({transform: 'translateY(0%)'})), @@ -41,6 +38,7 @@ import { animate, state, style, transition, trigger } from '@angular/animations' `] }) export class PopupComponent { + @HostBinding('@state') state: 'opened' | 'closed' = 'closed'; @Input() diff --git a/aio/content/examples/event-binding/src/app/click.directive.ts b/aio/content/examples/event-binding/src/app/click.directive.ts index 4e9e9085b1..a04863fac8 100644 --- a/aio/content/examples/event-binding/src/app/click.directive.ts +++ b/aio/content/examples/event-binding/src/app/click.directive.ts @@ -1,4 +1,4 @@ -/* tslint:disable use-output-property-decorator directive-class-suffix */ +// tslint:disable: directive-selector import { Directive, ElementRef, EventEmitter, Output } from '@angular/core'; @Directive({selector: '[myClick]'}) diff --git a/aio/content/examples/hierarchical-dependency-injection/src/app/hero-tax-return.component.ts b/aio/content/examples/hierarchical-dependency-injection/src/app/hero-tax-return.component.ts index cbca85946a..9ee6325b2e 100644 --- a/aio/content/examples/hierarchical-dependency-injection/src/app/hero-tax-return.component.ts +++ b/aio/content/examples/hierarchical-dependency-injection/src/app/hero-tax-return.component.ts @@ -1,3 +1,4 @@ +// tslint:disable: no-output-native // #docregion import { Component, EventEmitter, Input, Output } from '@angular/core'; import { HeroTaxReturn } from './hero'; diff --git a/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.ts b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.ts index 8eed8bddbd..c150bcc214 100644 --- a/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.ts +++ b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.ts @@ -1,9 +1,4 @@ -/* tslint:disable:use-input-property-decorator */ -/* tslint:disable:use-output-property-decorator */ - -/* tslint:disable:no-input-rename */ - - +// tslint:disable: no-input-rename no-output-rename use-input-property-decorator use-output-property-decorator import { Component, EventEmitter, Input, Output } from '@angular/core'; @Component({ diff --git a/aio/content/examples/lifecycle-hooks/e2e/src/app.e2e-spec.ts b/aio/content/examples/lifecycle-hooks/e2e/src/app.e2e-spec.ts index 8aa8ee9f29..6ec9eb2e63 100644 --- a/aio/content/examples/lifecycle-hooks/e2e/src/app.e2e-spec.ts +++ b/aio/content/examples/lifecycle-hooks/e2e/src/app.e2e-spec.ts @@ -135,7 +135,7 @@ describe('Lifecycle hooks', () => { const inputEle = element(by.css('spy-parent input')); const addHeroButtonEle = element(by.cssContainingText('spy-parent button', 'Add Hero')); const resetHeroesButtonEle = element(by.cssContainingText('spy-parent button', 'Reset Heroes')); - const heroEles = element.all(by.css('spy-parent div[mySpy')); + 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'); diff --git a/aio/content/examples/lifecycle-hooks/src/app/counter.component.ts b/aio/content/examples/lifecycle-hooks/src/app/counter.component.ts index cddc847136..a2d7b5fd9b 100644 --- a/aio/content/examples/lifecycle-hooks/src/app/counter.component.ts +++ b/aio/content/examples/lifecycle-hooks/src/app/counter.component.ts @@ -13,7 +13,7 @@ import { LoggerService } from './logger.service'; Counter = {{counter}}
-- Counter Change Log --
-
{{chg}}
+
{{chg}}
`, styles: ['.counter {background: LightYellow; padding: 8px; margin-top: 8px}'] diff --git a/aio/content/examples/lifecycle-hooks/src/app/peek-a-boo.component.ts b/aio/content/examples/lifecycle-hooks/src/app/peek-a-boo.component.ts index a597a5ca73..c66e41a6c9 100644 --- a/aio/content/examples/lifecycle-hooks/src/app/peek-a-boo.component.ts +++ b/aio/content/examples/lifecycle-hooks/src/app/peek-a-boo.component.ts @@ -1,3 +1,5 @@ +// tslint:disable: no-conflicting-lifecycle +// #docregion import { AfterContentChecked, AfterContentInit, diff --git a/aio/content/examples/lifecycle-hooks/src/app/spy.component.html b/aio/content/examples/lifecycle-hooks/src/app/spy.component.html index f7deae9448..96ca78194a 100644 --- a/aio/content/examples/lifecycle-hooks/src/app/spy.component.html +++ b/aio/content/examples/lifecycle-hooks/src/app/spy.component.html @@ -7,7 +7,7 @@

-
+
{{hero}}
diff --git a/aio/content/examples/lifecycle-hooks/src/app/spy.directive.ts b/aio/content/examples/lifecycle-hooks/src/app/spy.directive.ts index 01f3f95880..c14f4e6455 100644 --- a/aio/content/examples/lifecycle-hooks/src/app/spy.directive.ts +++ b/aio/content/examples/lifecycle-hooks/src/app/spy.directive.ts @@ -7,8 +7,8 @@ let nextId = 1; // #docregion spy-directive // Spy on any element to which it is applied. -// Usage:
...
-@Directive({selector: '[mySpy]'}) +// Usage:
...
+@Directive({selector: '[appSpy]'}) export class SpyDirective implements OnInit, OnDestroy { constructor(private logger: LoggerService) { } diff --git a/aio/content/examples/observables-in-angular/src/main.ts b/aio/content/examples/observables-in-angular/src/main.ts index 0ba6e38f54..0eaca6bc62 100644 --- a/aio/content/examples/observables-in-angular/src/main.ts +++ b/aio/content/examples/observables-in-angular/src/main.ts @@ -1,11 +1,12 @@ - +// tslint:disable: no-output-native +// #docregion import { Component, Output, OnInit, EventEmitter, NgModule } from '@angular/core'; import { Observable } from 'rxjs'; // #docregion eventemitter @Component({ - selector: 'zippy', + selector: 'app-zippy', template: `
Toggle
diff --git a/aio/content/examples/styleguide/src/02-08/app/shared/input-highlight.directive.ts b/aio/content/examples/styleguide/src/02-08/app/shared/input-highlight.directive.ts index fd74771981..13f8bc1e98 100644 --- a/aio/content/examples/styleguide/src/02-08/app/shared/input-highlight.directive.ts +++ b/aio/content/examples/styleguide/src/02-08/app/shared/input-highlight.directive.ts @@ -1,3 +1,4 @@ +// tslint:disable: directive-selector // #docregion import { Directive, ElementRef } from '@angular/core'; diff --git a/aio/content/examples/styleguide/src/05-13/app/heroes/shared/hero-highlight.directive.ts b/aio/content/examples/styleguide/src/05-13/app/heroes/shared/hero-highlight.directive.ts index 737af31f4f..3af48d241e 100644 --- a/aio/content/examples/styleguide/src/05-13/app/heroes/shared/hero-highlight.directive.ts +++ b/aio/content/examples/styleguide/src/05-13/app/heroes/shared/hero-highlight.directive.ts @@ -1,3 +1,4 @@ +// tslint:disable: directive-selector // #docregion import { Directive, ElementRef, Input, OnChanges } from '@angular/core'; diff --git a/aio/content/examples/styleguide/src/06-03/app/shared/validator2.directive.ts b/aio/content/examples/styleguide/src/06-03/app/shared/validator2.directive.ts index 05a0f9f07f..dc61f4e504 100644 --- a/aio/content/examples/styleguide/src/06-03/app/shared/validator2.directive.ts +++ b/aio/content/examples/styleguide/src/06-03/app/shared/validator2.directive.ts @@ -1,3 +1,4 @@ +// tslint:disable: no-host-metadata-property // #docregion import { Directive } from '@angular/core'; diff --git a/aio/content/examples/testing/src/app/demo/demo.ts b/aio/content/examples/testing/src/app/demo/demo.ts index e97ccb387c..5c02167ea3 100644 --- a/aio/content/examples/testing/src/app/demo/demo.ts +++ b/aio/content/examples/testing/src/app/demo/demo.ts @@ -1,4 +1,4 @@ -/* tslint:disable:forin */ +// tslint:disable: directive-selector forin no-input-rename import { Component, ContentChildren, Directive, EventEmitter, Injectable, Input, Output, Optional, HostBinding, HostListener, diff --git a/aio/content/examples/testing/src/app/shared/highlight.directive.ts b/aio/content/examples/testing/src/app/shared/highlight.directive.ts index ce851d70d0..465c39cd09 100644 --- a/aio/content/examples/testing/src/app/shared/highlight.directive.ts +++ b/aio/content/examples/testing/src/app/shared/highlight.directive.ts @@ -1,3 +1,4 @@ +// tslint:disable: directive-selector // #docregion import { Directive, ElementRef, Input, OnChanges } from '@angular/core'; diff --git a/aio/content/examples/testing/src/testing/router-link-directive-stub.ts b/aio/content/examples/testing/src/testing/router-link-directive-stub.ts index 4da6f8fd04..ea89f67ab9 100644 --- a/aio/content/examples/testing/src/testing/router-link-directive-stub.ts +++ b/aio/content/examples/testing/src/testing/router-link-directive-stub.ts @@ -3,7 +3,7 @@ import { Directive, Input, HostListener } from '@angular/core'; // export for convenience. export { RouterLink} from '@angular/router'; -/* tslint:disable:directive-class-suffix */ +// tslint:disable: directive-class-suffix directive-selector // #docregion router-link @Directive({ selector: '[routerLink]' diff --git a/aio/content/examples/upgrade-module/src/app/a-to-ajs-transclusion/hero-detail.component.ts b/aio/content/examples/upgrade-module/src/app/a-to-ajs-transclusion/hero-detail.component.ts index 9d467a5411..f8292434f2 100644 --- a/aio/content/examples/upgrade-module/src/app/a-to-ajs-transclusion/hero-detail.component.ts +++ b/aio/content/examples/upgrade-module/src/app/a-to-ajs-transclusion/hero-detail.component.ts @@ -1,3 +1,4 @@ +// tslint:disable: directive-selector // #docregion export const heroDetail = { bindings: { diff --git a/aio/content/examples/upgrade-module/src/app/upgrade-io/hero-detail.component.ts b/aio/content/examples/upgrade-module/src/app/upgrade-io/hero-detail.component.ts index 4d74a1bea1..451507dda8 100644 --- a/aio/content/examples/upgrade-module/src/app/upgrade-io/hero-detail.component.ts +++ b/aio/content/examples/upgrade-module/src/app/upgrade-io/hero-detail.component.ts @@ -1,3 +1,4 @@ +// tslint:disable: directive-selector // #docregion // #docregion hero-detail-io export const heroDetail = { diff --git a/aio/content/examples/upgrade-module/src/app/upgrade-static/hero-detail.component.ts b/aio/content/examples/upgrade-module/src/app/upgrade-static/hero-detail.component.ts index 959ff16b01..3b81dc6be2 100644 --- a/aio/content/examples/upgrade-module/src/app/upgrade-static/hero-detail.component.ts +++ b/aio/content/examples/upgrade-module/src/app/upgrade-static/hero-detail.component.ts @@ -1,3 +1,4 @@ +// tslint:disable: directive-selector // #docregion // #docregion hero-detail export const heroDetail = { diff --git a/aio/content/guide/dynamic-component-loader.md b/aio/content/guide/dynamic-component-loader.md index 37a693f43c..94ec37fbc4 100644 --- a/aio/content/guide/dynamic-component-loader.md +++ b/aio/content/guide/dynamic-component-loader.md @@ -42,7 +42,7 @@ mark valid insertion points in the template. `AdDirective` injects `ViewContainerRef` to gain access to the view container of the element that will host the dynamically added component. -In the `@Directive` decorator, notice the selector name, `ad-host`; +In the `@Directive` decorator, notice the selector name, `adHost`; that's what you use to apply the directive to the element. The next section shows you how. @@ -56,7 +56,7 @@ decorator's `template` property as a template string. The `` element is where you apply the directive you just made. To apply the `AdDirective`, recall the selector from `ad.directive.ts`, -`ad-host`. Apply that to `` without the square brackets. Now Angular knows +`[adHost]`. Apply that to `` without the square brackets. Now Angular knows where to dynamically load components. diff --git a/aio/content/guide/observables-in-angular.md b/aio/content/guide/observables-in-angular.md index d4f31ab32d..a12030410e 100644 --- a/aio/content/guide/observables-in-angular.md +++ b/aio/content/guide/observables-in-angular.md @@ -14,7 +14,7 @@ When you call `emit()`, it passes the emitted value to the `next()` method of an A good example of usage can be found in the [EventEmitter](api/core/EventEmitter) documentation. Here is the example component that listens for open and close events: -`` +`` Here is the component definition: From 3a46c2da7c02619c5a19056f64e14b9917774fe2 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 30 Jul 2020 13:03:23 +0300 Subject: [PATCH 100/133] refactor(docs-infra): update docs examples `tslint.json` to match CLI and fix failures (#38143) This commit updates the `tslint.json` configuration file, that is used to lint the docs examples, to match the one generated for new Angular CLI apps. There are some minimal differences (marked with `TODO` comments) for things, such as component selector prefix, that would require extensive and/or difficult to validate changes in guides. This commit also includes the final adjustments to make the docs examples code compatible with the new tslint rules. (The bulk of the work has been done in previous commits.) PR Close #38143 --- .../e2e/src/app.e2e-spec.ts | 3 +- .../src/app/app.component.ts | 6 +- .../src/app/parent-finder.component.ts | 2 +- .../src/app/car/car.component.ts | 4 +- .../app/dashboard/dashboard.component.spec.ts | 2 +- .../testing/src/app/demo/async-helper.spec.ts | 2 +- .../testing/src/testing/jasmine-matchers.d.ts | 1 + .../testing/src/testing/jasmine-matchers.ts | 1 + aio/content/examples/tslint.json | 186 ++++++++++++------ .../examples/universal/src/app/app.module.ts | 2 +- .../shared/boilerplate/cli/tslint.json | 86 ++++++-- .../shared/boilerplate/systemjs/tslint.json | 183 +++++++++++------ 12 files changed, 323 insertions(+), 155 deletions(-) diff --git a/aio/content/examples/ajs-quick-reference/e2e/src/app.e2e-spec.ts b/aio/content/examples/ajs-quick-reference/e2e/src/app.e2e-spec.ts index ae38d15d8f..cc4c47c127 100644 --- a/aio/content/examples/ajs-quick-reference/e2e/src/app.e2e-spec.ts +++ b/aio/content/examples/ajs-quick-reference/e2e/src/app.e2e-spec.ts @@ -24,8 +24,7 @@ describe('AngularJS to Angular Quick Reference Tests', () => { // Go through the samples const movieRows = getMovieRows(); - for (let i = 0; i < expectedSamples.length; i++) { - const sample = expectedSamples[i]; + for (const sample of expectedSamples) { const tableCell = movieRows.get(sample.row) .all(by.tagName('td')).get(sample.column); // Check the cell or its nested element diff --git a/aio/content/examples/built-in-directives/src/app/app.component.ts b/aio/content/examples/built-in-directives/src/app/app.component.ts index 10a37ee371..a2035effa5 100644 --- a/aio/content/examples/built-in-directives/src/app/app.component.ts +++ b/aio/content/examples/built-in-directives/src/app/app.component.ts @@ -76,11 +76,7 @@ export class AppComponent implements OnInit { } giveNullCustomerValue() { - !(this.nullCustomer = null) ? (this.nullCustomer = 'Kelly') : (this.nullCustomer = null); - } - - resetNullItem() { - this.nullCustomer = null; + this.nullCustomer = 'Kelly'; } resetItems() { diff --git a/aio/content/examples/dependency-injection-in-action/src/app/parent-finder.component.ts b/aio/content/examples/dependency-injection-in-action/src/app/parent-finder.component.ts index 18df7ddae0..cf25406ed5 100644 --- a/aio/content/examples/dependency-injection-in-action/src/app/parent-finder.component.ts +++ b/aio/content/examples/dependency-injection-in-action/src/app/parent-finder.component.ts @@ -1,4 +1,4 @@ -/* tslint:disable: no-unused-variable component-selector one-line space-before-function-paren */ +// tslint:disable: component-selector space-before-function-paren // #docplaster // #docregion import { Component, forwardRef, Optional, SkipSelf } from '@angular/core'; diff --git a/aio/content/examples/dependency-injection/src/app/car/car.component.ts b/aio/content/examples/dependency-injection/src/app/car/car.component.ts index d4f418e058..3463c52250 100644 --- a/aio/content/examples/dependency-injection/src/app/car/car.component.ts +++ b/aio/content/examples/dependency-injection/src/app/car/car.component.ts @@ -27,9 +27,9 @@ import { useInjector } from './car-injector'; providers: [Car, Engine, Tires] }) export class CarComponent { - factoryCar = (new CarFactory).createCar(); + factoryCar = (new CarFactory()).createCar(); injectorCar = useInjector(); - noDiCar = new CarNoDi; + noDiCar = new CarNoDi(); simpleCar = simpleCar(); superCar = superCar(); testCar = testCar(); diff --git a/aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts b/aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts index 1a9a695cc4..e60e4cc313 100644 --- a/aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts +++ b/aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts @@ -90,7 +90,7 @@ function compileAndCreate() { * The (almost) same tests for both. * Only change: the way that the first hero is clicked */ -function tests(heroClick: Function) { +function tests(heroClick: () => void) { it('should NOT have heroes before ngOnInit', () => { expect(comp.heroes.length).toBe(0, diff --git a/aio/content/examples/testing/src/app/demo/async-helper.spec.ts b/aio/content/examples/testing/src/app/demo/async-helper.spec.ts index 8f6d340ffb..c96954d042 100644 --- a/aio/content/examples/testing/src/app/demo/async-helper.spec.ts +++ b/aio/content/examples/testing/src/app/demo/async-helper.spec.ts @@ -147,7 +147,7 @@ describe('Angular async helper', () => { // #docregion async-test-promise-then describe('test jsonp', () => { - function jsonp(url: string, callback: Function) { + function jsonp(url: string, callback: () => void) { // do a jsonp call which is not zone aware } // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag diff --git a/aio/content/examples/testing/src/testing/jasmine-matchers.d.ts b/aio/content/examples/testing/src/testing/jasmine-matchers.d.ts index cafce642f9..62063b8dfa 100644 --- a/aio/content/examples/testing/src/testing/jasmine-matchers.d.ts +++ b/aio/content/examples/testing/src/testing/jasmine-matchers.d.ts @@ -1,3 +1,4 @@ +// tslint:disable-next-line: no-namespace declare namespace jasmine { interface Matchers { toHaveText(actual: any, expectationFailOutput?: any): jasmine.CustomMatcher; diff --git a/aio/content/examples/testing/src/testing/jasmine-matchers.ts b/aio/content/examples/testing/src/testing/jasmine-matchers.ts index 0c127e0977..6f2ff3c250 100644 --- a/aio/content/examples/testing/src/testing/jasmine-matchers.ts +++ b/aio/content/examples/testing/src/testing/jasmine-matchers.ts @@ -1,3 +1,4 @@ +// tslint:disable-next-line: no-reference /// //// Jasmine Custom Matchers //// diff --git a/aio/content/examples/tslint.json b/aio/content/examples/tslint.json index 45be76dec4..bf1abc0372 100644 --- a/aio/content/examples/tslint.json +++ b/aio/content/examples/tslint.json @@ -1,28 +1,61 @@ { - "rulesDirectory": [ - "../../node_modules/codelyzer" - ], + "extends": "tslint:recommended", "rules": { - "class-name": true, - "comment-format": [ - true, - "check-space" - ], + "align": { + "options": [ + "parameters", + "statements" + ] + }, + "array-type": false, + "arrow-return-shorthand": true, "curly": true, - "eofline": true, - "forin": true, - "indent": [ + "deprecation": { + "severity": "warning" + }, + "component-class-suffix": true, + "component-selector": [ true, - "spaces" + "element", + // TODO: Fix the code and change the prefix to `"app"` (or whatever makes sense). + "", + "kebab-case" ], - "label-position": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "directive-selector": [ + true, + "attribute", + ["app", "toh"], + "camelCase" + ], + "eofline": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": { + "options": [ + "spaces" + ] + }, + "max-classes-per-file": false, "max-line-length": [ true, 140 ], - "member-access": false, - "no-arg": true, - "no-bitwise": true, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], "no-console": [ true, "debug", @@ -31,63 +64,86 @@ "timeEnd", "trace" ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-variable": true, "no-empty": false, - "no-eval": true, - "no-inferrable-types": true, - "no-string-literal": false, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unused-expression": true, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "one-line": [ + "no-inferrable-types": [ true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" ], "quotemark": [ true, "single" ], - "radix": true, - "semicolon": [ - "always" - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" } - ], - "variable-name": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ], - + }, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + }, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, + "no-input-rename": true, "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, "no-outputs-metadata-property": true, - "use-life-cycle-interface": true, - "use-pipe-transform-interface": true, - "component-class-suffix": true, - "directive-class-suffix": true, - "import-destructuring-spacing": true - } + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true + }, + "rulesDirectory": [ + "codelyzer" + ] } diff --git a/aio/content/examples/universal/src/app/app.module.ts b/aio/content/examples/universal/src/app/app.module.ts index a843e4d774..b27d822356 100644 --- a/aio/content/examples/universal/src/app/app.module.ts +++ b/aio/content/examples/universal/src/app/app.module.ts @@ -51,7 +51,7 @@ import { isPlatformBrowser } from '@angular/common'; export class AppModule { // #docregion platform-detection constructor( - @Inject(PLATFORM_ID) private platformId: Object, + @Inject(PLATFORM_ID) private platformId: object, @Inject(APP_ID) private appId: string) { const platform = isPlatformBrowser(platformId) ? 'in the browser' : 'on the server'; diff --git a/aio/tools/examples/shared/boilerplate/cli/tslint.json b/aio/tools/examples/shared/boilerplate/cli/tslint.json index f85fc68d9e..bf1abc0372 100644 --- a/aio/tools/examples/shared/boilerplate/cli/tslint.json +++ b/aio/tools/examples/shared/boilerplate/cli/tslint.json @@ -1,37 +1,50 @@ { "extends": "tslint:recommended", "rules": { + "align": { + "options": [ + "parameters", + "statements" + ] + }, "array-type": false, - "arrow-parens": false, + "arrow-return-shorthand": true, + "curly": true, "deprecation": { "severity": "warning" }, "component-class-suffix": true, + "component-selector": [ + true, + "element", + // TODO: Fix the code and change the prefix to `"app"` (or whatever makes sense). + "", + "kebab-case" + ], "contextual-lifecycle": true, "directive-class-suffix": true, "directive-selector": [ true, "attribute", - "app", + ["app", "toh"], "camelCase" ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ], + "eofline": true, "import-blacklist": [ true, "rxjs/Rx" ], - "interface-name": false, + "import-spacing": true, + "indent": { + "options": [ + "spaces" + ] + }, "max-classes-per-file": false, "max-line-length": [ true, 140 ], - "member-access": false, "member-ordering": [ true, { @@ -43,7 +56,6 @@ ] } ], - "no-consecutive-blank-lines": false, "no-console": [ true, "debug", @@ -65,13 +77,59 @@ true, "as-needed" ], - "object-literal-sort-keys": false, - "ordered-imports": false, "quotemark": [ true, "single" ], - "trailing-comma": false, + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" + } + }, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + }, "no-conflicting-lifecycle": true, "no-host-metadata-property": true, "no-input-rename": true, diff --git a/aio/tools/examples/shared/boilerplate/systemjs/tslint.json b/aio/tools/examples/shared/boilerplate/systemjs/tslint.json index a766b0154e..bf1abc0372 100644 --- a/aio/tools/examples/shared/boilerplate/systemjs/tslint.json +++ b/aio/tools/examples/shared/boilerplate/systemjs/tslint.json @@ -1,31 +1,61 @@ { + "extends": "tslint:recommended", "rules": { - "class-name": true, - "comment-format": [ - true, - "check-space" - ], + "align": { + "options": [ + "parameters", + "statements" + ] + }, + "array-type": false, + "arrow-return-shorthand": true, "curly": true, - "eofline": true, - "forin": true, - "indent": [ + "deprecation": { + "severity": "warning" + }, + "component-class-suffix": true, + "component-selector": [ true, - "spaces" + "element", + // TODO: Fix the code and change the prefix to `"app"` (or whatever makes sense). + "", + "kebab-case" ], - "label-position": true, - "label-undefined": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "directive-selector": [ + true, + "attribute", + ["app", "toh"], + "camelCase" + ], + "eofline": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": { + "options": [ + "spaces" + ] + }, + "max-classes-per-file": false, "max-line-length": [ true, 140 ], - "member-access": false, "member-ordering": [ true, - "static-before-instance", - "variables-before-functions" + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } ], - "no-arg": true, - "no-bitwise": true, "no-console": [ true, "debug", @@ -34,59 +64,86 @@ "timeEnd", "trace" ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-key": true, - "no-duplicate-variable": true, "no-empty": false, - "no-eval": true, - "no-inferrable-types": true, - "no-shadowed-variable": true, - "no-string-literal": false, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unused-expression": true, - "no-unused-variable": true, - "no-unreachable": true, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "one-line": [ + "no-inferrable-types": [ true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" ], "quotemark": [ true, "single" ], - "radix": true, - "semicolon": [ - "always" - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" } - ], - "variable-name": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ] - } + }, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + }, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, + "no-outputs-metadata-property": true, + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true + }, + "rulesDirectory": [ + "codelyzer" + ] } From ff9f4de4f1147ba9ea6d17c31442a2eedcf4e0d2 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Fri, 31 Jul 2020 12:26:39 -0700 Subject: [PATCH 101/133] fix(compiler): update unparsable character reference entity error messages (#38319) Within an angular template, when a character entity is unable to be parsed, previously a generic unexpected character error was thrown. This does not properly express the issue that was discovered as the issue is actually caused by the discovered character making the whole of the entity unparsable. The compiler will now instead inform via the error message what string was attempted to be parsed and what it was attempted to be parsed as. Example, for this template: ```

ģp

``` Before this change: `Unexpected character "p"` After this change: `Unable to parse entity "ģp" - hexadecimal character reference entities must end with ";"` Fixes #26067 PR Close #38319 --- packages/compiler/src/ml_parser/lexer.ts | 17 ++++++++++++++++- packages/compiler/test/ml_parser/lexer_spec.ts | 16 ++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/ml_parser/lexer.ts b/packages/compiler/src/ml_parser/lexer.ts index ffe386537e..38a82a7d23 100644 --- a/packages/compiler/src/ml_parser/lexer.ts +++ b/packages/compiler/src/ml_parser/lexer.ts @@ -138,6 +138,16 @@ function _unknownEntityErrorMsg(entitySrc: string): string { return `Unknown entity "${entitySrc}" - use the "&#;" or "&#x;" syntax`; } +function _unparsableEntityErrorMsg(type: CharacterReferenceType, entityStr: string): string { + return `Unable to parse entity "${entityStr}" - ${ + type} character reference entities must end with ";"`; +} + +enum CharacterReferenceType { + HEX = 'hexadecimal', + DEC = 'decimal', +} + class _ControlFlowError { constructor(public error: TokenError) {} } @@ -400,8 +410,13 @@ class _Tokenizer { const codeStart = this._cursor.clone(); this._attemptCharCodeUntilFn(isDigitEntityEnd); if (this._cursor.peek() != chars.$SEMICOLON) { + // Advance cursor to include the peeked character in the string provided to the error + // message. + this._cursor.advance(); + const entityType = isHex ? CharacterReferenceType.HEX : CharacterReferenceType.DEC; throw this._createError( - _unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan()); + _unparsableEntityErrorMsg(entityType, this._cursor.getChars(start)), + this._cursor.getSpan()); } const strNum = this._cursor.getChars(codeStart); this._cursor.advance(); diff --git a/packages/compiler/test/ml_parser/lexer_spec.ts b/packages/compiler/test/ml_parser/lexer_spec.ts index 79b387e86f..32895b12ea 100644 --- a/packages/compiler/test/ml_parser/lexer_spec.ts +++ b/packages/compiler/test/ml_parser/lexer_spec.ts @@ -477,12 +477,16 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u lex.TokenType.TEXT, 'Unknown entity "tbo" - use the "&#;" or "&#x;" syntax', '0:0' ]]); - expect(tokenizeAndHumanizeErrors('sdf;')).toEqual([ - [lex.TokenType.TEXT, 'Unexpected character "s"', '0:3'] - ]); - expect(tokenizeAndHumanizeErrors(' sdf;')).toEqual([ - [lex.TokenType.TEXT, 'Unexpected character "s"', '0:4'] - ]); + expect(tokenizeAndHumanizeErrors('sdf;')).toEqual([[ + lex.TokenType.TEXT, + 'Unable to parse entity "s" - decimal character reference entities must end with ";"', + '0:4' + ]]); + expect(tokenizeAndHumanizeErrors(' sdf;')).toEqual([[ + lex.TokenType.TEXT, + 'Unable to parse entity " s" - hexadecimal character reference entities must end with ";"', + '0:5' + ]]); expect(tokenizeAndHumanizeErrors('઼')).toEqual([ [lex.TokenType.TEXT, 'Unexpected character "EOF"', '0:6'] From 87baa06cc6be1c99145ad5083b06c625128435aa Mon Sep 17 00:00:00 2001 From: Zara Cooper Date: Sat, 1 Aug 2020 23:00:34 +0300 Subject: [PATCH 102/133] revert: docs(core): correct SomeService to SomeComponent (#38325) This reverts commit b4449e35bfd04c5858ded17c0ce56248d680e8d4. The example given from the previous change was for a component selector and not a provider selector. This change fixes it. Fixes #38323. PR Close #38325 --- packages/core/src/metadata/di.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/metadata/di.ts b/packages/core/src/metadata/di.ts index 792ee45a34..3b14ec3163 100644 --- a/packages/core/src/metadata/di.ts +++ b/packages/core/src/metadata/di.ts @@ -332,7 +332,7 @@ export interface ViewChildDecorator { * * A template reference variable as a string (e.g. query `` * with `@ViewChild('cmp')`) * * Any provider defined in the child component tree of the current component (e.g. - * `@ViewChild(SomeComponent) someComponent: SomeComponent`) + * `@ViewChild(SomeService) someService: SomeService`) * * Any provider defined through a string token (e.g. `@ViewChild('someToken') someTokenVal: * any`) * * A `TemplateRef` (e.g. query `` with `@ViewChild(TemplateRef) From e49b053dac3ca60369491f9c5a0b355ddefb5f20 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 17 Jul 2020 13:31:28 -0700 Subject: [PATCH 103/133] build: cleanup .bazelrc file to no longer set unused flags (#38124) This option is no longer needed in Bazel and will be an error in the future PR Close #38124 --- .bazelrc | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.bazelrc b/.bazelrc index 37f7b117fb..f7804b95e9 100644 --- a/.bazelrc +++ b/.bazelrc @@ -136,15 +136,6 @@ build:remote --remote_executor=remotebuildexecution.googleapis.com # retry mechanism and we do not want to retry unnecessarily if Karma already tried multiple times. test:saucelabs --flaky_test_attempts=1 -############################### -# NodeJS rules settings -# These settings are required for rules_nodejs -############################### - -# Turn on managed directories feature in Bazel -# This allows us to avoid installing a second copy of node_modules -common --experimental_allow_incremental_repository_updates - #################################################### # User bazel configuration # NOTE: This needs to be the *last* entry in the config. From 8f074296c2ffb20521e2ad1bbbb3dc8f2194cae6 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sat, 1 Aug 2020 04:43:05 +0900 Subject: [PATCH 104/133] feat(core): rename async to waitForAsync to avoid confusing (#37583) @angular/core/testing provide `async` test utility, but the name `async` is confusing with the javascript keyword `async`. And in some test case, if you want to use both the `async` from `@angular/core/testing` and `async/await`, you may have to write the code like this. ```typescript it('test async operations', async(async() => { const result = await asyncMethod(); expect(result).toEqual('expected'); })); ``` So in this PR, the `async` is renamed to `waitForAsync` and also deprecate `async`. PR Close #37583 --- packages/core/testing/src/async.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/core/testing/src/async.ts b/packages/core/testing/src/async.ts index 31d12f3d0d..eac783214c 100644 --- a/packages/core/testing/src/async.ts +++ b/packages/core/testing/src/async.ts @@ -16,7 +16,7 @@ import {asyncFallback} from './async_fallback'; * Example: * * ``` - * it('...', async(inject([AClass], (object) => { + * it('...', waitForAsync(inject([AClass], (object) => { * object.doSomething.then(() => { * expect(...); * }) @@ -25,12 +25,12 @@ import {asyncFallback} from './async_fallback'; * * @publicApi */ -export function async(fn: Function): (done: any) => any { +export function waitForAsync(fn: Function): (done: any) => any { const _Zone: any = typeof Zone !== 'undefined' ? Zone : null; if (!_Zone) { return function() { return Promise.reject( - 'Zone is needed for the async() test helper but could not be found. ' + + 'Zone is needed for the waitForAsync() test helper but could not be found. ' + 'Please make sure that your environment includes zone.js/dist/zone.js'); }; } @@ -43,3 +43,12 @@ export function async(fn: Function): (done: any) => any { // newest version of zone.js(0.8.25) return asyncFallback(fn); } + +/** + * @deprecated use `waitForAsync()`, (expected removal in v12) + * @see {@link waitForAsync} + * @publicApi + * */ +export function async(fn: Function): (done: any) => any { + return waitForAsync(fn); +} From 8fbf40bf4000944c06458472142ca967ef8d3b1d Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sat, 1 Aug 2020 04:43:18 +0900 Subject: [PATCH 105/133] feat(core): update reference and doc to change `async` to `waitAsync`. (#37583) The last commit change `async` to `waitForAsync`. This commit update all usages in the code and also update aio doc. PR Close #37583 --- .../src/app/app.component.spec.ts | 42 +-- .../favorite-color.component.spec.ts | 15 +- .../favorite-color.component.spec.ts | 39 ++- .../my-lib/src/lib/my-lib.component.spec.ts | 9 +- .../setup/src/app/app.component.spec.ts | 20 +- .../src/app/app-initial.component.spec.ts | 54 ++-- .../src/app/app.component.router.spec.ts | 135 +++++----- .../testing/src/app/app.component.spec.ts | 122 ++++----- .../banner/banner-external.component.spec.ts | 37 +-- .../banner/banner-initial.component.spec.ts | 23 +- .../dashboard-hero.component.spec.ts | 56 ++-- .../app/dashboard/dashboard.component.spec.ts | 66 ++--- .../testing/src/app/demo/async-helper.spec.ts | 83 ++++-- .../testing/src/app/demo/demo.testbed.spec.ts | 123 ++++----- .../app/hero/hero-detail.component.spec.ts | 253 +++++++++--------- .../src/app/hero/hero-list.component.spec.ts | 70 +++-- .../src/app/shared/canvas.component.spec.ts | 31 +-- .../src/app/twain/twain.component.spec.ts | 88 +++--- .../app/dashboard/dashboard.component.spec.ts | 50 ++-- .../phone-detail.component.spec.ts | 14 +- .../phone-list/phone-list.component.spec.ts | 57 ++-- .../phone-detail.component.spec.ts | 14 +- .../phone-list/phone-list.component.spec.ts | 57 ++-- aio/content/guide/deprecations.md | 6 +- .../guide/testing-components-basics.md | 4 +- .../guide/testing-components-scenarios.md | 32 +-- aio/content/guide/testing-utility-apis.md | 2 +- aio/content/guide/upgrade-setup.md | 4 +- goldens/public-api/core/testing/testing.d.ts | 3 + .../src/app/app.component.spec.ts | 19 +- .../src/app/app.component.spec.ts | 16 +- .../src/app/app.component.spec.ts | 25 +- .../src/app/app.component.spec.ts | 25 +- .../src/app/app.component.spec.ts | 19 +- .../src/app/app.component.spec.ts | 16 +- .../common/test/directives/ng_class_spec.ts | 60 +++-- .../directives/ng_component_outlet_spec.ts | 26 +- .../common/test/directives/ng_for_spec.ts | 67 ++--- packages/common/test/directives/ng_if_spec.ts | 29 +- .../common/test/directives/ng_plural_spec.ts | 16 +- .../common/test/directives/ng_style_spec.ts | 20 +- .../directives/ng_template_outlet_spec.ts | 23 +- .../test/directives/non_bindable_spec.ts | 8 +- packages/common/test/pipes/json_pipe_spec.ts | 4 +- packages/common/test/pipes/slice_pipe_spec.ts | 4 +- .../test/i18n/integration_xliff2_spec.ts | 10 +- .../test/i18n/integration_xliff_spec.ts | 9 +- .../test/i18n/integration_xmb_xtb_spec.ts | 6 +- packages/compiler/test/integration_spec.ts | 6 +- .../compiler/test/metadata_resolver_spec.ts | 6 +- .../compiler/test/runtime_compiler_spec.ts | 8 +- .../core/test/acceptance/providers_spec.ts | 4 +- packages/core/test/application_init_spec.ts | 8 +- packages/core/test/application_ref_spec.ts | 136 +++++----- packages/core/test/component_fixture_spec.ts | 26 +- packages/core/test/debug/debug_node_spec.ts | 4 +- packages/core/test/linker/integration_spec.ts | 23 +- .../linker/jit_summaries_integration_spec.ts | 4 +- .../test/linker/query_integration_spec.ts | 4 +- .../system_ng_module_factory_loader_spec.ts | 12 +- .../core/test/testability/testability_spec.ts | 22 +- packages/core/test/zone/ng_zone_spec.ts | 4 +- packages/forms/test/form_group_spec.ts | 4 +- .../forms/test/template_integration_spec.ts | 8 +- .../test/value_accessor_integration_spec.ts | 4 +- .../test/http-client-backend-service_spec.ts | 93 +++---- .../resource_loader_cache_spec.ts | 6 +- .../test/testing_public_browser_spec.ts | 8 +- .../test/testing_public_spec.ts | 34 +-- .../platform-server/test/integration_spec.ts | 62 ++--- .../upgrade/src/dynamic/test/upgrade_spec.ts | 122 +++++---- .../test/integration/change_detection_spec.ts | 8 +- .../integration/content_projection_spec.ts | 10 +- .../integration/downgrade_component_spec.ts | 34 +-- .../test/integration/downgrade_module_spec.ts | 43 +-- .../static/test/integration/examples_spec.ts | 4 +- .../static/test/integration/injection_spec.ts | 10 +- .../integration/upgrade_component_spec.ts | 103 +++---- 78 files changed, 1363 insertions(+), 1368 deletions(-) diff --git a/aio/content/examples/forms-overview/src/app/app.component.spec.ts b/aio/content/examples/forms-overview/src/app/app.component.spec.ts index 489e490485..81488e7bc6 100644 --- a/aio/content/examples/forms-overview/src/app/app.component.spec.ts +++ b/aio/content/examples/forms-overview/src/app/app.component.spec.ts @@ -1,31 +1,31 @@ -import { TestBed, async } from '@angular/core/testing'; +import { TestBed, waitForAsync } from '@angular/core/testing'; + import { AppComponent } from './app.component'; -import { TemplateModule } from './template/template.module'; import { ReactiveModule } from './reactive/reactive.module'; +import { TemplateModule } from './template/template.module'; describe('AppComponent', () => { - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ReactiveModule, TemplateModule], - declarations: [ - AppComponent - ], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + imports: [ReactiveModule, TemplateModule], + declarations: [AppComponent], + }) + .compileComponents(); })); - it('should create the app', async(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; + it('should create the app', waitForAsync(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; - expect(app).toBeTruthy(); - })); + expect(app).toBeTruthy(); + })); - it('should render title', async(() => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); + it('should render title', waitForAsync(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); - const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Forms Overview'); - })); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Forms Overview'); + })); }); diff --git a/aio/content/examples/forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts b/aio/content/examples/forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts index 5b676c08b8..47e7549501 100644 --- a/aio/content/examples/forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts +++ b/aio/content/examples/forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts @@ -1,19 +1,18 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; -import { FavoriteColorComponent } from './favorite-color.component'; import { createNewEvent } from '../../shared/utils'; +import { FavoriteColorComponent } from './favorite-color.component'; describe('Favorite Color Component', () => { let component: FavoriteColorComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ ReactiveFormsModule ], - declarations: [ FavoriteColorComponent ] - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule( + {imports: [ReactiveFormsModule], declarations: [FavoriteColorComponent]}) + .compileComponents(); })); beforeEach(() => { diff --git a/aio/content/examples/forms-overview/src/app/template/favorite-color/favorite-color.component.spec.ts b/aio/content/examples/forms-overview/src/app/template/favorite-color/favorite-color.component.spec.ts index 60e1830b4d..0a00898167 100644 --- a/aio/content/examples/forms-overview/src/app/template/favorite-color/favorite-color.component.spec.ts +++ b/aio/content/examples/forms-overview/src/app/template/favorite-color/favorite-color.component.spec.ts @@ -1,19 +1,16 @@ -import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; -import { FavoriteColorComponent } from './favorite-color.component'; import { createNewEvent } from '../../shared/utils'; +import { FavoriteColorComponent } from './favorite-color.component'; describe('FavoriteColorComponent', () => { let component: FavoriteColorComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ FormsModule ], - declarations: [ FavoriteColorComponent ] - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({imports: [FormsModule], declarations: [FavoriteColorComponent]}) + .compileComponents(); })); beforeEach(() => { @@ -28,29 +25,29 @@ describe('FavoriteColorComponent', () => { // #docregion model-to-view it('should update the favorite color on the input field', fakeAsync(() => { - component.favoriteColor = 'Blue'; + component.favoriteColor = 'Blue'; - fixture.detectChanges(); + fixture.detectChanges(); - tick(); + tick(); - const input = fixture.nativeElement.querySelector('input'); + const input = fixture.nativeElement.querySelector('input'); - expect(input.value).toBe('Blue'); - })); + expect(input.value).toBe('Blue'); + })); // #enddocregion model-to-view // #docregion view-to-model it('should update the favorite color in the component', fakeAsync(() => { - const input = fixture.nativeElement.querySelector('input'); - const event = createNewEvent('input'); + const input = fixture.nativeElement.querySelector('input'); + const event = createNewEvent('input'); - input.value = 'Red'; - input.dispatchEvent(event); + input.value = 'Red'; + input.dispatchEvent(event); - fixture.detectChanges(); + fixture.detectChanges(); - expect(component.favoriteColor).toEqual('Red'); - })); + expect(component.favoriteColor).toEqual('Red'); + })); // #enddocregion view-to-model }); diff --git a/aio/content/examples/schematics-for-libraries/projects/my-lib/src/lib/my-lib.component.spec.ts b/aio/content/examples/schematics-for-libraries/projects/my-lib/src/lib/my-lib.component.spec.ts index f3c07b177c..ca72b617a9 100644 --- a/aio/content/examples/schematics-for-libraries/projects/my-lib/src/lib/my-lib.component.spec.ts +++ b/aio/content/examples/schematics-for-libraries/projects/my-lib/src/lib/my-lib.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MyLibComponent } from './my-lib.component'; @@ -6,11 +6,8 @@ describe('MyLibComponent', () => { let component: MyLibComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ MyLibComponent ] - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({declarations: [MyLibComponent]}).compileComponents(); })); beforeEach(() => { diff --git a/aio/content/examples/setup/src/app/app.component.spec.ts b/aio/content/examples/setup/src/app/app.component.spec.ts index eebc33ed8a..36ded93437 100644 --- a/aio/content/examples/setup/src/app/app.component.spec.ts +++ b/aio/content/examples/setup/src/app/app.component.spec.ts @@ -1,19 +1,16 @@ -import { AppComponent } from './app.component'; - -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; describe('AppComponent', () => { let de: DebugElement; let comp: AppComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ AppComponent ] - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({declarations: [AppComponent]}).compileComponents(); })); beforeEach(() => { @@ -22,12 +19,11 @@ describe('AppComponent', () => { de = fixture.debugElement.query(By.css('h1')); }); - it('should create component', () => expect(comp).toBeDefined() ); + it('should create component', () => expect(comp).toBeDefined()); it('should have expected

text', () => { fixture.detectChanges(); const h1 = de.nativeElement; - expect(h1.textContent).toMatch(/angular/i, - '

should say something about "Angular"'); + expect(h1.textContent).toMatch(/angular/i, '

should say something about "Angular"'); }); }); diff --git a/aio/content/examples/testing/src/app/app-initial.component.spec.ts b/aio/content/examples/testing/src/app/app-initial.component.spec.ts index 1cd11c37f4..66e1858c94 100644 --- a/aio/content/examples/testing/src/app/app-initial.component.spec.ts +++ b/aio/content/examples/testing/src/app/app-initial.component.spec.ts @@ -1,6 +1,6 @@ // #docplaster // #docregion -import { TestBed, async } from '@angular/core/testing'; +import { TestBed, waitForAsync } from '@angular/core/testing'; // #enddocregion import { AppComponent } from './app-initial.component'; /* @@ -12,29 +12,29 @@ describe('AppComponent', () => { */ describe('AppComponent (initial CLI version)', () => { // #docregion - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - }).compileComponents(); - })); - it('should create the app', async(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - })); - it(`should have as title 'app'`, async(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('app'); - })); - it('should render title', async(() => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [AppComponent], + }) + .compileComponents(); })); + it('should create the app', waitForAsync(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + })); + it(`should have as title 'app'`, waitForAsync(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('app'); + })); + it('should render title', waitForAsync(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); + })); }); // #enddocregion @@ -43,16 +43,13 @@ import { DebugElement } from '@angular/core'; import { ComponentFixture } from '@angular/core/testing'; describe('AppComponent (initial CLI version - as it should be)', () => { - let app: AppComponent; let de: DebugElement; let fixture: ComponentFixture; beforeEach(() => { TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], + declarations: [AppComponent], }); fixture = TestBed.createComponent(AppComponent); @@ -70,7 +67,6 @@ describe('AppComponent (initial CLI version - as it should be)', () => { it('should render title in an h1 tag', () => { fixture.detectChanges(); - expect(de.nativeElement.querySelector('h1').textContent) - .toContain('Welcome to app!'); + expect(de.nativeElement.querySelector('h1').textContent).toContain('Welcome to app!'); }); }); diff --git a/aio/content/examples/testing/src/app/app.component.router.spec.ts b/aio/content/examples/testing/src/app/app.component.router.spec.ts index 3ae0fb1fd8..d094c0fef9 100644 --- a/aio/content/examples/testing/src/app/app.component.router.spec.ts +++ b/aio/content/examples/testing/src/app/app.component.router.spec.ts @@ -1,7 +1,7 @@ // For more examples: // https://github.com/angular/angular/blob/master/modules/@angular/router/test/integration.spec.ts -import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { waitForAsync, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { asyncData } from '../testing'; @@ -21,9 +21,9 @@ import { AppModule } from './app.module'; import { AppComponent } from './app.component'; import { AboutComponent } from './about/about.component'; import { DashboardComponent } from './dashboard/dashboard.component'; -import { TwainService } from './twain/twain.service'; import { HeroService, TestHeroService } from './model/testing/test-hero.service'; +import { TwainService } from './twain/twain.service'; let comp: AppComponent; let fixture: ComponentFixture; @@ -32,54 +32,51 @@ let router: Router; let location: SpyLocation; describe('AppComponent & RouterTestingModule', () => { - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - AppModule, - RouterTestingModule.withRoutes(routes), - ], - providers: [ - { provide: HeroService, useClass: TestHeroService } - ] - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + imports: [ + AppModule, + RouterTestingModule.withRoutes(routes), + ], + providers: [{provide: HeroService, useClass: TestHeroService}] + }) + .compileComponents(); })); it('should navigate to "Dashboard" immediately', fakeAsync(() => { - createComponent(); - tick(); // wait for async data to arrive - expectPathToBe('/dashboard', 'after initialNavigation()'); - expectElementOf(DashboardComponent); - })); + createComponent(); + tick(); // wait for async data to arrive + expectPathToBe('/dashboard', 'after initialNavigation()'); + expectElementOf(DashboardComponent); + })); it('should navigate to "About" on click', fakeAsync(() => { - createComponent(); - click(page.aboutLinkDe); - // page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom + createComponent(); + click(page.aboutLinkDe); + // page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom - advance(); - expectPathToBe('/about'); - expectElementOf(AboutComponent); - })); + advance(); + expectPathToBe('/about'); + expectElementOf(AboutComponent); + })); it('should navigate to "About" w/ browser location URL change', fakeAsync(() => { - createComponent(); - location.simulateHashChange('/about'); - // location.go('/about'); // also works ... except, perhaps, in Stackblitz - advance(); - expectPathToBe('/about'); - expectElementOf(AboutComponent); - })); + createComponent(); + location.simulateHashChange('/about'); + // location.go('/about'); // also works ... except, perhaps, in Stackblitz + advance(); + expectPathToBe('/about'); + expectElementOf(AboutComponent); + })); // Can't navigate to lazy loaded modules with this technique xit('should navigate to "Heroes" on click (not working yet)', fakeAsync(() => { - createComponent(); - page.heroesLinkDe.nativeElement.click(); - advance(); - expectPathToBe('/heroes'); - })); - + createComponent(); + page.heroesLinkDe.nativeElement.click(); + advance(); + expectPathToBe('/heroes'); + })); }); @@ -94,37 +91,37 @@ let loader: SpyNgModuleFactoryLoader; ///////// Can't get lazy loaded Heroes to work yet xdescribe('AppComponent & Lazy Loading (not working yet)', () => { - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - AppModule, - RouterTestingModule.withRoutes(routes), - ], - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + imports: [ + AppModule, + RouterTestingModule.withRoutes(routes), + ], + }) + .compileComponents(); })); beforeEach(fakeAsync(() => { createComponent(); loader = TestBed.inject(NgModuleFactoryLoader) as SpyNgModuleFactoryLoader; - loader.stubbedModules = { expected: HeroModule }; + loader.stubbedModules = {expected: HeroModule}; router.resetConfig([{path: 'heroes', loadChildren: 'expected'}]); })); - it('should navigate to "Heroes" on click', async(() => { - page.heroesLinkDe.nativeElement.click(); - advance(); - expectPathToBe('/heroes'); - expectElementOf(HeroListComponent); - })); + it('should navigate to "Heroes" on click', waitForAsync(() => { + page.heroesLinkDe.nativeElement.click(); + advance(); + expectPathToBe('/heroes'); + expectElementOf(HeroListComponent); + })); it('can navigate to "Heroes" w/ browser location URL change', fakeAsync(() => { - location.go('/heroes'); - advance(); - expectPathToBe('/heroes'); - expectElementOf(HeroListComponent); - })); + location.go('/heroes'); + advance(); + expectPathToBe('/heroes'); + expectElementOf(HeroListComponent); + })); }); ////// Helpers ///////// @@ -134,9 +131,9 @@ xdescribe('AppComponent & Lazy Loading (not working yet)', () => { * Wait a tick, then detect changes, and tick again */ function advance(): void { - tick(); // wait while navigating - fixture.detectChanges(); // update view - tick(); // wait for async data to arrive + tick(); // wait while navigating + fixture.detectChanges(); // update view + tick(); // wait for async data to arrive } function createComponent() { @@ -148,8 +145,8 @@ function createComponent() { router = injector.get(Router); router.initialNavigation(); spyOn(injector.get(TwainService), 'getQuote') - // fake fast async observable - .and.returnValue(asyncData('Test Quote')); + // fake fast async observable + .and.returnValue(asyncData('Test Quote')); advance(); page = new Page(); @@ -168,14 +165,14 @@ class Page { constructor() { const links = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); - this.aboutLinkDe = links[2]; + this.aboutLinkDe = links[2]; this.dashboardLinkDe = links[0]; - this.heroesLinkDe = links[1]; + this.heroesLinkDe = links[1]; // for debugging - this.comp = comp; + this.comp = comp; this.fixture = fixture; - this.router = router; + this.router = router; } } diff --git a/aio/content/examples/testing/src/app/app.component.spec.ts b/aio/content/examples/testing/src/app/app.component.spec.ts index e58dafcc12..2c0971b2c9 100644 --- a/aio/content/examples/testing/src/app/app.component.spec.ts +++ b/aio/content/examples/testing/src/app/app.component.spec.ts @@ -1,66 +1,70 @@ // #docplaster -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { AppComponent } from './app.component'; import { RouterLinkDirectiveStub } from '../testing'; +import { AppComponent } from './app.component'; + // #docregion component-stubs @Component({selector: 'app-banner', template: ''}) -class BannerStubComponent {} +class BannerStubComponent { +} @Component({selector: 'router-outlet', template: ''}) -class RouterOutletStubComponent { } +class RouterOutletStubComponent { +} @Component({selector: 'app-welcome', template: ''}) -class WelcomeStubComponent {} +class WelcomeStubComponent { +} // #enddocregion component-stubs let comp: AppComponent; let fixture: ComponentFixture; describe('AppComponent & TestModule', () => { - beforeEach(async(() => { + beforeEach(waitForAsync(() => { // #docregion testbed-stubs - TestBed.configureTestingModule({ - declarations: [ - AppComponent, - RouterLinkDirectiveStub, - BannerStubComponent, - RouterOutletStubComponent, - WelcomeStubComponent - ] - }) - // #enddocregion testbed-stubs - .compileComponents().then(() => { - fixture = TestBed.createComponent(AppComponent); - comp = fixture.componentInstance; - }); + TestBed + .configureTestingModule({ + declarations: [ + AppComponent, RouterLinkDirectiveStub, BannerStubComponent, RouterOutletStubComponent, + WelcomeStubComponent + ] + }) + // #enddocregion testbed-stubs + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + }); })); tests(); }); //////// Testing w/ NO_ERRORS_SCHEMA ////// describe('AppComponent & NO_ERRORS_SCHEMA', () => { - beforeEach(async(() => { + beforeEach(waitForAsync(() => { // #docregion no-errors-schema, mixed-setup - TestBed.configureTestingModule({ - declarations: [ - AppComponent, - // #enddocregion no-errors-schema - BannerStubComponent, - // #docregion no-errors-schema - RouterLinkDirectiveStub - ], - schemas: [ NO_ERRORS_SCHEMA ] - }) - // #enddocregion no-errors-schema, mixed-setup - .compileComponents().then(() => { - fixture = TestBed.createComponent(AppComponent); - comp = fixture.componentInstance; - }); + TestBed + .configureTestingModule({ + declarations: [ + AppComponent, + // #enddocregion no-errors-schema + BannerStubComponent, + // #docregion no-errors-schema + RouterLinkDirectiveStub + ], + schemas: [NO_ERRORS_SCHEMA] + }) + // #enddocregion no-errors-schema, mixed-setup + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + }); })); tests(); }); @@ -72,30 +76,23 @@ import { AppModule } from './app.module'; import { AppRoutingModule } from './app-routing.module'; describe('AppComponent & AppModule', () => { + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({imports: [AppModule]}) - beforeEach(async(() => { + // Get rid of app's Router configuration otherwise many failures. + // Doing so removes Router declarations; add the Router stubs + .overrideModule(AppModule, { + remove: {imports: [AppRoutingModule]}, + add: {declarations: [RouterLinkDirectiveStub, RouterOutletStubComponent]} + }) - TestBed.configureTestingModule({ - imports: [ AppModule ] - }) + .compileComponents() - // Get rid of app's Router configuration otherwise many failures. - // Doing so removes Router declarations; add the Router stubs - .overrideModule(AppModule, { - remove: { - imports: [ AppRoutingModule ] - }, - add: { - declarations: [ RouterLinkDirectiveStub, RouterOutletStubComponent ] - } - }) - - .compileComponents() - - .then(() => { - fixture = TestBed.createComponent(AppComponent); - comp = fixture.componentInstance; - }); + .then(() => { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + }); })); tests(); @@ -107,11 +104,10 @@ function tests() { // #docregion test-setup beforeEach(() => { - fixture.detectChanges(); // trigger initial data binding + fixture.detectChanges(); // trigger initial data binding // find DebugElements with an attached RouterLinkStubDirective - linkDes = fixture.debugElement - .queryAll(By.directive(RouterLinkDirectiveStub)); + linkDes = fixture.debugElement.queryAll(By.directive(RouterLinkDirectiveStub)); // get attached link directive instances // using each DebugElement's injector @@ -132,8 +128,8 @@ function tests() { }); it('can click Heroes link in template', () => { - const heroesLinkDe = linkDes[1]; // heroes link DebugElement - const heroesLink = routerLinks[1]; // heroes link directive + const heroesLinkDe = linkDes[1]; // heroes link DebugElement + const heroesLink = routerLinks[1]; // heroes link directive expect(heroesLink.navigatedTo).toBeNull('should not have navigated yet'); diff --git a/aio/content/examples/testing/src/app/banner/banner-external.component.spec.ts b/aio/content/examples/testing/src/app/banner/banner-external.component.spec.ts index bee1cfc2b6..b52039a059 100644 --- a/aio/content/examples/testing/src/app/banner/banner-external.component.spec.ts +++ b/aio/content/examples/testing/src/app/banner/banner-external.component.spec.ts @@ -1,6 +1,6 @@ // #docplaster // #docregion import-async -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; // #enddocregion import-async import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; @@ -14,11 +14,12 @@ describe('BannerComponent (external files)', () => { describe('Two beforeEach', () => { // #docregion async-before-each - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ BannerComponent ], - }) - .compileComponents(); // compile template and css + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [BannerComponent], + }) + .compileComponents(); // compile template and css })); // #enddocregion async-before-each @@ -26,7 +27,7 @@ describe('BannerComponent (external files)', () => { // #docregion sync-before-each beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); - component = fixture.componentInstance; // BannerComponent test instance + component = fixture.componentInstance; // BannerComponent test instance h1 = fixture.nativeElement.querySelector('h1'); }); // #enddocregion sync-before-each @@ -36,16 +37,17 @@ describe('BannerComponent (external files)', () => { describe('One beforeEach', () => { // #docregion one-before-each - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ BannerComponent ], - }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(BannerComponent); - component = fixture.componentInstance; - h1 = fixture.nativeElement.querySelector('h1'); - }); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [BannerComponent], + }) + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(BannerComponent); + component = fixture.componentInstance; + h1 = fixture.nativeElement.querySelector('h1'); + }); })); // #enddocregion one-before-each @@ -69,4 +71,3 @@ describe('BannerComponent (external files)', () => { }); } }); - diff --git a/aio/content/examples/testing/src/app/banner/banner-initial.component.spec.ts b/aio/content/examples/testing/src/app/banner/banner-initial.component.spec.ts index a9c4d60472..8e52947d4e 100644 --- a/aio/content/examples/testing/src/app/banner/banner-initial.component.spec.ts +++ b/aio/content/examples/testing/src/app/banner/banner-initial.component.spec.ts @@ -1,14 +1,16 @@ // #docplaster // #docregion import-by -import { By } from '@angular/platform-browser'; // #enddocregion import-by // #docregion import-debug-element import { DebugElement } from '@angular/core'; // #enddocregion import-debug-element // #docregion v1 -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + // #enddocregion v1 import { BannerComponent } from './banner-initial.component'; + /* // #docregion v1 import { BannerComponent } from './banner.component'; @@ -17,15 +19,12 @@ describe('BannerComponent', () => { // #enddocregion v1 */ describe('BannerComponent (initial CLI generated)', () => { -// #docregion v1 + // #docregion v1 let component: BannerComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ BannerComponent ] - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({declarations: [BannerComponent]}).compileComponents(); })); beforeEach(() => { @@ -44,9 +43,7 @@ describe('BannerComponent (initial CLI generated)', () => { describe('BannerComponent (minimal)', () => { it('should create', () => { // #docregion configureTestingModule - TestBed.configureTestingModule({ - declarations: [ BannerComponent ] - }); + TestBed.configureTestingModule({declarations: [BannerComponent]}); // #enddocregion configureTestingModule // #docregion createComponent const fixture = TestBed.createComponent(BannerComponent); @@ -65,9 +62,7 @@ describe('BannerComponent (with beforeEach)', () => { let fixture: ComponentFixture; beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ BannerComponent ] - }); + TestBed.configureTestingModule({declarations: [BannerComponent]}); fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; }); diff --git a/aio/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts b/aio/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts index ca6eb028c1..9eae3ede14 100644 --- a/aio/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts +++ b/aio/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts @@ -1,22 +1,21 @@ // #docplaster -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { addMatchers, click } from '../../testing'; - import { Hero } from '../model/hero'; + import { DashboardHeroComponent } from './dashboard-hero.component'; -beforeEach( addMatchers ); +beforeEach(addMatchers); describe('DashboardHeroComponent class only', () => { // #docregion class-only it('raises the selected event when clicked', () => { const comp = new DashboardHeroComponent(); - const hero: Hero = { id: 42, name: 'Test' }; + const hero: Hero = {id: 42, name: 'Test'}; comp.hero = hero; comp.selected.subscribe((selectedHero: Hero) => expect(selectedHero).toBe(hero)); @@ -26,33 +25,31 @@ describe('DashboardHeroComponent class only', () => { }); describe('DashboardHeroComponent when tested directly', () => { - let comp: DashboardHeroComponent; let expectedHero: Hero; let fixture: ComponentFixture; let heroDe: DebugElement; let heroEl: HTMLElement; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { // #docregion setup, config-testbed - TestBed.configureTestingModule({ - declarations: [ DashboardHeroComponent ] - }) - // #enddocregion setup, config-testbed - .compileComponents(); + TestBed + .configureTestingModule({declarations: [DashboardHeroComponent]}) + // #enddocregion setup, config-testbed + .compileComponents(); })); beforeEach(() => { // #docregion setup fixture = TestBed.createComponent(DashboardHeroComponent); - comp = fixture.componentInstance; + comp = fixture.componentInstance; // find the hero's DebugElement and element - heroDe = fixture.debugElement.query(By.css('.hero')); + heroDe = fixture.debugElement.query(By.css('.hero')); heroEl = heroDe.nativeElement; // mock the hero supplied by the parent component - expectedHero = { id: 42, name: 'Test Name' }; + expectedHero = {id: 42, name: 'Test Name'}; // simulate the parent setting the input property with that hero comp.hero = expectedHero; @@ -96,8 +93,8 @@ describe('DashboardHeroComponent when tested directly', () => { let selectedHero: Hero; comp.selected.subscribe((hero: Hero) => selectedHero = hero); - click(heroDe); // click helper with DebugElement - click(heroEl); // click helper with native element + click(heroDe); // click helper with DebugElement + click(heroEl); // click helper with native element expect(selectedHero).toBe(expectedHero); }); @@ -111,22 +108,21 @@ describe('DashboardHeroComponent when inside a test host', () => { let fixture: ComponentFixture; let heroEl: HTMLElement; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { // #docregion test-host-setup - TestBed.configureTestingModule({ - declarations: [ DashboardHeroComponent, TestHostComponent ] - }) - // #enddocregion test-host-setup - .compileComponents(); + TestBed + .configureTestingModule({declarations: [DashboardHeroComponent, TestHostComponent]}) + // #enddocregion test-host-setup + .compileComponents(); })); beforeEach(() => { // #docregion test-host-setup // create TestHostComponent instead of DashboardHeroComponent - fixture = TestBed.createComponent(TestHostComponent); + fixture = TestBed.createComponent(TestHostComponent); testHost = fixture.componentInstance; - heroEl = fixture.nativeElement.querySelector('.hero'); - fixture.detectChanges(); // trigger initial data binding + heroEl = fixture.nativeElement.querySelector('.hero'); + fixture.detectChanges(); // trigger initial data binding // #enddocregion test-host-setup }); @@ -155,8 +151,10 @@ import { Component } from '@angular/core'; ` }) class TestHostComponent { - hero: Hero = {id: 42, name: 'Test Name' }; + hero: Hero = {id: 42, name: 'Test Name'}; selectedHero: Hero; - onSelected(hero: Hero) { this.selectedHero = hero; } + onSelected(hero: Hero) { + this.selectedHero = hero; + } } // #enddocregion test-host diff --git a/aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts b/aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts index e60e4cc313..0714a463b9 100644 --- a/aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts +++ b/aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts @@ -1,6 +1,5 @@ // #docplaster -import { async, inject, ComponentFixture, TestBed -} from '@angular/core/testing'; +import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; import { addMatchers, asyncData, click } from '../../testing'; import { HeroService } from '../model/hero.service'; @@ -12,7 +11,7 @@ import { Router } from '@angular/router'; import { DashboardComponent } from './dashboard.component'; import { DashboardModule } from './dashboard.module'; -beforeEach ( addMatchers ); +beforeEach(addMatchers); let comp: DashboardComponent; let fixture: ComponentFixture; @@ -21,9 +20,7 @@ let fixture: ComponentFixture; describe('DashboardComponent (deep)', () => { beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ DashboardModule ] - }); + TestBed.configureTestingModule({imports: [DashboardModule]}); }); compileAndCreate(); @@ -43,10 +40,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('DashboardComponent (shallow)', () => { beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ DashboardComponent ], - schemas: [NO_ERRORS_SCHEMA] - }); + TestBed.configureTestingModule( + {declarations: [DashboardComponent], schemas: [NO_ERRORS_SCHEMA]}); }); compileAndCreate(); @@ -63,25 +58,26 @@ describe('DashboardComponent (shallow)', () => { /** Add TestBed providers, compile, and create DashboardComponent */ function compileAndCreate() { // #docregion compile-and-create-body - beforeEach(async(() => { + beforeEach(waitForAsync(() => { // #docregion router-spy const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']); const heroServiceSpy = jasmine.createSpyObj('HeroService', ['getHeroes']); - TestBed.configureTestingModule({ - providers: [ - { provide: HeroService, useValue: heroServiceSpy }, - { provide: Router, useValue: routerSpy } - ] - }) - // #enddocregion router-spy - .compileComponents().then(() => { - fixture = TestBed.createComponent(DashboardComponent); - comp = fixture.componentInstance; + TestBed + .configureTestingModule({ + providers: [ + {provide: HeroService, useValue: heroServiceSpy}, {provide: Router, useValue: routerSpy} + ] + }) + // #enddocregion router-spy + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(DashboardComponent); + comp = fixture.componentInstance; - // getHeroes spy returns observable of test heroes - heroServiceSpy.getHeroes.and.returnValue(asyncData(getTestHeroes())); - }); + // getHeroes spy returns observable of test heroes + heroServiceSpy.getHeroes.and.returnValue(asyncData(getTestHeroes())); + }); // #enddocregion compile-and-create-body })); } @@ -93,23 +89,20 @@ function compileAndCreate() { function tests(heroClick: () => void) { it('should NOT have heroes before ngOnInit', () => { - expect(comp.heroes.length).toBe(0, - 'should not have heroes before ngOnInit'); + expect(comp.heroes.length).toBe(0, 'should not have heroes before ngOnInit'); }); it('should NOT have heroes immediately after ngOnInit', () => { - fixture.detectChanges(); // runs initial lifecycle hooks + fixture.detectChanges(); // runs initial lifecycle hooks - expect(comp.heroes.length).toBe(0, - 'should not have heroes until service promise resolves'); + expect(comp.heroes.length).toBe(0, 'should not have heroes until service promise resolves'); }); describe('after get dashboard heroes', () => { - let router: Router; // Trigger component so it gets heroes and binds to them - beforeEach(async(() => { + beforeEach(waitForAsync(() => { router = fixture.debugElement.injector.get(Router); fixture.detectChanges(); // runs ngOnInit -> getHeroes fixture.whenStable() // No need for the `lastPromise` hack! @@ -117,8 +110,8 @@ function tests(heroClick: () => void) { })); it('should HAVE heroes', () => { - expect(comp.heroes.length).toBeGreaterThan(0, - 'should have heroes after service promise resolves'); + expect(comp.heroes.length) + .toBeGreaterThan(0, 'should have heroes after service promise resolves'); }); it('should DISPLAY heroes', () => { @@ -130,8 +123,7 @@ function tests(heroClick: () => void) { // #docregion navigate-test it('should tell ROUTER to navigate when hero clicked', () => { - - heroClick(); // trigger click on first inner
+ heroClick(); // trigger click on first inner
// args passed to router.navigateByUrl() spy const spy = router.navigateByUrl as jasmine.Spy; @@ -139,10 +131,8 @@ function tests(heroClick: () => void) { // expecting to navigate to id of the component's first hero const id = comp.heroes[0].id; - expect(navArgs).toBe('/heroes/' + id, - 'should nav to HeroDetail for first hero'); + expect(navArgs).toBe('/heroes/' + id, 'should nav to HeroDetail for first hero'); }); // #enddocregion navigate-test }); } - diff --git a/aio/content/examples/testing/src/app/demo/async-helper.spec.ts b/aio/content/examples/testing/src/app/demo/async-helper.spec.ts index c96954d042..18a793d1a6 100644 --- a/aio/content/examples/testing/src/app/demo/async-helper.spec.ts +++ b/aio/content/examples/testing/src/app/demo/async-helper.spec.ts @@ -1,18 +1,23 @@ // tslint:disable-next-line:no-unused-variable -import { async, fakeAsync, tick } from '@angular/core/testing'; +import { fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { interval, of } from 'rxjs'; import { delay, take } from 'rxjs/operators'; describe('Angular async helper', () => { - describe('async', () => { let actuallyDone = false; - beforeEach(() => { actuallyDone = false; }); + beforeEach(() => { + actuallyDone = false; + }); - afterEach(() => { expect(actuallyDone).toBe(true, 'actuallyDone should be true'); }); + afterEach(() => { + expect(actuallyDone).toBe(true, 'actuallyDone should be true'); + }); - it('should run normal test', () => { actuallyDone = true; }); + it('should run normal test', () => { + actuallyDone = true; + }); it('should run normal async test', (done: DoneFn) => { setTimeout(() => { @@ -21,39 +26,50 @@ describe('Angular async helper', () => { }, 0); }); - it('should run async test with task', - async(() => { setTimeout(() => { actuallyDone = true; }, 0); })); + it('should run async test with task', waitForAsync(() => { + setTimeout(() => { + actuallyDone = true; + }, 0); + })); - it('should run async test with task', async(() => { + it('should run async test with task', waitForAsync(() => { const id = setInterval(() => { actuallyDone = true; clearInterval(id); }, 100); })); - it('should run async test with successful promise', async(() => { - const p = new Promise(resolve => { setTimeout(resolve, 10); }); - p.then(() => { actuallyDone = true; }); + it('should run async test with successful promise', waitForAsync(() => { + const p = new Promise(resolve => { + setTimeout(resolve, 10); + }); + p.then(() => { + actuallyDone = true; + }); })); - it('should run async test with failed promise', async(() => { - const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); - p.catch(() => { actuallyDone = true; }); + it('should run async test with failed promise', waitForAsync(() => { + const p = new Promise((resolve, reject) => { + setTimeout(reject, 10); + }); + p.catch(() => { + actuallyDone = true; + }); })); // Use done. Can also use async or fakeAsync. it('should run async test with successful delayed Observable', (done: DoneFn) => { - const source = of (true).pipe(delay(10)); + const source = of(true).pipe(delay(10)); source.subscribe(val => actuallyDone = true, err => fail(err), done); }); - it('should run async test with successful delayed Observable', async(() => { - const source = of (true).pipe(delay(10)); + it('should run async test with successful delayed Observable', waitForAsync(() => { + const source = of(true).pipe(delay(10)); source.subscribe(val => actuallyDone = true, err => fail(err)); })); it('should run async test with successful delayed Observable', fakeAsync(() => { - const source = of (true).pipe(delay(10)); + const source = of(true).pipe(delay(10)); source.subscribe(val => actuallyDone = true, err => fail(err)); tick(10); @@ -64,7 +80,9 @@ describe('Angular async helper', () => { // #docregion fake-async-test-tick it('should run timeout callback with delay after call tick with millis', fakeAsync(() => { let called = false; - setTimeout(() => { called = true; }, 100); + setTimeout(() => { + called = true; + }, 100); tick(100); expect(called).toBe(true); })); @@ -73,7 +91,9 @@ describe('Angular async helper', () => { // #docregion fake-async-test-tick-new-macro-task-sync it('should run new macro task callback with delay after call tick with millis', fakeAsync(() => { - function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } + function nestedTimer(cb: () => any): void { + setTimeout(() => setTimeout(() => cb())); + } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); @@ -86,7 +106,9 @@ describe('Angular async helper', () => { // #docregion fake-async-test-tick-new-macro-task-async it('should not run new macro task callback with delay after call tick with millis', fakeAsync(() => { - function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); } + function nestedTimer(cb: () => any): void { + setTimeout(() => setTimeout(() => cb())); + } const callback = jasmine.createSpy('callback'); nestedTimer(callback); expect(callback).not.toHaveBeenCalled(); @@ -112,7 +134,9 @@ describe('Angular async helper', () => { // need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async' // to patch rxjs scheduler let result = null; - of ('hello').pipe(delay(1000)).subscribe(v => { result = v; }); + of('hello').pipe(delay(1000)).subscribe(v => { + result = v; + }); expect(result).toBeNull(); tick(1000); expect(result).toBe('hello'); @@ -133,12 +157,18 @@ describe('Angular async helper', () => { describe('use jasmine.clock()', () => { // need to config __zone_symbol__fakeAsyncPatchLock flag // before loading zone.js/dist/zone-testing - beforeEach(() => { jasmine.clock().install(); }); - afterEach(() => { jasmine.clock().uninstall(); }); + beforeEach(() => { + jasmine.clock().install(); + }); + afterEach(() => { + jasmine.clock().uninstall(); + }); it('should auto enter fakeAsync', () => { // is in fakeAsync now, don't need to call fakeAsync(testFn) let called = false; - setTimeout(() => { called = true; }, 100); + setTimeout(() => { + called = true; + }, 100); jasmine.clock().tick(100); expect(called).toBe(true); }); @@ -152,7 +182,7 @@ describe('Angular async helper', () => { } // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag // before loading zone.js/dist/zone-testing - it('should wait until promise.then is called', async(() => { + it('should wait until promise.then is called', waitForAsync(() => { let finished = false; new Promise((res, rej) => { jsonp('localhost:8080/jsonp', () => { @@ -168,5 +198,4 @@ describe('Angular async helper', () => { })); }); // #enddocregion async-test-promise-then - }); diff --git a/aio/content/examples/testing/src/app/demo/demo.testbed.spec.ts b/aio/content/examples/testing/src/app/demo/demo.testbed.spec.ts index cb5b20ea6a..a6bbd1aafc 100644 --- a/aio/content/examples/testing/src/app/demo/demo.testbed.spec.ts +++ b/aio/content/examples/testing/src/app/demo/demo.testbed.spec.ts @@ -24,17 +24,18 @@ import { FormsModule } from '@angular/forms'; // Forms symbols imported only for a specific test below import { NgModel, NgControl } from '@angular/forms'; -import { async, ComponentFixture, fakeAsync, inject, TestBed, tick +import { + ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { addMatchers, newEvent, click } from '../../testing'; -export class NotProvided extends ValueService { /* example below */} -beforeEach( addMatchers ); +export class NotProvided extends ValueService { /* example below */ } +beforeEach(addMatchers); describe('demo (with TestBed):', () => { -//////// Service Tests ///////////// + //////// Service Tests ///////////// // #docregion ValueService describe('ValueService', () => { @@ -64,13 +65,13 @@ describe('demo (with TestBed):', () => { // #enddocregion testbed-get-w-null }); - it('test should wait for ValueService.getPromiseValue', async(() => { + it('test should wait for ValueService.getPromiseValue', waitForAsync(() => { service.getPromiseValue().then( value => expect(value).toBe('promise value') ); })); - it('test should wait for ValueService.getObservableValue', async(() => { + it('test should wait for ValueService.getObservableValue', waitForAsync(() => { service.getObservableValue().subscribe( value => expect(value).toBe('observable value') ); @@ -150,7 +151,7 @@ describe('demo (with TestBed):', () => { TestBed.configureTestingModule({ providers: [ValueService] }); }); - beforeEach(async(inject([ValueService], (service: ValueService) => { + beforeEach(waitForAsync(inject([ValueService], (service: ValueService) => { service.getPromiseValue().then(value => serviceValue = value); }))); @@ -159,11 +160,11 @@ describe('demo (with TestBed):', () => { }); }); -/////////// Component Tests ////////////////// + /////////// Component Tests ////////////////// describe('TestBed component tests', () => { - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed .configureTestingModule({ imports: [DemoModule], @@ -235,7 +236,7 @@ describe('demo (with TestBed):', () => { // #docregion ButtonComp it('should support clicking a button', () => { const fixture = TestBed.createComponent(LightswitchComponent); - const btn = fixture.debugElement.query(By.css('button')); + const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); @@ -248,7 +249,7 @@ describe('demo (with TestBed):', () => { // #enddocregion ButtonComp // ngModel is async so we must wait for it with promise-based `whenStable` - it('should support entering text in input box (ngModel)', async(() => { + it('should support entering text in input box (ngModel)', waitForAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; @@ -278,10 +279,10 @@ describe('demo (with TestBed):', () => { input.dispatchEvent(newEvent('input')); return fixture.whenStable(); }) - .then(() => { - expect(comp.name).toBe(expectedNewName, - `After ngModel updates the model, comp.name should be ${expectedNewName} `); - }); + .then(() => { + expect(comp.name).toBe(expectedNewName, + `After ngModel updates the model, comp.name should be ${expectedNewName} `); + }); })); // fakeAsync version of ngModel input test enables sync test style @@ -327,9 +328,9 @@ describe('demo (with TestBed):', () => { const fixture = TestBed.createComponent(ReversePipeComponent); fixture.detectChanges(); - const comp = fixture.componentInstance; + const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; - const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; + const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; // simulate user entering new name in input input.value = inputText; @@ -381,12 +382,12 @@ describe('demo (with TestBed):', () => { expect(el.styles.color).toBe(comp.color, 'color style'); expect(el.styles.width).toBe(comp.width + 'px', 'width style'); - // #enddocregion dom-attributes + // #enddocregion dom-attributes // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // expect(el.properties['customProperty']).toBe(true, 'customProperty'); - // #docregion dom-attributes + // #docregion dom-attributes }); // #enddocregion dom-attributes @@ -400,10 +401,10 @@ describe('demo (with TestBed):', () => { const fixture = TestBed.configureTestingModule({ declarations: [Child1Component], }) - .overrideComponent(Child1Component, { - set: { template: 'Fake' } - }) - .createComponent(Child1Component); + .overrideComponent(Child1Component, { + set: { template: 'Fake' } + }) + .createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Fake'); @@ -413,14 +414,14 @@ describe('demo (with TestBed):', () => { const fixture = TestBed.configureTestingModule({ declarations: [TestProvidersComponent], }) - .overrideComponent(TestProvidersComponent, { - remove: { providers: [ValueService]}, - add: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, + .overrideComponent(TestProvidersComponent, { + remove: { providers: [ValueService] }, + add: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, - // Or replace them all (this component has only one provider) - // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, - }) - .createComponent(TestProvidersComponent); + // Or replace them all (this component has only one provider) + // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, + }) + .createComponent(TestProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value', 'text'); @@ -436,14 +437,14 @@ describe('demo (with TestBed):', () => { const fixture = TestBed.configureTestingModule({ declarations: [TestViewProvidersComponent], }) - .overrideComponent(TestViewProvidersComponent, { - // remove: { viewProviders: [ValueService]}, - // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] }, + .overrideComponent(TestViewProvidersComponent, { + // remove: { viewProviders: [ValueService]}, + // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] }, - // Or replace them all (this component has only one viewProvider) - set: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] }, - }) - .createComponent(TestViewProvidersComponent); + // Or replace them all (this component has only one viewProvider) + set: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] }, + }) + .createComponent(TestViewProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value'); @@ -453,20 +454,20 @@ describe('demo (with TestBed):', () => { // TestComponent is parent of TestProvidersComponent @Component({ template: '' }) - class TestComponent {} + class TestComponent { } // 3 levels of ValueService provider: module, TestCompomponent, TestProvidersComponent const fixture = TestBed.configureTestingModule({ declarations: [TestComponent, TestProvidersComponent], - providers: [ValueService] + providers: [ValueService] }) - .overrideComponent(TestComponent, { - set: { providers: [{ provide: ValueService, useValue: {} }] } - }) - .overrideComponent(TestProvidersComponent, { - set: { providers: [{ provide: ValueService, useClass: FakeValueService }] } - }) - .createComponent(TestComponent); + .overrideComponent(TestComponent, { + set: { providers: [{ provide: ValueService, useValue: {} }] } + }) + .overrideComponent(TestProvidersComponent, { + set: { providers: [{ provide: ValueService, useClass: FakeValueService }] } + }) + .createComponent(TestComponent); let testBedProvider: ValueService; let tcProvider: ValueService; @@ -489,10 +490,10 @@ describe('demo (with TestBed):', () => { const fixture = TestBed.configureTestingModule({ declarations: [ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component], }) - .overrideComponent(ShellComponent, { - set: { - selector: 'test-shell', - template: ` + .overrideComponent(ShellComponent, { + set: { + selector: 'test-shell', + template: ` @@ -501,9 +502,9 @@ describe('demo (with TestBed):', () => {
!
` - } - }) - .createComponent(ShellComponent); + } + }) + .createComponent(ShellComponent); fixture.detectChanges(); @@ -615,7 +616,7 @@ describe('demo (with TestBed):', () => { }); // must be async test to see child flow to parent - it('changed child value flows to parent', async(() => { + it('changed child value flows to parent', waitForAsync(() => { fixture.detectChanges(); getChild(); @@ -625,14 +626,14 @@ describe('demo (with TestBed):', () => { // Wait one JS engine turn! setTimeout(() => resolve(), 0); }) - .then(() => { - fixture.detectChanges(); + .then(() => { + fixture.detectChanges(); - expect(child.ngOnChangesCounter).toBe(2, - 'expected 2 changes: initial value and changed value'); - expect(parent.parentValue).toBe('bar', - 'parentValue should eq changed parent value'); - }); + expect(child.ngOnChangesCounter).toBe(2, + 'expected 2 changes: initial value and changed value'); + expect(parent.parentValue).toBe('bar', + 'parentValue should eq changed parent value'); + }); })); diff --git a/aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts b/aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts index 9fc287d137..0015c96d52 100644 --- a/aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts +++ b/aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts @@ -1,8 +1,5 @@ // #docplaster -import { - async, ComponentFixture, fakeAsync, inject, TestBed, tick -} from '@angular/core/testing'; - +import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { Router } from '@angular/router'; import { @@ -36,59 +33,54 @@ describe('HeroDetailComponent', () => { function overrideSetup() { // #docregion hds-spy class HeroDetailServiceSpy { - testHero: Hero = {id: 42, name: 'Test Hero' }; + testHero: Hero = {id: 42, name: 'Test Hero'}; /* emit cloned test hero */ getHero = jasmine.createSpy('getHero').and.callFake( - () => asyncData(Object.assign({}, this.testHero)) - ); + () => asyncData(Object.assign({}, this.testHero))); /* emit clone of test hero, with changes merged in */ - saveHero = jasmine.createSpy('saveHero').and.callFake( - (hero: Hero) => asyncData(Object.assign(this.testHero, hero)) - ); + saveHero = jasmine.createSpy('saveHero') + .and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero))); } // #enddocregion hds-spy // the `id` value is irrelevant because ignored by service stub - beforeEach(() => activatedRoute.setParamMap({ id: 99999 })); + beforeEach(() => activatedRoute.setParamMap({id: 99999})); // #docregion setup-override - beforeEach(async(() => { + beforeEach(waitForAsync(() => { const routerSpy = createRouterSpy(); - TestBed.configureTestingModule({ - imports: [ HeroModule ], - providers: [ - { provide: ActivatedRoute, useValue: activatedRoute }, - { provide: Router, useValue: routerSpy}, + TestBed + .configureTestingModule({ + imports: [HeroModule], + providers: [ + {provide: ActivatedRoute, useValue: activatedRoute}, + {provide: Router, useValue: routerSpy}, // #enddocregion setup-override - // HeroDetailService at this level is IRRELEVANT! - { provide: HeroDetailService, useValue: {} } + // HeroDetailService at this level is IRRELEVANT! + {provide: HeroDetailService, useValue: {}} // #docregion setup-override - ] - }) + ] + }) - // Override component's own provider - // #docregion override-component-method - .overrideComponent(HeroDetailComponent, { - set: { - providers: [ - { provide: HeroDetailService, useClass: HeroDetailServiceSpy } - ] - } - }) - // #enddocregion override-component-method + // Override component's own provider + // #docregion override-component-method + .overrideComponent( + HeroDetailComponent, + {set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}}) + // #enddocregion override-component-method - .compileComponents(); + .compileComponents(); })); // #enddocregion setup-override // #docregion override-tests let hdsSpy: HeroDetailServiceSpy; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { createComponent(); // get the component's injected HeroDetailServiceSpy hdsSpy = fixture.debugElement.injector.get(HeroDetailService) as any; @@ -103,33 +95,32 @@ function overrideSetup() { }); it('should save stub hero change', fakeAsync(() => { - const origName = hdsSpy.testHero.name; - const newName = 'New Name'; + const origName = hdsSpy.testHero.name; + const newName = 'New Name'; - page.nameInput.value = newName; - page.nameInput.dispatchEvent(newEvent('input')); // tell Angular + page.nameInput.value = newName; + page.nameInput.dispatchEvent(newEvent('input')); // tell Angular - expect(component.hero.name).toBe(newName, 'component hero has new name'); - expect(hdsSpy.testHero.name).toBe(origName, 'service hero unchanged before save'); + expect(component.hero.name).toBe(newName, 'component hero has new name'); + expect(hdsSpy.testHero.name).toBe(origName, 'service hero unchanged before save'); - click(page.saveBtn); - expect(hdsSpy.saveHero.calls.count()).toBe(1, 'saveHero called once'); + click(page.saveBtn); + expect(hdsSpy.saveHero.calls.count()).toBe(1, 'saveHero called once'); - tick(); // wait for async save to complete - expect(hdsSpy.testHero.name).toBe(newName, 'service hero has new name after save'); - expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called'); - })); + tick(); // wait for async save to complete + expect(hdsSpy.testHero.name).toBe(newName, 'service hero has new name after save'); + expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called'); + })); // #enddocregion override-tests it('fixture injected service is not the component injected service', - // inject gets the service from the fixture - inject([HeroDetailService], (fixtureService: HeroDetailService) => { + // inject gets the service from the fixture + inject([HeroDetailService], (fixtureService: HeroDetailService) => { + // use `fixture.debugElement.injector` to get service from component + const componentService = fixture.debugElement.injector.get(HeroDetailService); - // use `fixture.debugElement.injector` to get service from component - const componentService = fixture.debugElement.injector.get(HeroDetailService); - - expect(fixtureService).not.toBe(componentService, 'service injected from fixture'); - })); + expect(fixtureService).not.toBe(componentService, 'service injected from fixture'); + })); } //////////////////// @@ -139,21 +130,22 @@ const firstHero = getTestHeroes()[0]; function heroModuleSetup() { // #docregion setup-hero-module - beforeEach(async(() => { + beforeEach(waitForAsync(() => { const routerSpy = createRouterSpy(); - TestBed.configureTestingModule({ - imports: [ HeroModule ], - // #enddocregion setup-hero-module - // declarations: [ HeroDetailComponent ], // NO! DOUBLE DECLARATION - // #docregion setup-hero-module - providers: [ - { provide: ActivatedRoute, useValue: activatedRoute }, - { provide: HeroService, useClass: TestHeroService }, - { provide: Router, useValue: routerSpy}, - ] - }) - .compileComponents(); + TestBed + .configureTestingModule({ + imports: [HeroModule], + // #enddocregion setup-hero-module + // declarations: [ HeroDetailComponent ], // NO! DOUBLE DECLARATION + // #docregion setup-hero-module + providers: [ + {provide: ActivatedRoute, useValue: activatedRoute}, + {provide: HeroService, useClass: TestHeroService}, + {provide: Router, useValue: routerSpy}, + ] + }) + .compileComponents(); })); // #enddocregion setup-hero-module @@ -161,17 +153,17 @@ function heroModuleSetup() { describe('when navigate to existing hero', () => { let expectedHero: Hero; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { expectedHero = firstHero; - activatedRoute.setParamMap({ id: expectedHero.id }); + activatedRoute.setParamMap({id: expectedHero.id}); createComponent(); })); - // #docregion selected-tests + // #docregion selected-tests it('should display that hero\'s name', () => { expect(page.nameDisplay.textContent).toBe(expectedHero.name); }); - // #enddocregion route-good-id + // #enddocregion route-good-id it('should navigate when click cancel', () => { click(page.cancelBtn); @@ -190,10 +182,10 @@ function heroModuleSetup() { }); it('should navigate when click save and save resolves', fakeAsync(() => { - click(page.saveBtn); - tick(); // wait for async save to complete - expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called'); - })); + click(page.saveBtn); + tick(); // wait for async save to complete + expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called'); + })); // #docregion title-case-pipe it('should convert hero name to Title Case', () => { @@ -215,14 +207,14 @@ function heroModuleSetup() { expect(nameDisplay.textContent).toBe('Quick Brown Fox'); }); // #enddocregion title-case-pipe - // #enddocregion selected-tests - // #docregion route-good-id + // #enddocregion selected-tests + // #docregion route-good-id }); // #enddocregion route-good-id // #docregion route-no-id describe('when navigate with no hero id', () => { - beforeEach(async( createComponent )); + beforeEach(waitForAsync(createComponent)); it('should have hero.id === 0', () => { expect(component.hero.id).toBe(0); @@ -236,8 +228,8 @@ function heroModuleSetup() { // #docregion route-bad-id describe('when navigate to non-existent hero id', () => { - beforeEach(async(() => { - activatedRoute.setParamMap({ id: 99999 }); + beforeEach(waitForAsync(() => { + activatedRoute.setParamMap({id: 99999}); createComponent(); })); @@ -253,11 +245,10 @@ function heroModuleSetup() { let service: HeroDetailService; fixture = TestBed.createComponent(HeroDetailComponent); expect( - // Throws because `inject` only has access to TestBed's injector - // which is an ancestor of the component's injector - inject([HeroDetailService], (hds: HeroDetailService) => service = hds ) - ) - .toThrowError(/No provider for HeroDetailService/); + // Throws because `inject` only has access to TestBed's injector + // which is an ancestor of the component's injector + inject([HeroDetailService], (hds: HeroDetailService) => service = hds)) + .toThrowError(/No provider for HeroDetailService/); // get `HeroDetailService` with component's own injector service = fixture.debugElement.injector.get(HeroDetailService); @@ -270,30 +261,31 @@ import { FormsModule } from '@angular/forms'; import { TitleCasePipe } from '../shared/title-case.pipe'; function formsModuleSetup() { - // #docregion setup-forms-module - beforeEach(async(() => { + // #docregion setup-forms-module + beforeEach(waitForAsync(() => { const routerSpy = createRouterSpy(); - TestBed.configureTestingModule({ - imports: [ FormsModule ], - declarations: [ HeroDetailComponent, TitleCasePipe ], - providers: [ - { provide: ActivatedRoute, useValue: activatedRoute }, - { provide: HeroService, useClass: TestHeroService }, - { provide: Router, useValue: routerSpy}, - ] - }) - .compileComponents(); + TestBed + .configureTestingModule({ + imports: [FormsModule], + declarations: [HeroDetailComponent, TitleCasePipe], + providers: [ + {provide: ActivatedRoute, useValue: activatedRoute}, + {provide: HeroService, useClass: TestHeroService}, + {provide: Router, useValue: routerSpy}, + ] + }) + .compileComponents(); })); // #enddocregion setup-forms-module - it('should display 1st hero\'s name', async(() => { - const expectedHero = firstHero; - activatedRoute.setParamMap({ id: expectedHero.id }); - createComponent().then(() => { - expect(page.nameDisplay.textContent).toBe(expectedHero.name); - }); - })); + it('should display 1st hero\'s name', waitForAsync(() => { + const expectedHero = firstHero; + activatedRoute.setParamMap({id: expectedHero.id}); + createComponent().then(() => { + expect(page.nameDisplay.textContent).toBe(expectedHero.name); + }); + })); } /////////////////////// @@ -301,29 +293,30 @@ import { SharedModule } from '../shared/shared.module'; function sharedModuleSetup() { // #docregion setup-shared-module - beforeEach(async(() => { + beforeEach(waitForAsync(() => { const routerSpy = createRouterSpy(); - TestBed.configureTestingModule({ - imports: [ SharedModule ], - declarations: [ HeroDetailComponent ], - providers: [ - { provide: ActivatedRoute, useValue: activatedRoute }, - { provide: HeroService, useClass: TestHeroService }, - { provide: Router, useValue: routerSpy}, - ] - }) - .compileComponents(); + TestBed + .configureTestingModule({ + imports: [SharedModule], + declarations: [HeroDetailComponent], + providers: [ + {provide: ActivatedRoute, useValue: activatedRoute}, + {provide: HeroService, useClass: TestHeroService}, + {provide: Router, useValue: routerSpy}, + ] + }) + .compileComponents(); })); // #enddocregion setup-shared-module - it('should display 1st hero\'s name', async(() => { - const expectedHero = firstHero; - activatedRoute.setParamMap({ id: expectedHero.id }); - createComponent().then(() => { - expect(page.nameDisplay.textContent).toBe(expectedHero.name); - }); - })); + it('should display 1st hero\'s name', waitForAsync(() => { + const expectedHero = firstHero; + activatedRoute.setParamMap({id: expectedHero.id}); + createComponent().then(() => { + expect(page.nameDisplay.textContent).toBe(expectedHero.name); + }); + })); } /////////// Helpers ///// @@ -347,11 +340,21 @@ function createComponent() { // #docregion page class Page { // getter properties wait to query the DOM until called. - get buttons() { return this.queryAll('button'); } - get saveBtn() { return this.buttons[0]; } - get cancelBtn() { return this.buttons[1]; } - get nameDisplay() { return this.query('span'); } - get nameInput() { return this.query('input'); } + get buttons() { + return this.queryAll('button'); + } + get saveBtn() { + return this.buttons[0]; + } + get cancelBtn() { + return this.buttons[1]; + } + get nameDisplay() { + return this.query('span'); + } + get nameInput() { + return this.query('input'); + } gotoListSpy: jasmine.Spy; navigateSpy: jasmine.Spy; diff --git a/aio/content/examples/testing/src/app/hero/hero-list.component.spec.ts b/aio/content/examples/testing/src/app/hero/hero-list.component.spec.ts index 526be15c43..b8c0278bf6 100644 --- a/aio/content/examples/testing/src/app/hero/hero-list.component.spec.ts +++ b/aio/content/examples/testing/src/app/hero/hero-list.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, fakeAsync, TestBed, tick +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; @@ -7,13 +7,12 @@ import { DebugElement } from '@angular/core'; import { Router } from '@angular/router'; import { addMatchers, newEvent } from '../../testing'; - +import { HeroService } from '../model/hero.service'; import { getTestHeroes, TestHeroService } from '../model/testing/test-hero.service'; import { HeroModule } from './hero.module'; import { HeroListComponent } from './hero-list.component'; import { HighlightDirective } from '../shared/highlight.directive'; -import { HeroService } from '../model/hero.service'; const HEROES = getTestHeroes(); @@ -24,20 +23,20 @@ let page: Page; /////// Tests ////// describe('HeroListComponent', () => { - - beforeEach(async(() => { + beforeEach(waitForAsync(() => { addMatchers(); const routerSpy = jasmine.createSpyObj('Router', ['navigate']); - TestBed.configureTestingModule({ - imports: [HeroModule], - providers: [ - { provide: HeroService, useClass: TestHeroService }, - { provide: Router, useValue: routerSpy} - ] - }) - .compileComponents() - .then(createComponent); + TestBed + .configureTestingModule({ + imports: [HeroModule], + providers: [ + {provide: HeroService, useClass: TestHeroService}, + {provide: Router, useValue: routerSpy} + ] + }) + .compileComponents() + .then(createComponent); })); it('should display heroes', () => { @@ -52,36 +51,35 @@ describe('HeroListComponent', () => { }); it('should select hero on click', fakeAsync(() => { - const expectedHero = HEROES[1]; - const li = page.heroRows[1]; - li.dispatchEvent(newEvent('click')); - tick(); - // `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService - expect(comp.selectedHero).toEqual(expectedHero); - })); + const expectedHero = HEROES[1]; + const li = page.heroRows[1]; + li.dispatchEvent(newEvent('click')); + tick(); + // `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService + expect(comp.selectedHero).toEqual(expectedHero); + })); it('should navigate to selected hero detail on click', fakeAsync(() => { - const expectedHero = HEROES[1]; - const li = page.heroRows[1]; - li.dispatchEvent(newEvent('click')); - tick(); + const expectedHero = HEROES[1]; + const li = page.heroRows[1]; + li.dispatchEvent(newEvent('click')); + tick(); - // should have navigated - expect(page.navSpy.calls.any()).toBe(true, 'navigate called'); + // should have navigated + expect(page.navSpy.calls.any()).toBe(true, 'navigate called'); - // composed hero detail will be URL like 'heroes/42' - // expect link array with the route path and hero id - // first argument to router.navigate is link array - const navArgs = page.navSpy.calls.first().args[0]; - expect(navArgs[0]).toContain('heroes', 'nav to heroes detail URL'); - expect(navArgs[1]).toBe(expectedHero.id, 'expected hero.id'); - - })); + // composed hero detail will be URL like 'heroes/42' + // expect link array with the route path and hero id + // first argument to router.navigate is link array + const navArgs = page.navSpy.calls.first().args[0]; + expect(navArgs[0]).toContain('heroes', 'nav to heroes detail URL'); + expect(navArgs[1]).toBe(expectedHero.id, 'expected hero.id'); + })); it('should find `HighlightDirective` with `By.directive', () => { // #docregion by // Can find DebugElement either by css selector or by directive - const h2 = fixture.debugElement.query(By.css('h2')); + const h2 = fixture.debugElement.query(By.css('h2')); const directive = fixture.debugElement.query(By.directive(HighlightDirective)); // #enddocregion by expect(h2).toBe(directive); diff --git a/aio/content/examples/testing/src/app/shared/canvas.component.spec.ts b/aio/content/examples/testing/src/app/shared/canvas.component.spec.ts index 3ff225d33c..1c30c7dc6a 100644 --- a/aio/content/examples/testing/src/app/shared/canvas.component.spec.ts +++ b/aio/content/examples/testing/src/app/shared/canvas.component.spec.ts @@ -1,6 +1,7 @@ // #docplaster // #docregion without-toBlob-macrotask -import { TestBed, async, tick, fakeAsync } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; + import { CanvasComponent } from './canvas.component'; describe('CanvasComponent', () => { @@ -10,29 +11,29 @@ describe('CanvasComponent', () => { (window as any).__zone_symbol__FakeAsyncTestMacroTask = [ { source: 'HTMLCanvasElement.toBlob', - callbackArgs: [{ size: 200 }], + callbackArgs: [{size: 200}], }, ]; }); // #enddocregion enable-toBlob-macrotask // #docregion without-toBlob-macrotask - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - CanvasComponent - ], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [CanvasComponent], + }) + .compileComponents(); })); it('should be able to generate blob data from canvas', fakeAsync(() => { - const fixture = TestBed.createComponent(CanvasComponent); - const canvasComp = fixture.componentInstance; + const fixture = TestBed.createComponent(CanvasComponent); + const canvasComp = fixture.componentInstance; - fixture.detectChanges(); - expect(canvasComp.blobSize).toBe(0); + fixture.detectChanges(); + expect(canvasComp.blobSize).toBe(0); - tick(); - expect(canvasComp.blobSize).toBeGreaterThan(0); - })); + tick(); + expect(canvasComp.blobSize).toBeGreaterThan(0); + })); }); // #enddocregion without-toBlob-macrotask diff --git a/aio/content/examples/testing/src/app/twain/twain.component.spec.ts b/aio/content/examples/testing/src/app/twain/twain.component.spec.ts index ced3bb0094..629b6a4d5b 100644 --- a/aio/content/examples/testing/src/app/twain/twain.component.spec.ts +++ b/aio/content/examples/testing/src/app/twain/twain.component.spec.ts @@ -1,14 +1,13 @@ // #docplaster -import { async, fakeAsync, ComponentFixture, TestBed, tick } from '@angular/core/testing'; +import { fakeAsync, ComponentFixture, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { asyncData, asyncError } from '../../testing'; import { of, throwError } from 'rxjs'; - import { last } from 'rxjs/operators'; -import { TwainService } from './twain.service'; import { TwainComponent } from './twain.component'; +import { TwainService } from './twain.service'; describe('TwainComponent', () => { let component: TwainComponent; @@ -32,14 +31,12 @@ describe('TwainComponent', () => { // Create a fake TwainService object with a `getQuote()` spy const twainService = jasmine.createSpyObj('TwainService', ['getQuote']); // Make the spy return a synchronous Observable with the test data - getQuoteSpy = twainService.getQuote.and.returnValue( of(testQuote) ); + getQuoteSpy = twainService.getQuote.and.returnValue(of(testQuote)); // #enddocregion spy TestBed.configureTestingModule({ - declarations: [ TwainComponent ], - providers: [ - { provide: TwainService, useValue: twainService } - ] + declarations: [TwainComponent], + providers: [{provide: TwainService, useValue: twainService}] }); fixture = TestBed.createComponent(TwainComponent); @@ -58,7 +55,7 @@ describe('TwainComponent', () => { // The quote would not be immediately available if the service were truly async. // #docregion sync-test it('should show quote after component initialized', () => { - fixture.detectChanges(); // onInit() + fixture.detectChanges(); // onInit() // sync spy result shows testQuote immediately after init expect(quoteEl.textContent).toBe(testQuote); @@ -71,20 +68,19 @@ describe('TwainComponent', () => { // Use `fakeAsync` because the component error calls `setTimeout` // #docregion error-test it('should display error when TwainService fails', fakeAsync(() => { - // tell spy to return an error observable - getQuoteSpy.and.returnValue( - throwError('TwainService test failure')); + // tell spy to return an error observable + getQuoteSpy.and.returnValue(throwError('TwainService test failure')); - fixture.detectChanges(); // onInit() - // sync spy errors immediately after init + fixture.detectChanges(); // onInit() + // sync spy errors immediately after init - tick(); // flush the component's setTimeout() + tick(); // flush the component's setTimeout() - fixture.detectChanges(); // update errorMessage within setTimeout() + fixture.detectChanges(); // update errorMessage within setTimeout() - expect(errorMessage()).toMatch(/test failure/, 'should display error'); - expect(quoteEl.textContent).toBe('...', 'should show placeholder'); - })); + expect(errorMessage()).toMatch(/test failure/, 'should display error'); + expect(quoteEl.textContent).toBe('...', 'should show placeholder'); + })); // #enddocregion error-test }); @@ -113,28 +109,28 @@ describe('TwainComponent', () => { // #docregion fake-async-test it('should show quote after getQuote (fakeAsync)', fakeAsync(() => { - fixture.detectChanges(); // ngOnInit() - expect(quoteEl.textContent).toBe('...', 'should show placeholder'); + fixture.detectChanges(); // ngOnInit() + expect(quoteEl.textContent).toBe('...', 'should show placeholder'); - tick(); // flush the observable to get the quote - fixture.detectChanges(); // update view + tick(); // flush the observable to get the quote + fixture.detectChanges(); // update view - expect(quoteEl.textContent).toBe(testQuote, 'should show quote'); - expect(errorMessage()).toBeNull('should not show error'); - })); + expect(quoteEl.textContent).toBe(testQuote, 'should show quote'); + expect(errorMessage()).toBeNull('should not show error'); + })); // #enddocregion fake-async-test // #docregion async-test - it('should show quote after getQuote (async)', async(() => { - fixture.detectChanges(); // ngOnInit() - expect(quoteEl.textContent).toBe('...', 'should show placeholder'); + it('should show quote after getQuote (async)', waitForAsync(() => { + fixture.detectChanges(); // ngOnInit() + expect(quoteEl.textContent).toBe('...', 'should show placeholder'); - fixture.whenStable().then(() => { // wait for async getQuote - fixture.detectChanges(); // update view with quote - expect(quoteEl.textContent).toBe(testQuote); - expect(errorMessage()).toBeNull('should not show error'); - }); - })); + fixture.whenStable().then(() => { // wait for async getQuote + fixture.detectChanges(); // update view with quote + expect(quoteEl.textContent).toBe(testQuote); + expect(errorMessage()).toBeNull('should not show error'); + }); + })); // #enddocregion async-test @@ -142,8 +138,8 @@ describe('TwainComponent', () => { it('should show last quote (quote done)', (done: DoneFn) => { fixture.detectChanges(); - component.quote.pipe( last() ).subscribe(() => { - fixture.detectChanges(); // update view with quote + component.quote.pipe(last()).subscribe(() => { + fixture.detectChanges(); // update view with quote expect(quoteEl.textContent).toBe(testQuote); expect(errorMessage()).toBeNull('should not show error'); done(); @@ -157,7 +153,7 @@ describe('TwainComponent', () => { // the spy's most recent call returns the observable with the test quote getQuoteSpy.calls.mostRecent().returnValue.subscribe(() => { - fixture.detectChanges(); // update view with quote + fixture.detectChanges(); // update view with quote expect(quoteEl.textContent).toBe(testQuote); expect(errorMessage()).toBeNull('should not show error'); done(); @@ -167,16 +163,16 @@ describe('TwainComponent', () => { // #docregion async-error-test it('should display error when TwainService fails', fakeAsync(() => { - // tell spy to return an async error observable - getQuoteSpy.and.returnValue(asyncError('TwainService test failure')); + // tell spy to return an async error observable + getQuoteSpy.and.returnValue(asyncError('TwainService test failure')); - fixture.detectChanges(); - tick(); // component shows error after a setTimeout() - fixture.detectChanges(); // update error message + fixture.detectChanges(); + tick(); // component shows error after a setTimeout() + fixture.detectChanges(); // update error message - expect(errorMessage()).toMatch(/test failure/, 'should display error'); - expect(quoteEl.textContent).toBe('...', 'should show placeholder'); - })); + expect(errorMessage()).toMatch(/test failure/, 'should display error'); + expect(quoteEl.textContent).toBe('...', 'should show placeholder'); + })); // #enddocregion async-error-test }); }); diff --git a/aio/content/examples/toh-pt6/src/app/dashboard/dashboard.component.spec.ts b/aio/content/examples/toh-pt6/src/app/dashboard/dashboard.component.spec.ts index 3926c21e9f..224f5db3ce 100644 --- a/aio/content/examples/toh-pt6/src/app/dashboard/dashboard.component.spec.ts +++ b/aio/content/examples/toh-pt6/src/app/dashboard/dashboard.component.spec.ts @@ -1,12 +1,12 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DashboardComponent } from './dashboard.component'; -import { HeroSearchComponent } from '../hero-search/hero-search.component'; - +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; -import { HEROES } from '../mock-heroes'; + +import { HeroSearchComponent } from '../hero-search/hero-search.component'; import { HeroService } from '../hero.service'; +import { HEROES } from '../mock-heroes'; + +import { DashboardComponent } from './dashboard.component'; describe('DashboardComponent', () => { let component: DashboardComponent; @@ -14,23 +14,16 @@ describe('DashboardComponent', () => { let heroService; let getHeroesSpy; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { heroService = jasmine.createSpyObj('HeroService', ['getHeroes']); - getHeroesSpy = heroService.getHeroes.and.returnValue( of(HEROES) ); - TestBed.configureTestingModule({ - declarations: [ - DashboardComponent, - HeroSearchComponent - ], - imports: [ - RouterTestingModule.withRoutes([]) - ], - providers: [ - { provide: HeroService, useValue: heroService } - ] - }) - .compileComponents(); - + getHeroesSpy = heroService.getHeroes.and.returnValue(of(HEROES)); + TestBed + .configureTestingModule({ + declarations: [DashboardComponent, HeroSearchComponent], + imports: [RouterTestingModule.withRoutes([])], + providers: [{provide: HeroService, useValue: heroService}] + }) + .compileComponents(); })); beforeEach(() => { @@ -47,12 +40,11 @@ describe('DashboardComponent', () => { expect(fixture.nativeElement.querySelector('h3').textContent).toEqual('Top Heroes'); }); - it('should call heroService', async(() => { - expect(getHeroesSpy.calls.any()).toBe(true); - })); - - it('should display 4 links', async(() => { - expect(fixture.nativeElement.querySelectorAll('a').length).toEqual(4); - })); + it('should call heroService', waitForAsync(() => { + expect(getHeroesSpy.calls.any()).toBe(true); + })); + it('should display 4 links', waitForAsync(() => { + expect(fixture.nativeElement.querySelectorAll('a').length).toEqual(4); + })); }); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/app/phone-detail/phone-detail.component.spec.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/app/phone-detail/phone-detail.component.spec.ts index 607fad3184..4ebfdd344e 100644 --- a/aio/content/examples/upgrade-phonecat-2-hybrid/app/phone-detail/phone-detail.component.spec.ts +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/app/phone-detail/phone-detail.component.spec.ts @@ -1,22 +1,16 @@ // #docregion // #docregion activatedroute +import { TestBed, waitForAsync } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; - // #enddocregion activatedroute import { Observable, of } from 'rxjs'; -import { async, TestBed } from '@angular/core/testing'; - import { PhoneDetailComponent } from './phone-detail.component'; import { Phone, PhoneData } from '../core/phone/phone.service'; import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe'; function xyzPhoneData(): PhoneData { - return { - name: 'phone xyz', - snippet: '', - images: ['image/url1.png', 'image/url2.png'] - }; + return {name: 'phone xyz', snippet: '', images: ['image/url1.png', 'image/url2.png']}; } class MockPhone { @@ -34,10 +28,9 @@ class ActivatedRouteMock { // #enddocregion activatedroute describe('PhoneDetailComponent', () => { - // #docregion activatedroute - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ CheckmarkPipe, PhoneDetailComponent ], providers: [ @@ -55,5 +48,4 @@ describe('PhoneDetailComponent', () => { const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name); }); - }); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/app/phone-list/phone-list.component.spec.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/app/phone-list/phone-list.component.spec.ts index 21bd8182b7..443fc65178 100644 --- a/aio/content/examples/upgrade-phonecat-2-hybrid/app/phone-list/phone-list.component.spec.ts +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/app/phone-list/phone-list.component.spec.ts @@ -1,13 +1,14 @@ /* tslint:disable */ // #docregion -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Observable, of } from 'rxjs'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { SpyLocation } from '@angular/common/testing'; +import {SpyLocation} from '@angular/common/testing'; +import {NO_ERRORS_SCHEMA} from '@angular/core'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; +import {ActivatedRoute} from '@angular/router'; +import {Observable, of} from 'rxjs'; -import { PhoneListComponent } from './phone-list.component'; -import { Phone, PhoneData } from '../core/phone/phone.service'; +import {Phone, PhoneData} from '../core/phone/phone.service'; + +import {PhoneListComponent} from './phone-list.component'; class ActivatedRouteMock { constructor(public snapshot: any) {} @@ -16,8 +17,7 @@ class ActivatedRouteMock { class MockPhone { query(): Observable { return of([ - {name: 'Nexus S', snippet: '', images: []}, - {name: 'Motorola DROID', snippet: '', images: []} + {name: 'Nexus S', snippet: '', images: []}, {name: 'Motorola DROID', snippet: '', images: []} ]); } } @@ -25,18 +25,18 @@ class MockPhone { let fixture: ComponentFixture; describe('PhoneList', () => { - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ PhoneListComponent ], - providers: [ - { provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) }, - { provide: Location, useClass: SpyLocation }, - { provide: Phone, useClass: MockPhone }, - ], - schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [PhoneListComponent], + providers: [ + {provide: ActivatedRoute, useValue: new ActivatedRouteMock({params: {'phoneId': 1}})}, + {provide: Location, useClass: SpyLocation}, + {provide: Phone, useClass: MockPhone}, + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); })); beforeEach(() => { @@ -47,20 +47,15 @@ describe('PhoneList', () => { fixture.detectChanges(); let compiled = fixture.debugElement.nativeElement; expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2); - expect( - compiled.querySelector('.phone-list-item:nth-child(1)').textContent - ).toContain('Motorola DROID'); - expect( - compiled.querySelector('.phone-list-item:nth-child(2)').textContent - ).toContain('Nexus S'); + expect(compiled.querySelector('.phone-list-item:nth-child(1)').textContent) + .toContain('Motorola DROID'); + expect(compiled.querySelector('.phone-list-item:nth-child(2)').textContent) + .toContain('Nexus S'); }); xit('should set the default value of orderProp model', () => { fixture.detectChanges(); let compiled = fixture.debugElement.nativeElement; - expect( - compiled.querySelector('select option:last-child').selected - ).toBe(true); + expect(compiled.querySelector('select option:last-child').selected).toBe(true); }); - }); diff --git a/aio/content/examples/upgrade-phonecat-3-final/app/phone-detail/phone-detail.component.spec.ts b/aio/content/examples/upgrade-phonecat-3-final/app/phone-detail/phone-detail.component.spec.ts index 607fad3184..4ebfdd344e 100644 --- a/aio/content/examples/upgrade-phonecat-3-final/app/phone-detail/phone-detail.component.spec.ts +++ b/aio/content/examples/upgrade-phonecat-3-final/app/phone-detail/phone-detail.component.spec.ts @@ -1,22 +1,16 @@ // #docregion // #docregion activatedroute +import { TestBed, waitForAsync } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; - // #enddocregion activatedroute import { Observable, of } from 'rxjs'; -import { async, TestBed } from '@angular/core/testing'; - import { PhoneDetailComponent } from './phone-detail.component'; import { Phone, PhoneData } from '../core/phone/phone.service'; import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe'; function xyzPhoneData(): PhoneData { - return { - name: 'phone xyz', - snippet: '', - images: ['image/url1.png', 'image/url2.png'] - }; + return {name: 'phone xyz', snippet: '', images: ['image/url1.png', 'image/url2.png']}; } class MockPhone { @@ -34,10 +28,9 @@ class ActivatedRouteMock { // #enddocregion activatedroute describe('PhoneDetailComponent', () => { - // #docregion activatedroute - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ CheckmarkPipe, PhoneDetailComponent ], providers: [ @@ -55,5 +48,4 @@ describe('PhoneDetailComponent', () => { const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name); }); - }); diff --git a/aio/content/examples/upgrade-phonecat-3-final/app/phone-list/phone-list.component.spec.ts b/aio/content/examples/upgrade-phonecat-3-final/app/phone-list/phone-list.component.spec.ts index 588eabe76e..cb5dd53084 100644 --- a/aio/content/examples/upgrade-phonecat-3-final/app/phone-list/phone-list.component.spec.ts +++ b/aio/content/examples/upgrade-phonecat-3-final/app/phone-list/phone-list.component.spec.ts @@ -1,13 +1,14 @@ /* tslint:disable */ // #docregion routestuff -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Observable, of } from 'rxjs'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { SpyLocation } from '@angular/common/testing'; +import {SpyLocation} from '@angular/common/testing'; +import {NO_ERRORS_SCHEMA} from '@angular/core'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; +import {ActivatedRoute} from '@angular/router'; +import {Observable, of} from 'rxjs'; -import { PhoneListComponent } from './phone-list.component'; -import { Phone, PhoneData } from '../core/phone/phone.service'; +import {Phone, PhoneData} from '../core/phone/phone.service'; + +import {PhoneListComponent} from './phone-list.component'; // #enddocregion routestuff @@ -18,8 +19,7 @@ class ActivatedRouteMock { class MockPhone { query(): Observable { return of([ - {name: 'Nexus S', snippet: '', images: []}, - {name: 'Motorola DROID', snippet: '', images: []} + {name: 'Nexus S', snippet: '', images: []}, {name: 'Motorola DROID', snippet: '', images: []} ]); } } @@ -27,20 +27,20 @@ class MockPhone { let fixture: ComponentFixture; describe('PhoneList', () => { - // #docregion routestuff - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ PhoneListComponent ], - providers: [ - { provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) }, - { provide: Location, useClass: SpyLocation }, - { provide: Phone, useClass: MockPhone }, - ], - schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [PhoneListComponent], + providers: [ + {provide: ActivatedRoute, useValue: new ActivatedRouteMock({params: {'phoneId': 1}})}, + {provide: Location, useClass: SpyLocation}, + {provide: Phone, useClass: MockPhone}, + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); })); beforeEach(() => { @@ -52,20 +52,15 @@ describe('PhoneList', () => { fixture.detectChanges(); let compiled = fixture.debugElement.nativeElement; expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2); - expect( - compiled.querySelector('.phone-list-item:nth-child(1)').textContent - ).toContain('Motorola DROID'); - expect( - compiled.querySelector('.phone-list-item:nth-child(2)').textContent - ).toContain('Nexus S'); + expect(compiled.querySelector('.phone-list-item:nth-child(1)').textContent) + .toContain('Motorola DROID'); + expect(compiled.querySelector('.phone-list-item:nth-child(2)').textContent) + .toContain('Nexus S'); }); xit('should set the default value of orderProp model', () => { fixture.detectChanges(); let compiled = fixture.debugElement.nativeElement; - expect( - compiled.querySelector('select option:last-child').selected - ).toBe(true); + expect(compiled.querySelector('select option:last-child').selected).toBe(true); }); - }); diff --git a/aio/content/guide/deprecations.md b/aio/content/guide/deprecations.md index c97f67b89c..d4e2320411 100644 --- a/aio/content/guide/deprecations.md +++ b/aio/content/guide/deprecations.md @@ -58,6 +58,7 @@ v9 - v12 | `@angular/core` | [`ANALYZE_FOR_ENTRY_COMPONENTS`](api/core/ANALYZE_FOR_ENTRY_COMPONENTS) | v11 | | `@angular/router` | [`loadChildren` string syntax](#loadChildren) | v11 | | `@angular/core/testing` | [`TestBed.get`](#testing) | v12 | +| `@angular/core/testing` | [`async`](#testing) | v12 | | `@angular/router` | [`ActivatedRoute` params and `queryParams` properties](#activatedroute-props) | unspecified | | template syntax | [`/deep/`, `>>>`, and `::ng-deep`](#deep-component-style-selector) | unspecified | | browser support | [`IE 9 and 10, IE mobile`](#ie-9-10-and-mobile) | v11 | @@ -108,6 +109,7 @@ Tip: In the [API reference section](api) of this doc site, deprecated APIs are i | API | Replacement | Deprecation announced | Notes | | --- | ----------- | --------------------- | ----- | | [`TestBed.get`](api/core/testing/TestBed#get) | [`TestBed.inject`](api/core/testing/TestBed#inject) | v9 | Same behavior, but type safe. | +| [`async`](api/core/testing/async) | [`waitForAsync`](api/core/testing/waitForAsync) | v10 | Same behavior, but rename to avoid confusion. | {@a forms} @@ -477,7 +479,7 @@ The final decision was made on three key points: {@a wrapped-value} -### `WrappedValue` +### `WrappedValue` The purpose of `WrappedValue` is to allow the same object instance to be treated as different for the purposes of change detection. It is commonly used with the `async` pipe in the case where the `Observable` produces the same instance of the value. @@ -487,7 +489,7 @@ No replacement is planned for this deprecation. If you rely on the behavior that the same object instance should cause change detection, you have two options: - Clone the resulting value so that it has a new identity. -- Explicitly call [`ChangeDetectorRef.detectChanges()`](api/core/ChangeDetectorRef#detectchanges) to force the update. +- Explicitly call [`ChangeDetectorRef.detectChanges()`](api/core/ChangeDetectorRef#detectchanges) to force the update. {@a deprecated-cli-flags} ## Deprecated CLI APIs and Options diff --git a/aio/content/guide/testing-components-basics.md b/aio/content/guide/testing-components-basics.md index 01e0bec05a..ca70d13559 100644 --- a/aio/content/guide/testing-components-basics.md +++ b/aio/content/guide/testing-components-basics.md @@ -159,10 +159,10 @@ It also generates an initial test file for the component, `banner-external.compo
Because `compileComponents` is asynchronous, it uses -the [`async`](api/core/testing/async) utility +the [`waitForAsync`](api/core/testing/waitForAsync) utility function imported from `@angular/core/testing`. -Please refer to the [async](guide/testing-components-scenarios#async) section for more details. +Please refer to the [waitForAsync](guide/testing-components-scenarios#waitForAsync) section for more details.
diff --git a/aio/content/guide/testing-components-scenarios.md b/aio/content/guide/testing-components-scenarios.md index 7ddb1af097..fb0ec07f9e 100644 --- a/aio/content/guide/testing-components-scenarios.md +++ b/aio/content/guide/testing-components-scenarios.md @@ -402,7 +402,7 @@ There is no nested syntax (like a `Promise.then()`) to disrupt the flow of contr
Limitation: The `fakeAsync()` function won't work if the test body makes an `XMLHttpRequest` (XHR) call. -XHR calls within a test are rare, but if you need to call XHR, see [`async()`](#async), below. +XHR calls within a test are rare, but if you need to call XHR, see [`waitForAsync()`](#waitForAsync), below.
@@ -587,41 +587,41 @@ Then call `detectChanges()` to tell Angular to update the screen. Then you can assert that the quote element displays the expected text. -{@a async} +{@a waitForAsync} -#### Async test with _async()_ +#### Async test with _waitForAsync()_ -To use `async()` functionality, you must import `zone.js/dist/zone-testing` in your test setup file. +To use `waitForAsync()` functionality, you must import `zone.js/dist/zone-testing` in your test setup file. If you created your project with the Angular CLI, `zone-testing` is already imported in `src/test.ts`. The `fakeAsync()` utility function has a few limitations. In particular, it won't work if the test body makes an `XMLHttpRequest` (XHR) call. XHR calls within a test are rare so you can generally stick with [`fakeAsync()`](#fake-async). -But if you ever do need to call `XMLHttpRequest`, you'll want to know about `async()`. +But if you ever do need to call `XMLHttpRequest`, you'll want to know about `waitForAsync()`.
The `TestBed.compileComponents()` method (see [below](#compile-components)) calls `XHR` to read external template and css files during "just-in-time" compilation. -Write tests that call `compileComponents()` with the `async()` utility. +Write tests that call `compileComponents()` with the `waitForAsync()` utility.
-Here's the previous `fakeAsync()` test, re-written with the `async()` utility. +Here's the previous `fakeAsync()` test, re-written with the `waitForAsync()` utility. -The `async()` utility hides some asynchronous boilerplate by arranging for the tester's code +The `waitForAsync()` utility hides some asynchronous boilerplate by arranging for the tester's code to run in a special _async test zone_. You don't need to pass Jasmine's `done()` into the test and call `done()` because it is `undefined` in promise or observable callbacks. But the test's asynchronous nature is revealed by the call to `fixture.whenStable()`, which breaks the linear flow of control. -When using an `intervalTimer()` such as `setInterval()` in `async()`, remember to cancel the timer with `clearInterval()` after the test, otherwise the `async()` never ends. +When using an `intervalTimer()` such as `setInterval()` in `waitForAsync()`, remember to cancel the timer with `clearInterval()` after the test, otherwise the `waitForAsync()` never ends. {@a when-stable} @@ -641,18 +641,18 @@ update the quote element with the expected text. #### Jasmine _done()_ -While the `async()` and `fakeAsync()` functions greatly +While the `waitForAsync()` and `fakeAsync()` functions greatly simplify Angular asynchronous testing, you can still fall back to the traditional technique and pass `it` a function that takes a [`done` callback](https://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support). -You can't call `done()` in `async()` or `fakeAsync()` functions, because the `done parameter` +You can't call `done()` in `waitForAsync()` or `fakeAsync()` functions, because the `done parameter` is `undefined`. Now you are responsible for chaining promises, handling errors, and calling `done()` at the appropriate moments. -Writing test functions with `done()`, is more cumbersome than `async()`and `fakeAsync()`, but it is occasionally necessary when code involves the `intervalTimer()` like `setInterval`. +Writing test functions with `done()`, is more cumbersome than `waitForAsync()`and `fakeAsync()`, but it is occasionally necessary when code involves the `intervalTimer()` like `setInterval`. Here are two more versions of the previous test, written with `done()`. The first one subscribes to the `Observable` exposed to the template by the component's `quote` property. @@ -738,7 +738,7 @@ you tell the `TestScheduler` to _flush_ its queue of prepared tasks like this. region="test-scheduler-flush"> This step serves a purpose analogous to [tick()](api/core/testing/tick) and `whenStable()` in the -earlier `fakeAsync()` and `async()` examples. +earlier `fakeAsync()` and `waitForAsync()` examples. The balance of the test is the same as those examples. #### Marble error testing @@ -1535,7 +1535,7 @@ You must call `compileComponents()` within an asynchronous test function.
If you neglect to make the test function async -(e.g., forget to use `async()` as described below), +(e.g., forget to use `waitForAsync()` as described below), you'll see this error message @@ -1549,7 +1549,7 @@ A typical approach is to divide the setup logic into two separate `beforeEach()` 1. An async `beforeEach()` that compiles the components 1. A synchronous `beforeEach()` that performs the remaining setup. -To follow this pattern, import the `async()` helper with the other testing symbols. +To follow this pattern, import the `waitForAsync()` helper with the other testing symbols. -The `async()` helper function takes a parameterless function with the body of the setup. +The `waitForAsync()` helper function takes a parameterless function with the body of the setup. The `TestBed.configureTestingModule()` method returns the `TestBed` class so you can chain calls to other `TestBed` static methods such as `compileComponents()`. diff --git a/aio/content/guide/testing-utility-apis.md b/aio/content/guide/testing-utility-apis.md index 8152d75acf..0c1b6ddd12 100644 --- a/aio/content/guide/testing-utility-apis.md +++ b/aio/content/guide/testing-utility-apis.md @@ -25,7 +25,7 @@ Here's a summary of the stand-alone functions, in order of likely utility: Runs the body of a test (`it`) or setup (`beforeEach`) function within a special _async test zone_. - See [discussion above](guide/testing-components-scenarios#async). + See [discussion above](guide/testing-components-scenarios#waitForAsync). diff --git a/aio/content/guide/upgrade-setup.md b/aio/content/guide/upgrade-setup.md index 284076e42f..bf1e9da49e 100644 --- a/aio/content/guide/upgrade-setup.md +++ b/aio/content/guide/upgrade-setup.md @@ -306,9 +306,9 @@ If you develop angular locally with `ng serve`, a `websocket` connection is set In Windows, by default, one application can only have 6 websocket connections, MSDN WebSocket Settings. So when IE is refreshed (manually or automatically by `ng serve`), sometimes the websocket does not close properly. When websocket connections exceed the limitations, a `SecurityError` will be thrown. This error will not affect the angular application, you can just restart IE to clear this error, or modify the windows registry to update the limitations. -## Appendix: Test using `fakeAsync()/async()` +## Appendix: Test using `fakeAsync()/waitForAsync()` -If you use the `fakeAsync()/async()` helper function to run unit tests (for details, read the [Testing guide](guide/testing-components-scenarios#fake-async)), you need to import `zone.js/dist/zone-testing` in your test setup file. +If you use the `fakeAsync()/waitForAsync()` helper function to run unit tests (for details, read the [Testing guide](guide/testing-components-scenarios#fake-async)), you need to import `zone.js/dist/zone-testing` in your test setup file.
If you create project with `Angular/CLI`, it is already imported in `src/test.ts`. diff --git a/goldens/public-api/core/testing/testing.d.ts b/goldens/public-api/core/testing/testing.d.ts index b2ef64a7d7..8144bc5d30 100644 --- a/goldens/public-api/core/testing/testing.d.ts +++ b/goldens/public-api/core/testing/testing.d.ts @@ -1,6 +1,7 @@ /** @codeGenApi */ export declare const __core_private_testing_placeholder__ = ""; +/** @deprecated */ export declare function async(fn: Function): (done: any) => any; export declare class ComponentFixture { @@ -141,5 +142,7 @@ export declare function tick(millis?: number, tickOptions?: { processNewMacroTasksSynchronously: boolean; }): void; +export declare function waitForAsync(fn: Function): (done: any) => any; + export declare function withModule(moduleDef: TestModuleMetadata): InjectSetupWrapper; export declare function withModule(moduleDef: TestModuleMetadata, fn: Function): () => any; diff --git a/integration/cli-hello-world-ivy-compat/src/app/app.component.spec.ts b/integration/cli-hello-world-ivy-compat/src/app/app.component.spec.ts index 249cf07d32..bde049e345 100644 --- a/integration/cli-hello-world-ivy-compat/src/app/app.component.spec.ts +++ b/integration/cli-hello-world-ivy-compat/src/app/app.component.spec.ts @@ -1,13 +1,13 @@ -import { TestBed, async } from '@angular/core/testing'; -import { AppComponent } from './app.component'; +import {TestBed, waitForAsync} from '@angular/core/testing'; +import {AppComponent} from './app.component'; describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [AppComponent], + }) + .compileComponents(); })); it('should create the app', () => { @@ -26,6 +26,7 @@ describe('AppComponent', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('.content span').textContent).toContain('cli-hello-world-ivy-compat app is running!'); + expect(compiled.querySelector('.content span').textContent) + .toContain('cli-hello-world-ivy-compat app is running!'); }); }); diff --git a/integration/cli-hello-world-ivy-minimal/src/app/app.component.spec.ts b/integration/cli-hello-world-ivy-minimal/src/app/app.component.spec.ts index 5f6eb96611..0efa90fe20 100644 --- a/integration/cli-hello-world-ivy-minimal/src/app/app.component.spec.ts +++ b/integration/cli-hello-world-ivy-minimal/src/app/app.component.spec.ts @@ -1,13 +1,13 @@ -import { TestBed, async } from '@angular/core/testing'; -import { AppComponent } from './app.component'; +import {TestBed, waitForAsync} from '@angular/core/testing'; +import {AppComponent} from './app.component'; describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [AppComponent], + }) + .compileComponents(); })); it('should create the app', () => { diff --git a/integration/cli-hello-world-lazy-rollup/src/app/app.component.spec.ts b/integration/cli-hello-world-lazy-rollup/src/app/app.component.spec.ts index deeee48471..3275ebecc7 100644 --- a/integration/cli-hello-world-lazy-rollup/src/app/app.component.spec.ts +++ b/integration/cli-hello-world-lazy-rollup/src/app/app.component.spec.ts @@ -1,17 +1,15 @@ -import { TestBed, async } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; +import {TestBed, waitForAsync} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; +import {AppComponent} from './app.component'; describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + imports: [RouterTestingModule], + declarations: [AppComponent], + }) + .compileComponents(); })); it('should create the app', () => { @@ -30,6 +28,7 @@ describe('AppComponent', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('.content span').textContent).toContain('cli-hello-world-lazy-rollup app is running!'); + expect(compiled.querySelector('.content span').textContent) + .toContain('cli-hello-world-lazy-rollup app is running!'); }); }); diff --git a/integration/cli-hello-world-lazy/src/app/app.component.spec.ts b/integration/cli-hello-world-lazy/src/app/app.component.spec.ts index 6da00987a8..0bbc645160 100644 --- a/integration/cli-hello-world-lazy/src/app/app.component.spec.ts +++ b/integration/cli-hello-world-lazy/src/app/app.component.spec.ts @@ -1,17 +1,15 @@ -import { TestBed, async } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; +import {TestBed, waitForAsync} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; +import {AppComponent} from './app.component'; describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + imports: [RouterTestingModule], + declarations: [AppComponent], + }) + .compileComponents(); })); it('should create the app', () => { @@ -30,6 +28,7 @@ describe('AppComponent', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('.content span').textContent).toContain('cli-hello-world-lazy app is running!'); + expect(compiled.querySelector('.content span').textContent) + .toContain('cli-hello-world-lazy app is running!'); }); }); diff --git a/integration/cli-hello-world/src/app/app.component.spec.ts b/integration/cli-hello-world/src/app/app.component.spec.ts index 22488f4a4c..103a5b5c5b 100644 --- a/integration/cli-hello-world/src/app/app.component.spec.ts +++ b/integration/cli-hello-world/src/app/app.component.spec.ts @@ -1,13 +1,13 @@ -import { TestBed, async } from '@angular/core/testing'; -import { AppComponent } from './app.component'; +import {TestBed, waitForAsync} from '@angular/core/testing'; +import {AppComponent} from './app.component'; describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [AppComponent], + }) + .compileComponents(); })); it('should create the app', () => { @@ -26,6 +26,7 @@ describe('AppComponent', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('.content span').textContent).toContain('cli-hello-world app is running!'); + expect(compiled.querySelector('.content span').textContent) + .toContain('cli-hello-world app is running!'); }); }); diff --git a/integration/ng_update_migrations/src/app/app.component.spec.ts b/integration/ng_update_migrations/src/app/app.component.spec.ts index 428143af15..1d9ff930df 100644 --- a/integration/ng_update_migrations/src/app/app.component.spec.ts +++ b/integration/ng_update_migrations/src/app/app.component.spec.ts @@ -1,13 +1,13 @@ -import { TestBed, async } from '@angular/core/testing'; -import { AppComponent } from './app.component'; +import {TestBed, waitForAsync} from '@angular/core/testing'; +import {AppComponent} from './app.component'; describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed + .configureTestingModule({ + declarations: [AppComponent], + }) + .compileComponents(); })); it('should create the app', () => { diff --git a/packages/common/test/directives/ng_class_spec.ts b/packages/common/test/directives/ng_class_spec.ts index b0eef33aad..66bd7b93df 100644 --- a/packages/common/test/directives/ng_class_spec.ts +++ b/packages/common/test/directives/ng_class_spec.ts @@ -7,7 +7,7 @@ */ import {Component} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; { describe('binding to CSS class list', () => { @@ -37,7 +37,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; }); }); - it('should clean up when the directive is destroyed', async(() => { + it('should clean up when the directive is destroyed', waitForAsync(() => { fixture = createTestComponent('
'); getComponent().items = [['0']]; @@ -47,21 +47,22 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); describe('expressions evaluating to objects', () => { - it('should add classes specified in an object literal', async(() => { + it('should add classes specified in an object literal', waitForAsync(() => { fixture = createTestComponent('
'); detectChangesAndExpectClassName('foo'); })); it('should add classes specified in an object literal without change in class names', - async(() => { + waitForAsync(() => { fixture = createTestComponent(`
`); detectChangesAndExpectClassName('foo-bar fooBar'); })); - it('should add and remove classes based on changes in object literal values', async(() => { + it('should add and remove classes based on changes in object literal values', + waitForAsync(() => { fixture = createTestComponent('
'); @@ -71,7 +72,8 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('bar'); })); - it('should add and remove classes based on changes to the expression object', async(() => { + it('should add and remove classes based on changes to the expression object', + waitForAsync(() => { fixture = createTestComponent('
'); const objExpr = getComponent().objExpr; @@ -88,7 +90,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); it('should add and remove classes based on reference changes to the expression object', - async(() => { + waitForAsync(() => { fixture = createTestComponent('
'); detectChangesAndExpectClassName('foo'); @@ -100,7 +102,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('baz'); })); - it('should remove active classes when expression evaluates to null', async(() => { + it('should remove active classes when expression evaluates to null', waitForAsync(() => { fixture = createTestComponent('
'); detectChangesAndExpectClassName('foo'); @@ -113,7 +115,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); - it('should allow multiple classes per expression', async(() => { + it('should allow multiple classes per expression', waitForAsync(() => { fixture = createTestComponent('
'); getComponent().objExpr = {'bar baz': true, 'bar1 baz1': true}; @@ -123,7 +125,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('bar1 baz1'); })); - it('should split by one or more spaces between classes', async(() => { + it('should split by one or more spaces between classes', waitForAsync(() => { fixture = createTestComponent('
'); getComponent().objExpr = {'foo bar baz': true}; @@ -132,14 +134,14 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; }); describe('expressions evaluating to lists', () => { - it('should add classes specified in a list literal', async(() => { + it('should add classes specified in a list literal', waitForAsync(() => { fixture = createTestComponent(`
`); detectChangesAndExpectClassName('foo bar foo-bar fooBar'); })); - it('should add and remove classes based on changes to the expression', async(() => { + it('should add and remove classes based on changes to the expression', waitForAsync(() => { fixture = createTestComponent('
'); const arrExpr = getComponent().arrExpr; detectChangesAndExpectClassName('foo'); @@ -154,7 +156,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('foo'); })); - it('should add and remove classes when a reference changes', async(() => { + it('should add and remove classes when a reference changes', waitForAsync(() => { fixture = createTestComponent('
'); detectChangesAndExpectClassName('foo'); @@ -162,7 +164,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('bar'); })); - it('should take initial classes into account when a reference changes', async(() => { + it('should take initial classes into account when a reference changes', waitForAsync(() => { fixture = createTestComponent('
'); detectChangesAndExpectClassName('foo'); @@ -170,13 +172,13 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('foo bar'); })); - it('should ignore empty or blank class names', async(() => { + it('should ignore empty or blank class names', waitForAsync(() => { fixture = createTestComponent('
'); getComponent().arrExpr = ['', ' ']; detectChangesAndExpectClassName('foo'); })); - it('should trim blanks from class names', async(() => { + it('should trim blanks from class names', waitForAsync(() => { fixture = createTestComponent('
'); getComponent().arrExpr = [' bar ']; @@ -184,7 +186,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); - it('should allow multiple classes per item in arrays', async(() => { + it('should allow multiple classes per item in arrays', waitForAsync(() => { fixture = createTestComponent('
'); getComponent().arrExpr = ['foo bar baz', 'foo1 bar1 baz1']; @@ -203,7 +205,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; }); describe('expressions evaluating to sets', () => { - it('should add and remove classes if the set instance changed', async(() => { + it('should add and remove classes if the set instance changed', waitForAsync(() => { fixture = createTestComponent('
'); let setExpr = new Set(); setExpr.add('bar'); @@ -218,12 +220,12 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; }); describe('expressions evaluating to string', () => { - it('should add classes specified in a string literal', async(() => { + it('should add classes specified in a string literal', waitForAsync(() => { fixture = createTestComponent(`
`); detectChangesAndExpectClassName('foo bar foo-bar fooBar'); })); - it('should add and remove classes based on changes to the expression', async(() => { + it('should add and remove classes based on changes to the expression', waitForAsync(() => { fixture = createTestComponent('
'); detectChangesAndExpectClassName('foo'); @@ -235,7 +237,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('baz'); })); - it('should remove active classes when switching from string to null', async(() => { + it('should remove active classes when switching from string to null', waitForAsync(() => { fixture = createTestComponent(`
`); detectChangesAndExpectClassName('foo'); @@ -244,7 +246,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); it('should take initial classes into account when switching from string to null', - async(() => { + waitForAsync(() => { fixture = createTestComponent(`
`); detectChangesAndExpectClassName('foo'); @@ -252,7 +254,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('foo'); })); - it('should ignore empty and blank strings', async(() => { + it('should ignore empty and blank strings', waitForAsync(() => { fixture = createTestComponent(`
`); getComponent().strExpr = ''; detectChangesAndExpectClassName('foo'); @@ -260,7 +262,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; }); describe('cooperation with other class-changing constructs', () => { - it('should co-operate with the class attribute', async(() => { + it('should co-operate with the class attribute', waitForAsync(() => { fixture = createTestComponent('
'); const objExpr = getComponent().objExpr; @@ -274,7 +276,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName('init foo'); })); - it('should co-operate with the interpolated class attribute', async(() => { + it('should co-operate with the interpolated class attribute', waitForAsync(() => { fixture = createTestComponent(`
`); const objExpr = getComponent().objExpr; @@ -289,7 +291,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); it('should co-operate with the interpolated class attribute when interpolation changes', - async(() => { + waitForAsync(() => { fixture = createTestComponent( `
`); @@ -299,7 +301,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName(`bar small`); })); - it('should co-operate with the class attribute and binding to it', async(() => { + it('should co-operate with the class attribute and binding to it', waitForAsync(() => { fixture = createTestComponent(`
`); const objExpr = getComponent().objExpr; @@ -314,7 +316,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; detectChangesAndExpectClassName(`init foo`); })); - it('should co-operate with the class attribute and class.name binding', async(() => { + it('should co-operate with the class attribute and class.name binding', waitForAsync(() => { const template = '
'; fixture = createTestComponent(template); @@ -333,7 +335,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); it('should co-operate with initial class and class attribute binding when binding changes', - async(() => { + waitForAsync(() => { const template = '
'; fixture = createTestComponent(template); const cmp = getComponent(); diff --git a/packages/common/test/directives/ng_component_outlet_spec.ts b/packages/common/test/directives/ng_component_outlet_spec.ts index cefe18a44a..c611247823 100644 --- a/packages/common/test/directives/ng_component_outlet_spec.ts +++ b/packages/common/test/directives/ng_component_outlet_spec.ts @@ -9,7 +9,7 @@ import {CommonModule} from '@angular/common'; import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet'; import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NgModule, NgModuleFactory, NO_ERRORS_SCHEMA, Optional, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core'; -import {async, TestBed} from '@angular/core/testing'; +import {TestBed, waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; describe('insert/remove', () => { @@ -17,7 +17,7 @@ describe('insert/remove', () => { TestBed.configureTestingModule({imports: [TestModule]}); }); - it('should do nothing if component is null', async(() => { + it('should do nothing if component is null', waitForAsync(() => { const template = ``; TestBed.overrideComponent(TestComponent, {set: {template: template}}); let fixture = TestBed.createComponent(TestComponent); @@ -28,7 +28,7 @@ describe('insert/remove', () => { expect(fixture.nativeElement).toHaveText(''); })); - it('should insert content specified by a component', async(() => { + it('should insert content specified by a component', waitForAsync(() => { let fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); @@ -40,7 +40,7 @@ describe('insert/remove', () => { expect(fixture.nativeElement).toHaveText('foo'); })); - it('should emit a ComponentRef once a component was created', async(() => { + it('should emit a ComponentRef once a component was created', waitForAsync(() => { let fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); @@ -56,7 +56,7 @@ describe('insert/remove', () => { })); - it('should clear view if component becomes null', async(() => { + it('should clear view if component becomes null', waitForAsync(() => { let fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); @@ -74,7 +74,7 @@ describe('insert/remove', () => { })); - it('should swap content if component changes', async(() => { + it('should swap content if component changes', waitForAsync(() => { let fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); @@ -91,7 +91,7 @@ describe('insert/remove', () => { expect(fixture.nativeElement).toHaveText('bar'); })); - it('should use the injector, if one supplied', async(() => { + it('should use the injector, if one supplied', waitForAsync(() => { let fixture = TestBed.createComponent(TestComponent); const uniqueValue = {}; @@ -107,7 +107,7 @@ describe('insert/remove', () => { })); - it('should resolve with an injector', async(() => { + it('should resolve with an injector', waitForAsync(() => { let fixture = TestBed.createComponent(TestComponent); // We are accessing a ViewChild (ngComponentOutlet) before change detection has run @@ -120,7 +120,7 @@ describe('insert/remove', () => { expect(cmpRef.instance.testToken).toBeNull(); })); - it('should render projectable nodes, if supplied', async(() => { + it('should render projectable nodes, if supplied', waitForAsync(() => { const template = `projected foo${TEST_CMP_TEMPLATE}`; TestBed.overrideComponent(TestComponent, {set: {template: template}}) .configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}); @@ -144,7 +144,7 @@ describe('insert/remove', () => { expect(fixture.nativeElement).toHaveText('projected foo'); })); - it('should resolve components from other modules, if supplied', async(() => { + it('should resolve components from other modules, if supplied', waitForAsync(() => { const compiler = TestBed.inject(Compiler); let fixture = TestBed.createComponent(TestComponent); @@ -158,7 +158,7 @@ describe('insert/remove', () => { expect(fixture.nativeElement).toHaveText('baz'); })); - it('should clean up moduleRef, if supplied', async(() => { + it('should clean up moduleRef, if supplied', waitForAsync(() => { let destroyed = false; const compiler = TestBed.inject(Compiler); const fixture = TestBed.createComponent(TestComponent); @@ -174,7 +174,7 @@ describe('insert/remove', () => { expect(moduleRef.destroy).toHaveBeenCalled(); })); - it('should not re-create moduleRef when it didn\'t actually change', async(() => { + it('should not re-create moduleRef when it didn\'t actually change', waitForAsync(() => { const compiler = TestBed.inject(Compiler); const fixture = TestBed.createComponent(TestComponent); @@ -191,7 +191,7 @@ describe('insert/remove', () => { expect(moduleRef).toBe(fixture.componentInstance.ngComponentOutlet['_moduleRef']); })); - it('should re-create moduleRef when changed', async(() => { + it('should re-create moduleRef when changed', waitForAsync(() => { const compiler = TestBed.inject(Compiler); const fixture = TestBed.createComponent(TestComponent); fixture.componentInstance.module = compiler.compileModuleSync(TestModule2); diff --git a/packages/common/test/directives/ng_for_spec.ts b/packages/common/test/directives/ng_for_spec.ts index 1b2fdb6f6c..549010ff8a 100644 --- a/packages/common/test/directives/ng_for_spec.ts +++ b/packages/common/test/directives/ng_for_spec.ts @@ -8,7 +8,7 @@ import {CommonModule} from '@angular/common'; import {Component} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -38,27 +38,27 @@ let thisArg: any; }); }); - it('should reflect initial elements', async(() => { + it('should reflect initial elements', waitForAsync(() => { fixture = createTestComponent(); detectChangesAndExpectText('1;2;'); })); - it('should reflect added elements', async(() => { + it('should reflect added elements', waitForAsync(() => { fixture = createTestComponent(); fixture.detectChanges(); getComponent().items.push(3); detectChangesAndExpectText('1;2;3;'); })); - it('should reflect removed elements', async(() => { + it('should reflect removed elements', waitForAsync(() => { fixture = createTestComponent(); fixture.detectChanges(); getComponent().items.splice(1, 1); detectChangesAndExpectText('1;'); })); - it('should reflect moved elements', async(() => { + it('should reflect moved elements', waitForAsync(() => { fixture = createTestComponent(); fixture.detectChanges(); getComponent().items.splice(0, 1); @@ -66,7 +66,7 @@ let thisArg: any; detectChangesAndExpectText('2;1;'); })); - it('should reflect a mix of all changes (additions/removals/moves)', async(() => { + it('should reflect a mix of all changes (additions/removals/moves)', waitForAsync(() => { fixture = createTestComponent(); getComponent().items = [0, 1, 2, 3, 4, 5]; @@ -77,7 +77,7 @@ let thisArg: any; detectChangesAndExpectText('6;2;7;0;4;8;'); })); - it('should iterate over an array of objects', async(() => { + it('should iterate over an array of objects', waitForAsync(() => { const template = '
  • {{item["name"]}};
'; fixture = createTestComponent(template); @@ -95,14 +95,14 @@ let thisArg: any; detectChangesAndExpectText('shyam;'); })); - it('should gracefully handle nulls', async(() => { + it('should gracefully handle nulls', waitForAsync(() => { const template = '
  • {{item}};
'; fixture = createTestComponent(template); detectChangesAndExpectText(''); })); - it('should gracefully handle ref changing to null and back', async(() => { + it('should gracefully handle ref changing to null and back', waitForAsync(() => { fixture = createTestComponent(); detectChangesAndExpectText('1;2;'); @@ -114,7 +114,7 @@ let thisArg: any; detectChangesAndExpectText('1;2;3;'); })); - it('should throw on non-iterable ref and suggest using an array', async(() => { + it('should throw on non-iterable ref and suggest using an array', waitForAsync(() => { fixture = createTestComponent(); getComponent().items = 'whaaa'; @@ -123,7 +123,7 @@ let thisArg: any; /Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays/); })); - it('should throw on ref changing to string', async(() => { + it('should throw on ref changing to string', waitForAsync(() => { fixture = createTestComponent(); detectChangesAndExpectText('1;2;'); @@ -132,7 +132,7 @@ let thisArg: any; expect(() => fixture.detectChanges()).toThrowError(); })); - it('should works with duplicates', async(() => { + it('should works with duplicates', waitForAsync(() => { fixture = createTestComponent(); const a = new Foo(); @@ -140,7 +140,7 @@ let thisArg: any; detectChangesAndExpectText('foo;foo;'); })); - it('should repeat over nested arrays', async(() => { + it('should repeat over nested arrays', waitForAsync(() => { const template = '
' + '
{{subitem}}-{{item.length}};
|' + '
'; @@ -153,7 +153,7 @@ let thisArg: any; detectChangesAndExpectText('e-1;|f-2;g-2;|'); })); - it('should repeat over nested arrays with no intermediate element', async(() => { + it('should repeat over nested arrays with no intermediate element', waitForAsync(() => { const template = '
' + '
{{subitem}}-{{item.length}};
' + '
'; @@ -166,7 +166,8 @@ let thisArg: any; detectChangesAndExpectText('e-1;f-2;g-2;'); })); - it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => { + it('should repeat over nested ngIf that are the last node in the ngFor template', + waitForAsync(() => { const template = `
` + `
{{i}}|
` + `
even|
` + @@ -185,7 +186,7 @@ let thisArg: any; detectChangesAndExpectText('0|even|1|2|even|'); })); - it('should allow of saving the collection', async(() => { + it('should allow of saving the collection', waitForAsync(() => { const template = '
  • {{i}}/{{collection.length}} - {{item}};
'; fixture = createTestComponent(template); @@ -196,7 +197,7 @@ let thisArg: any; detectChangesAndExpectText('0/3 - 1;1/3 - 2;2/3 - 3;'); })); - it('should display indices correctly', async(() => { + it('should display indices correctly', waitForAsync(() => { const template = '{{i.toString()}}'; fixture = createTestComponent(template); @@ -207,7 +208,7 @@ let thisArg: any; detectChangesAndExpectText('0123456789'); })); - it('should display count correctly', async(() => { + it('should display count correctly', waitForAsync(() => { const template = '{{len}}'; fixture = createTestComponent(template); @@ -218,7 +219,7 @@ let thisArg: any; detectChangesAndExpectText('666666'); })); - it('should display first item correctly', async(() => { + it('should display first item correctly', waitForAsync(() => { const template = '{{isFirst.toString()}}'; fixture = createTestComponent(template); @@ -230,7 +231,7 @@ let thisArg: any; detectChangesAndExpectText('truefalse'); })); - it('should display last item correctly', async(() => { + it('should display last item correctly', waitForAsync(() => { const template = '{{isLast.toString()}}'; fixture = createTestComponent(template); @@ -242,7 +243,7 @@ let thisArg: any; detectChangesAndExpectText('falsetrue'); })); - it('should display even items correctly', async(() => { + it('should display even items correctly', waitForAsync(() => { const template = '{{isEven.toString()}}'; fixture = createTestComponent(template); @@ -254,7 +255,7 @@ let thisArg: any; detectChangesAndExpectText('truefalse'); })); - it('should display odd items correctly', async(() => { + it('should display odd items correctly', waitForAsync(() => { const template = '{{isOdd.toString()}}'; fixture = createTestComponent(template); @@ -266,7 +267,7 @@ let thisArg: any; detectChangesAndExpectText('falsetrue'); })); - it('should allow to use a custom template', async(() => { + it('should allow to use a custom template', waitForAsync(() => { const template = '' + '

{{i}}: {{item}};

'; @@ -276,7 +277,7 @@ let thisArg: any; detectChangesAndExpectText('0: a;1: b;2: c;'); })); - it('should use a default template if a custom one is null', async(() => { + it('should use a default template if a custom one is null', waitForAsync(() => { const template = `
    {{i}}: {{item}};
`; fixture = createTestComponent(template); @@ -285,7 +286,8 @@ let thisArg: any; detectChangesAndExpectText('0: a;1: b;2: c;'); })); - it('should use a custom template when both default and a custom one are present', async(() => { + it('should use a custom template when both default and a custom one are present', + waitForAsync(() => { const template = '{{i}};' + '{{i}}: {{item}};'; @@ -296,7 +298,7 @@ let thisArg: any; })); describe('track by', () => { - it('should console.warn if trackBy is not a function', async(() => { + it('should console.warn if trackBy is not a function', waitForAsync(() => { // TODO(vicb): expect a warning message when we have a proper log service const template = `

`; fixture = createTestComponent(template); @@ -304,7 +306,7 @@ let thisArg: any; fixture.detectChanges(); })); - it('should track by identity when trackBy is to `null` or `undefined`', async(() => { + it('should track by identity when trackBy is to `null` or `undefined`', waitForAsync(() => { // TODO(vicb): expect no warning message when we have a proper log service const template = `

{{ item }}

`; fixture = createTestComponent(template); @@ -315,7 +317,7 @@ let thisArg: any; detectChangesAndExpectText('abc'); })); - it('should set the context to the component instance', async(() => { + it('should set the context to the component instance', waitForAsync(() => { const template = `

`; fixture = createTestComponent(template); @@ -325,7 +327,7 @@ let thisArg: any; expect(thisArg).toBe(getComponent()); })); - it('should not replace tracked items', async(() => { + it('should not replace tracked items', waitForAsync(() => { const template = `

{{items[i]}}

`; fixture = createTestComponent(template); @@ -341,7 +343,7 @@ let thisArg: any; expect(finalP.nativeElement).toBe(firstP.nativeElement); })); - it('should update implicit local variable on view', async(() => { + it('should update implicit local variable on view', waitForAsync(() => { const template = `
{{item['color']}}
`; fixture = createTestComponent(template); @@ -353,7 +355,7 @@ let thisArg: any; detectChangesAndExpectText('red'); })); - it('should move items around and keep them updated ', async(() => { + it('should move items around and keep them updated ', waitForAsync(() => { const template = `
{{item['color']}}
`; fixture = createTestComponent(template); @@ -365,7 +367,8 @@ let thisArg: any; detectChangesAndExpectText('orangered'); })); - it('should handle added and removed items properly when tracking by index', async(() => { + it('should handle added and removed items properly when tracking by index', + waitForAsync(() => { const template = `
{{item}}
`; fixture = createTestComponent(template); diff --git a/packages/common/test/directives/ng_if_spec.ts b/packages/common/test/directives/ng_if_spec.ts index 38f526ec67..01f5d1e5a1 100644 --- a/packages/common/test/directives/ng_if_spec.ts +++ b/packages/common/test/directives/ng_if_spec.ts @@ -8,7 +8,7 @@ import {CommonModule, ɵgetDOM as getDOM} from '@angular/common'; import {Component} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -31,7 +31,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; }); }); - it('should work in a template attribute', async(() => { + it('should work in a template attribute', waitForAsync(() => { const template = 'hello'; fixture = createTestComponent(template); fixture.detectChanges(); @@ -39,14 +39,14 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText('hello'); })); - it('should work on a template element', async(() => { + it('should work on a template element', waitForAsync(() => { const template = 'hello2'; fixture = createTestComponent(template); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('hello2'); })); - it('should toggle node when condition changes', async(() => { + it('should toggle node when condition changes', waitForAsync(() => { const template = 'hello'; fixture = createTestComponent(template); getComponent().booleanCondition = false; @@ -65,7 +65,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText(''); })); - it('should handle nested if correctly', async(() => { + it('should handle nested if correctly', waitForAsync(() => { const template = '
hello
'; @@ -97,7 +97,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText(''); })); - it('should update several nodes with if', async(() => { + it('should update several nodes with if', waitForAsync(() => { const template = 'helloNumber' + 'helloString' + 'helloFunction'; @@ -120,7 +120,8 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText('helloNumber'); })); - it('should not add the element twice if the condition goes from truthy to truthy', async(() => { + it('should not add the element twice if the condition goes from truthy to truthy', + waitForAsync(() => { const template = 'hello'; fixture = createTestComponent(template); @@ -141,7 +142,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; })); describe('then/else templates', () => { - it('should support else', async(() => { + it('should support else', waitForAsync(() => { const template = 'TRUE' + 'FALSE'; @@ -155,7 +156,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText('FALSE'); })); - it('should support then and else', async(() => { + it('should support then and else', waitForAsync(() => { const template = 'IGNORE' + 'THEN' + @@ -202,7 +203,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText(''); }); - it('should support dynamic else', async(() => { + it('should support dynamic else', waitForAsync(() => { const template = 'TRUE' + 'FALSE1' + @@ -222,7 +223,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText('FALSE2'); })); - it('should support binding to variable using let', async(() => { + it('should support binding to variable using let', waitForAsync(() => { const template = '{{v}}' + '{{v}}'; @@ -236,7 +237,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText('false'); })); - it('should support binding to variable using as', async(() => { + it('should support binding to variable using as', waitForAsync(() => { const template = '{{v}}' + '{{v}}'; @@ -252,7 +253,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; }); describe('Type guarding', () => { - it('should throw when then block is not template', async(() => { + it('should throw when then block is not template', waitForAsync(() => { const template = 'IGNORE' + '
THEN
'; @@ -262,7 +263,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; .toThrowError(/ngIfThen must be a TemplateRef, but received/); })); - it('should throw when else block is not template', async(() => { + it('should throw when else block is not template', waitForAsync(() => { const template = 'IGNORE' + '
ELSE
'; diff --git a/packages/common/test/directives/ng_plural_spec.ts b/packages/common/test/directives/ng_plural_spec.ts index 677c3240ed..7d6ab99295 100644 --- a/packages/common/test/directives/ng_plural_spec.ts +++ b/packages/common/test/directives/ng_plural_spec.ts @@ -8,7 +8,7 @@ import {CommonModule, NgLocalization} from '@angular/common'; import {Component, Injectable} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; { @@ -36,7 +36,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; }); }); - it('should display the template according to the exact value', async(() => { + it('should display the template according to the exact value', waitForAsync(() => { const template = '
    ' + '
  • you have no messages.
  • ' + '
  • you have one message.
  • ' + @@ -51,7 +51,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; detectChangesAndExpectText('you have one message.'); })); - it('should display the template according to the exact numeric value', async(() => { + it('should display the template according to the exact numeric value', waitForAsync(() => { const template = '
    ' + '
      ' + '
    • you have no messages.
    • ' + @@ -69,7 +69,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; // https://github.com/angular/angular/issues/9868 // https://github.com/angular/angular/issues/9882 - it('should not throw when ngPluralCase contains expressions', async(() => { + it('should not throw when ngPluralCase contains expressions', waitForAsync(() => { const template = '
        ' + '
      • {{ switchValue }}
      • ' + '
      '; @@ -81,7 +81,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; })); - it('should be applicable to elements', async(() => { + it('should be applicable to elements', waitForAsync(() => { const template = '' + 'you have no messages.' + 'you have one message.' + @@ -96,7 +96,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; detectChangesAndExpectText('you have one message.'); })); - it('should display the template according to the category', async(() => { + it('should display the template according to the category', waitForAsync(() => { const template = '
        ' + '
      • you have a few messages.
      • ' + '
      • you have many messages.
      • ' + @@ -111,7 +111,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; detectChangesAndExpectText('you have many messages.'); })); - it('should default to other when no matches are found', async(() => { + it('should default to other when no matches are found', waitForAsync(() => { const template = '
          ' + '
        • you have a few messages.
        • ' + '
        • default message.
        • ' + @@ -123,7 +123,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; detectChangesAndExpectText('default message.'); })); - it('should prioritize value matches over category matches', async(() => { + it('should prioritize value matches over category matches', waitForAsync(() => { const template = '
            ' + '
          • you have a few messages.
          • ' + 'you have two messages.' + diff --git a/packages/common/test/directives/ng_style_spec.ts b/packages/common/test/directives/ng_style_spec.ts index 095e5034e3..e9fd9fee8c 100644 --- a/packages/common/test/directives/ng_style_spec.ts +++ b/packages/common/test/directives/ng_style_spec.ts @@ -8,7 +8,7 @@ import {CommonModule} from '@angular/common'; import {Component} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; { describe('NgStyle', () => { @@ -30,14 +30,14 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; TestBed.configureTestingModule({declarations: [TestComponent], imports: [CommonModule]}); }); - it('should add styles specified in an object literal', async(() => { + it('should add styles specified in an object literal', waitForAsync(() => { const template = `
            `; fixture = createTestComponent(template); fixture.detectChanges(); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); })); - it('should add and change styles specified in an object expression', async(() => { + it('should add and change styles specified in an object expression', waitForAsync(() => { const template = `
            `; fixture = createTestComponent(template); @@ -51,7 +51,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'}); })); - it('should add and remove styles specified using style.unit notation', async(() => { + it('should add and remove styles specified using style.unit notation', waitForAsync(() => { const template = `
            `; fixture = createTestComponent(template); @@ -66,7 +66,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); // https://github.com/angular/angular/issues/21064 - it('should add and remove styles which names are not dash-cased', async(() => { + it('should add and remove styles which names are not dash-cased', waitForAsync(() => { fixture = createTestComponent(`
            `); getComponent().expr = 'green'; @@ -78,7 +78,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; expectNativeEl(fixture).not.toHaveCssStyle('color'); })); - it('should update styles using style.unit notation when unit changes', async(() => { + it('should update styles using style.unit notation when unit changes', waitForAsync(() => { const template = `
            `; fixture = createTestComponent(template); @@ -93,7 +93,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); // keyValueDiffer is sensitive to key order #9115 - it('should change styles specified in an object expression', async(() => { + it('should change styles specified in an object expression', waitForAsync(() => { const template = `
            `; fixture = createTestComponent(template); @@ -117,7 +117,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; expectNativeEl(fixture).toHaveCssStyle({'height': '5px', 'width': '5px'}); })); - it('should remove styles when deleting a key in an object expression', async(() => { + it('should remove styles when deleting a key in an object expression', waitForAsync(() => { const template = `
            `; fixture = createTestComponent(template); @@ -131,7 +131,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; expectNativeEl(fixture).not.toHaveCssStyle('max-width'); })); - it('should co-operate with the style attribute', async(() => { + it('should co-operate with the style attribute', waitForAsync(() => { const template = `
            `; fixture = createTestComponent(template); @@ -147,7 +147,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; })); it('should co-operate with the style.[styleName]="expr" special-case in the compiler', - async(() => { + waitForAsync(() => { const template = `
            `; fixture = createTestComponent(template); diff --git a/packages/common/test/directives/ng_template_outlet_spec.ts b/packages/common/test/directives/ng_template_outlet_spec.ts index 7a6f05771a..865f8c40da 100644 --- a/packages/common/test/directives/ng_template_outlet_spec.ts +++ b/packages/common/test/directives/ng_template_outlet_spec.ts @@ -8,7 +8,7 @@ import {CommonModule} from '@angular/common'; import {Component, ContentChildren, Directive, Injectable, NO_ERRORS_SCHEMA, OnDestroy, QueryList, TemplateRef} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; describe('NgTemplateOutlet', () => { @@ -36,7 +36,7 @@ describe('NgTemplateOutlet', () => { }); // https://github.com/angular/angular/issues/14778 - it('should accept the component as the context', async(() => { + it('should accept the component as the context', waitForAsync(() => { const template = `` + `{{context.foo}}`; @@ -44,20 +44,20 @@ describe('NgTemplateOutlet', () => { detectChangesAndExpectText('bar'); })); - it('should do nothing if templateRef is `null`', async(() => { + it('should do nothing if templateRef is `null`', waitForAsync(() => { const template = ``; fixture = createTestComponent(template); detectChangesAndExpectText(''); })); - it('should insert content specified by TemplateRef', async(() => { + it('should insert content specified by TemplateRef', waitForAsync(() => { const template = `foo` + ``; fixture = createTestComponent(template); detectChangesAndExpectText('foo'); })); - it('should clear content if TemplateRef becomes `null`', async(() => { + it('should clear content if TemplateRef becomes `null`', waitForAsync(() => { const template = `foo` + ``; fixture = createTestComponent(template); @@ -71,7 +71,7 @@ describe('NgTemplateOutlet', () => { detectChangesAndExpectText(''); })); - it('should swap content if TemplateRef changes', async(() => { + it('should swap content if TemplateRef changes', waitForAsync(() => { const template = `foobar` + ``; @@ -87,14 +87,14 @@ describe('NgTemplateOutlet', () => { detectChangesAndExpectText('bar'); })); - it('should display template if context is `null`', async(() => { + it('should display template if context is `null`', waitForAsync(() => { const template = `foo` + ``; fixture = createTestComponent(template); detectChangesAndExpectText('foo'); })); - it('should reflect initial context and changes', async(() => { + it('should reflect initial context and changes', waitForAsync(() => { const template = `{{foo}}` + ``; fixture = createTestComponent(template); @@ -106,7 +106,7 @@ describe('NgTemplateOutlet', () => { detectChangesAndExpectText('alter-bar'); })); - it('should reflect user defined `$implicit` property in the context', async(() => { + it('should reflect user defined `$implicit` property in the context', waitForAsync(() => { const template = `{{ctx.foo}}` + ``; fixture = createTestComponent(template); @@ -114,7 +114,7 @@ describe('NgTemplateOutlet', () => { detectChangesAndExpectText('bra'); })); - it('should reflect context re-binding', async(() => { + it('should reflect context re-binding', waitForAsync(() => { const template = `{{shawshank}}` + ``; fixture = createTestComponent(template); @@ -222,7 +222,8 @@ describe('NgTemplateOutlet', () => { }).not.toThrow(); }); - it('should not throw when switching from template to null and back to template', async(() => { + it('should not throw when switching from template to null and back to template', + waitForAsync(() => { const template = `foo` + ``; fixture = createTestComponent(template); diff --git a/packages/common/test/directives/non_bindable_spec.ts b/packages/common/test/directives/non_bindable_spec.ts index a1105efbc3..34ee43177a 100644 --- a/packages/common/test/directives/non_bindable_spec.ts +++ b/packages/common/test/directives/non_bindable_spec.ts @@ -9,7 +9,7 @@ import {ɵgetDOM as getDOM} from '@angular/common'; import {Component, Directive} from '@angular/core'; import {ElementRef} from '@angular/core/src/linker/element_ref'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {hasClass} from '@angular/platform-browser/testing/src/browser_util'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -21,7 +21,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; }); }); - it('should not interpolate children', async(() => { + it('should not interpolate children', waitForAsync(() => { const template = '
            {{text}}{{text}}
            '; const fixture = createTestComponent(template); @@ -29,7 +29,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(fixture.nativeElement).toHaveText('foo{{text}}'); })); - it('should ignore directives on child nodes', async(() => { + it('should ignore directives on child nodes', waitForAsync(() => { const template = '
            {{text}}
            '; const fixture = createTestComponent(template); fixture.detectChanges(); @@ -40,7 +40,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; expect(hasClass(span, 'compiled')).toBeFalsy(); })); - it('should trigger directives on the same node', async(() => { + it('should trigger directives on the same node', waitForAsync(() => { const template = '
            {{text}}
            '; const fixture = createTestComponent(template); fixture.detectChanges(); diff --git a/packages/common/test/pipes/json_pipe_spec.ts b/packages/common/test/pipes/json_pipe_spec.ts index fafa2445d3..f557cca878 100644 --- a/packages/common/test/pipes/json_pipe_spec.ts +++ b/packages/common/test/pipes/json_pipe_spec.ts @@ -8,7 +8,7 @@ import {CommonModule, JsonPipe} from '@angular/common'; import {Component} from '@angular/core'; -import {async, TestBed} from '@angular/core/testing'; +import {TestBed, waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; { @@ -64,7 +64,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; TestBed.configureTestingModule({declarations: [TestComp], imports: [CommonModule]}); }); - it('should work with mutable objects', async(() => { + it('should work with mutable objects', waitForAsync(() => { const fixture = TestBed.createComponent(TestComp); const mutable: number[] = [1]; fixture.componentInstance.data = mutable; diff --git a/packages/common/test/pipes/slice_pipe_spec.ts b/packages/common/test/pipes/slice_pipe_spec.ts index a0832fd926..1ab7398c4d 100644 --- a/packages/common/test/pipes/slice_pipe_spec.ts +++ b/packages/common/test/pipes/slice_pipe_spec.ts @@ -8,7 +8,7 @@ import {CommonModule, SlicePipe} from '@angular/common'; import {Component} from '@angular/core'; -import {async, TestBed} from '@angular/core/testing'; +import {TestBed, waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; { @@ -105,7 +105,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; TestBed.configureTestingModule({declarations: [TestComp], imports: [CommonModule]}); }); - it('should work with mutable arrays', async(() => { + it('should work with mutable arrays', waitForAsync(() => { const fixture = TestBed.createComponent(TestComp); const mutable: number[] = [1, 2]; fixture.componentInstance.data = mutable; diff --git a/packages/compiler/test/i18n/integration_xliff2_spec.ts b/packages/compiler/test/i18n/integration_xliff2_spec.ts index 184f471bec..9258a4e19e 100644 --- a/packages/compiler/test/i18n/integration_xliff2_spec.ts +++ b/packages/compiler/test/i18n/integration_xliff2_spec.ts @@ -7,15 +7,15 @@ */ import {Xliff2} from '@angular/compiler/src/i18n/serializers/xliff2'; -import {async} from '@angular/core/testing'; +import {waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {configureCompiler, createComponent, HTML, serializeTranslations, validateHtml} from './integration_common'; describe('i18n XLIFF integration spec', () => { describe('(with LF line endings)', () => { - beforeEach( - async(() => configureCompiler(XLIFF2_TOMERGE + LF_LINE_ENDING_XLIFF2_TOMERGE, 'xlf2'))); + beforeEach(waitForAsync( + () => configureCompiler(XLIFF2_TOMERGE + LF_LINE_ENDING_XLIFF2_TOMERGE, 'xlf2'))); it('should extract from templates', () => { const serializer = new Xliff2(); @@ -34,8 +34,8 @@ describe('i18n XLIFF integration spec', () => { }); describe('(with CRLF line endings', () => { - beforeEach( - async(() => configureCompiler(XLIFF2_TOMERGE + CRLF_LINE_ENDING_XLIFF2_TOMERGE, 'xlf2'))); + beforeEach(waitForAsync( + () => configureCompiler(XLIFF2_TOMERGE + CRLF_LINE_ENDING_XLIFF2_TOMERGE, 'xlf2'))); it('should extract from templates (with CRLF line endings)', () => { const serializer = new Xliff2(); diff --git a/packages/compiler/test/i18n/integration_xliff_spec.ts b/packages/compiler/test/i18n/integration_xliff_spec.ts index 6889a0d368..a7b5d9be22 100644 --- a/packages/compiler/test/i18n/integration_xliff_spec.ts +++ b/packages/compiler/test/i18n/integration_xliff_spec.ts @@ -7,14 +7,15 @@ */ import {Xliff} from '@angular/compiler/src/i18n/serializers/xliff'; -import {async} from '@angular/core/testing'; +import {waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {configureCompiler, createComponent, HTML, serializeTranslations, validateHtml} from './integration_common'; describe('i18n XLIFF integration spec', () => { describe('(with LF line endings)', () => { - beforeEach(async(() => configureCompiler(XLIFF_TOMERGE + LF_LINE_ENDING_XLIFF_TOMERGE, 'xlf'))); + beforeEach( + waitForAsync(() => configureCompiler(XLIFF_TOMERGE + LF_LINE_ENDING_XLIFF_TOMERGE, 'xlf'))); it('should extract from templates', () => { const serializer = new Xliff(); @@ -33,8 +34,8 @@ describe('i18n XLIFF integration spec', () => { }); describe('(with CRLF line endings', () => { - beforeEach( - async(() => configureCompiler(XLIFF_TOMERGE + CRLF_LINE_ENDING_XLIFF_TOMERGE, 'xlf'))); + beforeEach(waitForAsync( + () => configureCompiler(XLIFF_TOMERGE + CRLF_LINE_ENDING_XLIFF_TOMERGE, 'xlf'))); it('should extract from templates (with CRLF line endings)', () => { const serializer = new Xliff(); diff --git a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts index 20fecb9681..a214cc55ad 100644 --- a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts +++ b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts @@ -7,14 +7,14 @@ */ import {Xmb} from '@angular/compiler/src/i18n/serializers/xmb'; -import {async} from '@angular/core/testing'; +import {waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {configureCompiler, createComponent, HTML, serializeTranslations, validateHtml} from './integration_common'; describe('i18n XMB/XTB integration spec', () => { describe('(with LF line endings)', () => { - beforeEach(async(() => configureCompiler(XTB + LF_LINE_ENDING_XTB, 'xtb'))); + beforeEach(waitForAsync(() => configureCompiler(XTB + LF_LINE_ENDING_XTB, 'xtb'))); it('should extract from templates', () => { const serializer = new Xmb(); @@ -33,7 +33,7 @@ describe('i18n XMB/XTB integration spec', () => { }); describe('(with CRLF line endings', () => { - beforeEach(async(() => configureCompiler(XTB + CRLF_LINE_ENDING_XTB, 'xtb'))); + beforeEach(waitForAsync(() => configureCompiler(XTB + CRLF_LINE_ENDING_XTB, 'xtb'))); it('should extract from templates (with CRLF line endings)', () => { const serializer = new Xmb(); diff --git a/packages/compiler/test/integration_spec.ts b/packages/compiler/test/integration_spec.ts index de24253a37..ef5ddcb69c 100644 --- a/packages/compiler/test/integration_spec.ts +++ b/packages/compiler/test/integration_spec.ts @@ -7,7 +7,7 @@ */ import {Component, Directive, Input} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -17,7 +17,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; let fixture: ComponentFixture; describe('directives', () => { - it('should support dotted selectors', async(() => { + it('should support dotted selectors', waitForAsync(() => { @Directive({selector: '[dot.name]'}) class MyDir { // TODO(issue/24571): remove '!'. @@ -41,7 +41,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; describe('ng-container', () => { if (browserDetection.isChromeDesktop) { - it('should work regardless the namespace', async(() => { + it('should work regardless the namespace', waitForAsync(() => { @Component({ selector: 'comp', template: diff --git a/packages/compiler/test/metadata_resolver_spec.ts b/packages/compiler/test/metadata_resolver_spec.ts index f97125e4bd..040d075b4c 100644 --- a/packages/compiler/test/metadata_resolver_spec.ts +++ b/packages/compiler/test/metadata_resolver_spec.ts @@ -8,7 +8,7 @@ import {LIFECYCLE_HOOKS_VALUES, LifecycleHooks} from '@angular/compiler/src/lifecycle_reflector'; import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, Component, Directive, DoCheck, Injectable, NgModule, OnChanges, OnDestroy, OnInit, Pipe, SimpleChanges, ViewEncapsulation, ɵstringify as stringify} from '@angular/core'; -import {async, inject, TestBed} from '@angular/core/testing'; +import {inject, TestBed, waitForAsync} from '@angular/core/testing'; import {CompileDiDependencyMetadata, identifierName} from '../src/compile_metadata'; import {CompileMetadataResolver} from '../src/metadata_resolver'; @@ -77,7 +77,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings'; })); it('should read external metadata when sync=false', - async(inject( + waitForAsync(inject( [CompileMetadataResolver, ResourceLoader], (resolver: CompileMetadataResolver, resourceLoader: MockResourceLoader) => { @NgModule({declarations: [ComponentWithExternalResources]}) @@ -96,7 +96,7 @@ import {TEST_COMPILER_PROVIDERS} from './test_bindings'; }))); it('should use `./` as base url for templates during runtime compilation if no moduleId is given', - async(inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { + waitForAsync(inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { @Component({selector: 'someComponent', templateUrl: 'someUrl'}) class ComponentWithoutModuleId { } diff --git a/packages/compiler/test/runtime_compiler_spec.ts b/packages/compiler/test/runtime_compiler_spec.ts index 1cdc932c09..ce37f36e9f 100644 --- a/packages/compiler/test/runtime_compiler_spec.ts +++ b/packages/compiler/test/runtime_compiler_spec.ts @@ -8,7 +8,7 @@ import {DirectiveResolver, ResourceLoader} from '@angular/compiler'; import {Compiler, Component, Injector, NgModule, NgModuleFactory, ɵstringify as stringify} from '@angular/core'; -import {async, fakeAsync, inject, TestBed, tick} from '@angular/core/testing'; +import {fakeAsync, inject, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {MockDirectiveResolver} from '../testing'; @@ -42,7 +42,8 @@ class SomeCompWithUrlTemplate { {providers: [{provide: ResourceLoader, useClass: StubResourceLoader, deps: []}]}); }); - it('should throw when using a templateUrl that has not been compiled before', async(() => { + it('should throw when using a templateUrl that has not been compiled before', + waitForAsync(() => { TestBed.configureTestingModule({declarations: [SomeCompWithUrlTemplate]}); TestBed.compileComponents().then(() => { expect(() => TestBed.createComponent(SomeCompWithUrlTemplate)) @@ -76,7 +77,8 @@ class SomeCompWithUrlTemplate { {providers: [{provide: ResourceLoader, useClass: StubResourceLoader, deps: []}]}); }); - it('should allow to use templateUrl components that have been loaded before', async(() => { + it('should allow to use templateUrl components that have been loaded before', + waitForAsync(() => { TestBed.configureTestingModule({declarations: [SomeCompWithUrlTemplate]}); TestBed.compileComponents().then(() => { const fixture = TestBed.createComponent(SomeCompWithUrlTemplate); diff --git a/packages/core/test/acceptance/providers_spec.ts b/packages/core/test/acceptance/providers_spec.ts index d4a5b97670..ec0be8cf2e 100644 --- a/packages/core/test/acceptance/providers_spec.ts +++ b/packages/core/test/acceptance/providers_spec.ts @@ -8,7 +8,7 @@ import {CommonModule} from '@angular/common'; import {Component, Directive, forwardRef, Inject, Injectable, InjectionToken, Injector, NgModule, Optional} from '@angular/core'; -import {async, inject, TestBed} from '@angular/core/testing'; +import {inject, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {modifiedInIvy, onlyInIvy} from '@angular/private/testing'; @@ -603,7 +603,7 @@ describe('providers', () => { }); it('should support injecting without bootstrapping', - async(inject([MyComp, MyService], (comp: MyComp, service: MyService) => { + waitForAsync(inject([MyComp, MyService], (comp: MyComp, service: MyService) => { expect(comp.svc.value).toEqual('some value'); }))); }); diff --git a/packages/core/test/application_init_spec.ts b/packages/core/test/application_init_spec.ts index bf22d267d0..ee078a606e 100644 --- a/packages/core/test/application_init_spec.ts +++ b/packages/core/test/application_init_spec.ts @@ -8,19 +8,19 @@ import {Injector} from '@angular/core'; import {APP_INITIALIZER, ApplicationInitStatus} from '@angular/core/src/application_init'; -import {async, inject, TestBed} from '../testing'; +import {inject, TestBed, waitForAsync} from '../testing'; { describe('ApplicationInitStatus', () => { describe('no initializers', () => { it('should return true for `done`', - async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => { + waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => { (status as any).runInitializers(); expect(status.done).toBe(true); }))); it('should return a promise that resolves immediately for `donePromise`', - async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => { + waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => { (status as any).runInitializers(); status.donePromise.then(() => { expect(status.done).toBe(true); @@ -58,7 +58,7 @@ import {async, inject, TestBed} from '../testing'; }); it('should update the status once all async initializers are done', - async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => { + waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => { (status as any).runInitializers(); setTimeout(() => { diff --git a/packages/core/test/application_ref_spec.ts b/packages/core/test/application_ref_spec.ts index ea432e8b45..2f97e91745 100644 --- a/packages/core/test/application_ref_spec.ts +++ b/packages/core/test/application_ref_spec.ts @@ -19,7 +19,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers'; import {onlyInIvy} from '@angular/private/testing'; import {NoopNgZone} from '../src/zone/ng_zone'; -import {async, ComponentFixtureNoNgZone, inject, TestBed, withModule} from '../testing'; +import {ComponentFixtureNoNgZone, inject, TestBed, waitForAsync, withModule} from '../testing'; @Component({selector: 'bootstrap-app', template: 'hello'}) class SomeComponent { @@ -79,62 +79,66 @@ class SomeComponent { } it('should bootstrap a component from a child module', - async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { - @Component({ - selector: 'bootstrap-app', - template: '', - }) - class SomeComponent { - } + waitForAsync( + inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { + @Component({ + selector: 'bootstrap-app', + template: '', + }) + class SomeComponent { + } - const helloToken = new InjectionToken('hello'); + const helloToken = new InjectionToken('hello'); - @NgModule({ - providers: [{provide: helloToken, useValue: 'component'}], - declarations: [SomeComponent], - entryComponents: [SomeComponent], - }) - class SomeModule { - } + @NgModule({ + providers: [{provide: helloToken, useValue: 'component'}], + declarations: [SomeComponent], + entryComponents: [SomeComponent], + }) + class SomeModule { + } - createRootEl(); - const modFactory = compiler.compileModuleSync(SomeModule); - const module = modFactory.create(TestBed); - const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!; - const component = app.bootstrap(cmpFactory); + createRootEl(); + const modFactory = compiler.compileModuleSync(SomeModule); + const module = modFactory.create(TestBed); + const cmpFactory = + module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!; + const component = app.bootstrap(cmpFactory); - // The component should see the child module providers - expect(component.injector.get(helloToken)).toEqual('component'); - }))); + // The component should see the child module providers + expect(component.injector.get(helloToken)).toEqual('component'); + }))); it('should bootstrap a component with a custom selector', - async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { - @Component({ - selector: 'bootstrap-app', - template: '', - }) - class SomeComponent { - } + waitForAsync( + inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { + @Component({ + selector: 'bootstrap-app', + template: '', + }) + class SomeComponent { + } - const helloToken = new InjectionToken('hello'); + const helloToken = new InjectionToken('hello'); - @NgModule({ - providers: [{provide: helloToken, useValue: 'component'}], - declarations: [SomeComponent], - entryComponents: [SomeComponent], - }) - class SomeModule { - } + @NgModule({ + providers: [{provide: helloToken, useValue: 'component'}], + declarations: [SomeComponent], + entryComponents: [SomeComponent], + }) + class SomeModule { + } - createRootEl('custom-selector'); - const modFactory = compiler.compileModuleSync(SomeModule); - const module = modFactory.create(TestBed); - const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!; - const component = app.bootstrap(cmpFactory, 'custom-selector'); + createRootEl('custom-selector'); + const modFactory = compiler.compileModuleSync(SomeModule); + const module = modFactory.create(TestBed); + const cmpFactory = + module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!; + const component = app.bootstrap(cmpFactory, 'custom-selector'); - // The component should see the child module providers - expect(component.injector.get(helloToken)).toEqual('component'); - }))); + // The component should see the child module providers + expect(component.injector.get(helloToken)).toEqual('component'); + }))); describe('ApplicationRef', () => { beforeEach(() => { @@ -216,7 +220,7 @@ class SomeComponent { defaultPlatform = _platform; })); - it('should wait for asynchronous app initializers', async(() => { + it('should wait for asynchronous app initializers', waitForAsync(() => { let resolve: (result: any) => void; const promise: Promise = new Promise((res) => { resolve = res; @@ -235,7 +239,8 @@ class SomeComponent { }); })); - it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => { + it('should rethrow sync errors even if the exceptionHandler is not rethrowing', + waitForAsync(() => { defaultPlatform .bootstrapModule(createModule([{ provide: APP_INITIALIZER, @@ -253,7 +258,7 @@ class SomeComponent { })); it('should rethrow promise errors even if the exceptionHandler is not rethrowing', - async(() => { + waitForAsync(() => { defaultPlatform .bootstrapModule(createModule([ {provide: APP_INITIALIZER, useValue: () => Promise.reject('Test'), multi: true} @@ -264,7 +269,7 @@ class SomeComponent { }); })); - it('should throw useful error when ApplicationRef is not configured', async(() => { + it('should throw useful error when ApplicationRef is not configured', waitForAsync(() => { @NgModule() class EmptyModule { } @@ -277,7 +282,7 @@ class SomeComponent { })); it('should call the `ngDoBootstrap` method with `ApplicationRef` on the main module', - async(() => { + waitForAsync(() => { const ngDoBootstrap = jasmine.createSpy('ngDoBootstrap'); defaultPlatform.bootstrapModule(createModule({ngDoBootstrap: ngDoBootstrap})) .then((moduleRef) => { @@ -286,7 +291,7 @@ class SomeComponent { }); })); - it('should auto bootstrap components listed in @NgModule.bootstrap', async(() => { + it('should auto bootstrap components listed in @NgModule.bootstrap', waitForAsync(() => { defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]})) .then((moduleRef) => { const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); @@ -295,7 +300,7 @@ class SomeComponent { })); it('should error if neither `ngDoBootstrap` nor @NgModule.bootstrap was specified', - async(() => { + waitForAsync(() => { defaultPlatform.bootstrapModule(createModule({ngDoBootstrap: false})) .then(() => expect(false).toBe(true), (e) => { const expectedErrMsg = @@ -305,12 +310,12 @@ class SomeComponent { }); })); - it('should add bootstrapped module into platform modules list', async(() => { + it('should add bootstrapped module into platform modules list', waitForAsync(() => { defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]})) .then(module => expect((defaultPlatform)._modules).toContain(module)); })); - it('should bootstrap with NoopNgZone', async(() => { + it('should bootstrap with NoopNgZone', waitForAsync(() => { defaultPlatform .bootstrapModule(createModule({bootstrap: [SomeComponent]}), {ngZone: 'noop'}) .then((module) => { @@ -376,7 +381,7 @@ class SomeComponent { createRootEl(); defaultPlatform = _platform; })); - it('should wait for asynchronous app initializers', async(() => { + it('should wait for asynchronous app initializers', waitForAsync(() => { let resolve: (result: any) => void; const promise: Promise = new Promise((res) => { resolve = res; @@ -396,7 +401,8 @@ class SomeComponent { }); })); - it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => { + it('should rethrow sync errors even if the exceptionHandler is not rethrowing', + waitForAsync(() => { const compilerFactory: CompilerFactory = defaultPlatform.injector.get(CompilerFactory, null)!; const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule([{ @@ -413,7 +419,7 @@ class SomeComponent { })); it('should rethrow promise errors even if the exceptionHandler is not rethrowing', - async(() => { + waitForAsync(() => { const compilerFactory: CompilerFactory = defaultPlatform.injector.get(CompilerFactory, null)!; const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule( @@ -623,25 +629,25 @@ class SomeComponent { }); } - it('isStable should fire on synchronous component loading', async(() => { + it('isStable should fire on synchronous component loading', waitForAsync(() => { expectStableTexts(SyncComp, ['1']); })); - it('isStable should fire after a microtask on init is completed', async(() => { + it('isStable should fire after a microtask on init is completed', waitForAsync(() => { expectStableTexts(MicroTaskComp, ['11']); })); - it('isStable should fire after a macrotask on init is completed', async(() => { + it('isStable should fire after a macrotask on init is completed', waitForAsync(() => { expectStableTexts(MacroTaskComp, ['11']); })); it('isStable should fire only after chain of micro and macrotasks on init are completed', - async(() => { + waitForAsync(() => { expectStableTexts(MicroMacroTaskComp, ['111']); })); it('isStable should fire only after chain of macro and microtasks on init are completed', - async(() => { + waitForAsync(() => { expectStableTexts(MacroMicroTaskComp, ['111']); })); @@ -665,7 +671,7 @@ class SomeComponent { }); } - it('should be fired after app becomes unstable', async(() => { + it('should be fired after app becomes unstable', waitForAsync(() => { const fixture = TestBed.createComponent(ClickComp); const appRef: ApplicationRef = TestBed.inject(ApplicationRef); const zone: NgZone = TestBed.inject(NgZone); diff --git a/packages/core/test/component_fixture_spec.ts b/packages/core/test/component_fixture_spec.ts index a889ecd15f..41b398b6d1 100644 --- a/packages/core/test/component_fixture_spec.ts +++ b/packages/core/test/component_fixture_spec.ts @@ -7,7 +7,7 @@ */ import {Component, Injectable, Input} from '@angular/core'; -import {async, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBed, withModule} from '@angular/core/testing'; +import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBed, waitForAsync, withModule} from '@angular/core/testing'; import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -100,7 +100,7 @@ class NestedAsyncTimeoutComp { { describe('ComponentFixture', () => { - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ AutoDetectComp, AsyncComp, AsyncTimeoutComp, NestedAsyncTimeoutComp, AsyncChangeComp, @@ -134,7 +134,7 @@ class NestedAsyncTimeoutComp { })); it('should signal through whenStable when the fixture is stable (autoDetectChanges)', - async(() => { + waitForAsync(() => { const componentFixture = TestBed.createComponent(AsyncComp); componentFixture.autoDetectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); @@ -153,7 +153,7 @@ class NestedAsyncTimeoutComp { })); it('should signal through isStable when the fixture is stable (no autoDetectChanges)', - async(() => { + waitForAsync(() => { const componentFixture = TestBed.createComponent(AsyncComp); componentFixture.detectChanges(); @@ -174,7 +174,7 @@ class NestedAsyncTimeoutComp { it('should wait for macroTask(setTimeout) while checking for whenStable ' + '(autoDetectChanges)', - async(() => { + waitForAsync(() => { const componentFixture = TestBed.createComponent(AsyncTimeoutComp); componentFixture.autoDetectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); @@ -194,7 +194,7 @@ class NestedAsyncTimeoutComp { it('should wait for macroTask(setTimeout) while checking for whenStable ' + '(no autoDetectChanges)', - async(() => { + waitForAsync(() => { const componentFixture = TestBed.createComponent(AsyncTimeoutComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); @@ -215,7 +215,7 @@ class NestedAsyncTimeoutComp { it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' + '(autoDetectChanges)', - async(() => { + waitForAsync(() => { const componentFixture = TestBed.createComponent(NestedAsyncTimeoutComp); componentFixture.autoDetectChanges(); @@ -236,7 +236,7 @@ class NestedAsyncTimeoutComp { it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' + '(no autoDetectChanges)', - async(() => { + waitForAsync(() => { const componentFixture = TestBed.createComponent(NestedAsyncTimeoutComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('1'); @@ -255,7 +255,8 @@ class NestedAsyncTimeoutComp { }); })); - it('should stabilize after async task in change detection (autoDetectChanges)', async(() => { + it('should stabilize after async task in change detection (autoDetectChanges)', + waitForAsync(() => { const componentFixture = TestBed.createComponent(AsyncChangeComp); componentFixture.autoDetectChanges(); @@ -271,7 +272,8 @@ class NestedAsyncTimeoutComp { }); })); - it('should stabilize after async task in change detection(no autoDetectChanges)', async(() => { + it('should stabilize after async task in change detection(no autoDetectChanges)', + waitForAsync(() => { const componentFixture = TestBed.createComponent(AsyncChangeComp); componentFixture.detectChanges(); componentFixture.whenStable().then((_) => { @@ -306,7 +308,7 @@ class NestedAsyncTimeoutComp { }).toThrowError(/Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set/); }); - it('should instantiate a component with valid DOM', async(() => { + it('should instantiate a component with valid DOM', waitForAsync(() => { const componentFixture = TestBed.createComponent(SimpleComp); expect(componentFixture.ngZone).toBeNull(); @@ -314,7 +316,7 @@ class NestedAsyncTimeoutComp { expect(componentFixture.nativeElement).toHaveText('Original Simple'); })); - it('should allow changing members of the component', async(() => { + it('should allow changing members of the component', waitForAsync(() => { const componentFixture = TestBed.createComponent(MyIfComp); componentFixture.detectChanges(); diff --git a/packages/core/test/debug/debug_node_spec.ts b/packages/core/test/debug/debug_node_spec.ts index 63704324c1..af6ffc0717 100644 --- a/packages/core/test/debug/debug_node_spec.ts +++ b/packages/core/test/debug/debug_node_spec.ts @@ -10,7 +10,7 @@ import {CommonModule, NgIfContext, ɵgetDOM as getDOM} from '@angular/common'; import {Component, DebugElement, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; import {NgZone} from '@angular/core/src/zone'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {createMouseEvent, hasClass} from '@angular/platform-browser/testing/src/browser_util'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -264,7 +264,7 @@ class TestCmptWithPropInterpolation { describe('debug element', () => { let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ ChildComp, diff --git a/packages/core/test/linker/integration_spec.ts b/packages/core/test/linker/integration_spec.ts index 137268b641..46fd11e60f 100644 --- a/packages/core/test/linker/integration_spec.ts +++ b/packages/core/test/linker/integration_spec.ts @@ -17,7 +17,7 @@ import {TemplateRef} from '@angular/core/src/linker/template_ref'; import {ViewContainerRef} from '@angular/core/src/linker/view_container_ref'; import {EmbeddedViewRef} from '@angular/core/src/linker/view_ref'; import {Attribute, Component, ContentChildren, Directive, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata'; -import {async, fakeAsync, getTestBed, TestBed, tick} from '@angular/core/testing'; +import {fakeAsync, getTestBed, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {createMouseEvent, dispatchEvent, el, isCommentNode} from '@angular/platform-browser/testing/src/browser_util'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing'; @@ -757,7 +757,7 @@ function declareTests(config?: {useJit: boolean}) { expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective); }); - it('should support events via EventEmitter on regular elements', async(() => { + it('should support events via EventEmitter on regular elements', waitForAsync(() => { TestBed.configureTestingModule( {declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]}); const template = '
            '; @@ -787,7 +787,7 @@ function declareTests(config?: {useJit: boolean}) { emitter.fireEvent('fired !'); })); - it('should support events via EventEmitter on template elements', async(() => { + it('should support events via EventEmitter on template elements', waitForAsync(() => { const fixture = TestBed .configureTestingModule( @@ -819,7 +819,7 @@ function declareTests(config?: {useJit: boolean}) { emitter.fireEvent('fired !'); })); - it('should support [()] syntax', async(() => { + it('should support [()] syntax', waitForAsync(() => { TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTwoWayBinding]}); const template = '
            '; TestBed.overrideComponent(MyComp, {set: {template}}); @@ -1072,7 +1072,7 @@ function declareTests(config?: {useJit: boolean}) { }); describe('.createComponent', () => { - it('should allow to create a component at any bound location', async(() => { + it('should allow to create a component at any bound location', waitForAsync(() => { const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) .createComponent(MyComp); const tc = fixture.debugElement.children[0].children[0]; @@ -1083,7 +1083,7 @@ function declareTests(config?: {useJit: boolean}) { .toHaveText('dynamic greet'); })); - it('should allow to create multiple components at a location', async(() => { + it('should allow to create multiple components at a location', waitForAsync(() => { const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) .createComponent(MyComp); const tc = fixture.debugElement.children[0].children[0]; @@ -1219,7 +1219,7 @@ function declareTests(config?: {useJit: boolean}) { }); describe('.insert', () => { - it('should throw with destroyed views', async(() => { + it('should throw with destroyed views', waitForAsync(() => { const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) .createComponent(MyComp); const tc = fixture.debugElement.children[0].children[0]; @@ -1235,7 +1235,7 @@ function declareTests(config?: {useJit: boolean}) { }); describe('.move', () => { - it('should throw with destroyed views', async(() => { + it('should throw with destroyed views', waitForAsync(() => { const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}) .createComponent(MyComp); const tc = fixture.debugElement.children[0].children[0]; @@ -2001,7 +2001,7 @@ function declareTests(config?: {useJit: boolean}) { }); describe('whitespaces in templates', () => { - it('should not remove whitespaces by default', async(() => { + it('should not remove whitespaces by default', waitForAsync(() => { @Component({ selector: 'comp', template: 'foo bar', @@ -2015,7 +2015,8 @@ function declareTests(config?: {useJit: boolean}) { expect(f.nativeElement.childNodes.length).toBe(2); })); - it('should not remove whitespaces when explicitly requested not to do so', async(() => { + it('should not remove whitespaces when explicitly requested not to do so', + waitForAsync(() => { @Component({ selector: 'comp', template: 'foo bar', @@ -2030,7 +2031,7 @@ function declareTests(config?: {useJit: boolean}) { expect(f.nativeElement.childNodes.length).toBe(3); })); - it('should remove whitespaces when explicitly requested to do so', async(() => { + it('should remove whitespaces when explicitly requested to do so', waitForAsync(() => { @Component({ selector: 'comp', template: 'foo bar', diff --git a/packages/core/test/linker/jit_summaries_integration_spec.ts b/packages/core/test/linker/jit_summaries_integration_spec.ts index 5b4d366087..5c4f18b967 100644 --- a/packages/core/test/linker/jit_summaries_integration_spec.ts +++ b/packages/core/test/linker/jit_summaries_integration_spec.ts @@ -10,7 +10,7 @@ import {ResourceLoader} from '@angular/compiler'; import {CompileMetadataResolver} from '@angular/compiler/src/metadata_resolver'; import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock'; import {Component, Directive, Injectable, NgModule, OnDestroy, Pipe} from '@angular/core'; -import {async, getTestBed, TestBed} from '@angular/core/testing'; +import {getTestBed, TestBed, waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {obsoleteInIvy} from '@angular/private/testing'; @@ -135,7 +135,7 @@ import {obsoleteInIvy} from '@angular/private/testing'; SomeService.annotations = []; } - beforeEach(async(() => { + beforeEach(waitForAsync(() => { instances = new Map(); createSummaries().then(s => summaries = s); })); diff --git a/packages/core/test/linker/query_integration_spec.ts b/packages/core/test/linker/query_integration_spec.ts index 98a34fcde5..fe5ced8f36 100644 --- a/packages/core/test/linker/query_integration_spec.ts +++ b/packages/core/test/linker/query_integration_spec.ts @@ -8,7 +8,7 @@ import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, asNativeElements, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core'; import {ElementRef} from '@angular/core/src/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {ivyEnabled, modifiedInIvy, onlyInIvy} from '@angular/private/testing'; import {Subject} from 'rxjs'; @@ -414,7 +414,7 @@ describe('Query API', () => { }); describe('changes', () => { - it('should notify query on change', async(() => { + it('should notify query on change', waitForAsync(() => { const template = '' + '
            ' + '
            ' + diff --git a/packages/core/test/linker/system_ng_module_factory_loader_spec.ts b/packages/core/test/linker/system_ng_module_factory_loader_spec.ts index e55f1cfd20..a3b780e038 100644 --- a/packages/core/test/linker/system_ng_module_factory_loader_spec.ts +++ b/packages/core/test/linker/system_ng_module_factory_loader_spec.ts @@ -8,7 +8,7 @@ import {Compiler, SystemJsNgModuleLoader} from '@angular/core'; import {global} from '@angular/core/src/util/global'; -import {async} from '@angular/core/testing'; +import {waitForAsync} from '@angular/core/testing'; import {afterEach, beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal'; import {modifiedInIvy, onlyInIvy} from '@angular/private/testing'; @@ -36,19 +36,19 @@ describe('SystemJsNgModuleLoader', () => { global['System'] = oldSystem; }); - it('loads a default factory by appending the factory suffix', async(() => { + it('loads a default factory by appending the factory suffix', waitForAsync(() => { const loader = new SystemJsNgModuleLoader(new Compiler()); loader.load('test').then(contents => { expect(contents).toBe('test module factory' as any); }); })); - it('loads a named factory by appending the factory suffix', async(() => { + it('loads a named factory by appending the factory suffix', waitForAsync(() => { const loader = new SystemJsNgModuleLoader(new Compiler()); loader.load('test#Named').then(contents => { expect(contents).toBe('test NamedNgFactory' as any); }); })); - it('loads a named factory with a configured prefix and suffix', async(() => { + it('loads a named factory with a configured prefix and suffix', waitForAsync(() => { const loader = new SystemJsNgModuleLoader(new Compiler(), { factoryPathPrefix: 'prefixed/', factoryPathSuffix: '/suffixed', @@ -70,13 +70,13 @@ describe('SystemJsNgModuleLoader', () => { global['System'] = oldSystem; }); - it('loads a default module', async(() => { + it('loads a default module', waitForAsync(() => { const loader = new SystemJsNgModuleLoader(new Compiler()); loader.load('test').then(contents => { expect(contents.moduleType).toBe('test module' as any); }); })); - it('loads a named module', async(() => { + it('loads a named module', waitForAsync(() => { const loader = new SystemJsNgModuleLoader(new Compiler()); loader.load('test#NamedModule').then(contents => { expect(contents.moduleType).toBe('test NamedModule' as any); diff --git a/packages/core/test/testability/testability_spec.ts b/packages/core/test/testability/testability_spec.ts index c70f42620e..d3415a85e8 100644 --- a/packages/core/test/testability/testability_spec.ts +++ b/packages/core/test/testability/testability_spec.ts @@ -10,7 +10,7 @@ import {EventEmitter} from '@angular/core'; import {Injectable} from '@angular/core/src/di'; import {PendingMacrotask, Testability, TestabilityRegistry} from '@angular/core/src/testability/testability'; import {NgZone} from '@angular/core/src/zone/ng_zone'; -import {async, fakeAsync, flush, tick} from '@angular/core/testing'; +import {fakeAsync, flush, tick, waitForAsync} from '@angular/core/testing'; import {beforeEach, describe, expect, it, SpyObject} from '@angular/core/testing/src/testing_internal'; import {scheduleMicroTask} from '../../src/util/microtask'; @@ -55,7 +55,7 @@ class MockNgZone extends NgZone { let updateCallback: any; let ngZone: MockNgZone; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { ngZone = new MockNgZone(); testability = new Testability(ngZone); execute = new SpyObject().spy('execute'); @@ -68,7 +68,7 @@ class MockNgZone extends NgZone { expect(testability.getPendingRequestCount()).toEqual(0); }); - it('should fire whenstable callbacks if pending count is 0', async(() => { + it('should fire whenstable callbacks if pending count is 0', waitForAsync(() => { testability.whenStable(execute); microTask(() => { @@ -81,7 +81,7 @@ class MockNgZone extends NgZone { expect(execute).not.toHaveBeenCalled(); }); - it('should not call whenstable callbacks when there are pending counts', async(() => { + it('should not call whenstable callbacks when there are pending counts', waitForAsync(() => { testability.increasePendingRequestCount(); testability.increasePendingRequestCount(); testability.whenStable(execute); @@ -96,7 +96,7 @@ class MockNgZone extends NgZone { }); })); - it('should fire whenstable callbacks when pending drops to 0', async(() => { + it('should fire whenstable callbacks when pending drops to 0', waitForAsync(() => { testability.increasePendingRequestCount(); testability.whenStable(execute); @@ -110,7 +110,8 @@ class MockNgZone extends NgZone { }); })); - it('should not fire whenstable callbacks synchronously when pending drops to 0', async(() => { + it('should not fire whenstable callbacks synchronously when pending drops to 0', + waitForAsync(() => { testability.increasePendingRequestCount(); testability.whenStable(execute); testability.decreasePendingRequestCount(); @@ -118,7 +119,7 @@ class MockNgZone extends NgZone { expect(execute).not.toHaveBeenCalled(); })); - it('should fire whenstable callbacks with didWork if pending count is 0', async(() => { + it('should fire whenstable callbacks with didWork if pending count is 0', waitForAsync(() => { microTask(() => { testability.whenStable(execute); @@ -128,7 +129,8 @@ class MockNgZone extends NgZone { }); })); - it('should fire whenstable callbacks with didWork when pending drops to 0', async(() => { + it('should fire whenstable callbacks with didWork when pending drops to 0', + waitForAsync(() => { testability.increasePendingRequestCount(); testability.whenStable(execute); @@ -165,7 +167,7 @@ class MockNgZone extends NgZone { clearTimeout(id); })); - it('should fire if Angular is already stable', async(() => { + it('should fire if Angular is already stable', waitForAsync(() => { testability.whenStable(execute, 200); microTask(() => { @@ -363,7 +365,7 @@ class MockNgZone extends NgZone { let registry: TestabilityRegistry; let ngZone: MockNgZone; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { ngZone = new MockNgZone(); testability1 = new Testability(ngZone); testability2 = new Testability(ngZone); diff --git a/packages/core/test/zone/ng_zone_spec.ts b/packages/core/test/zone/ng_zone_spec.ts index 34655fb433..fef986e01f 100644 --- a/packages/core/test/zone/ng_zone_spec.ts +++ b/packages/core/test/zone/ng_zone_spec.ts @@ -7,7 +7,7 @@ */ import {EventEmitter, NgZone} from '@angular/core'; -import {async, fakeAsync, flushMicrotasks} from '@angular/core/testing'; +import {fakeAsync, flushMicrotasks, waitForAsync} from '@angular/core/testing'; import {AsyncTestCompleter, beforeEach, describe, expect, inject, it, Log, xit} from '@angular/core/testing/src/testing_internal'; import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; @@ -914,7 +914,7 @@ function commonTests() { asyncResult = null!; }); - it('should async even if the NgZone was created outside.', async(() => { + it('should async even if the NgZone was created outside.', waitForAsync(() => { // try to escape the current async zone by using NgZone which was created outside. ngZone.run(() => { setTimeout(() => { diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index 9addf0dd37..316a3c86a8 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {async, fakeAsync, tick} from '@angular/core/testing'; +import {fakeAsync, tick, waitForAsync} from '@angular/core/testing'; import {AsyncTestCompleter, beforeEach, describe, inject, it} from '@angular/core/testing/src/testing_internal'; import {AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, Validators} from '@angular/forms'; import {of} from 'rxjs'; @@ -726,7 +726,7 @@ describe('FormGroup', () => { let control: FormControl; let group: FormGroup; - beforeEach(async(() => { + beforeEach(waitForAsync(() => { control = new FormControl('', asyncValidatorReturningObservable); group = new FormGroup({'one': control}); })); diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts index ed85da7131..6f77c5698f 100644 --- a/packages/forms/test/template_integration_spec.ts +++ b/packages/forms/test/template_integration_spec.ts @@ -8,7 +8,7 @@ import {ɵgetDOM as getDOM} from '@angular/common'; import {Component, Directive, forwardRef, Type} from '@angular/core'; -import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; +import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormControl, FormsModule, NG_ASYNC_VALIDATORS, NgForm, NgModel} from '@angular/forms'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {dispatchEvent, sortedClassList} from '@angular/platform-browser/testing/src/browser_util'; @@ -168,7 +168,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat expect(form.value).toEqual({}); })); - it('should set status classes with ngModel', async(() => { + it('should set status classes with ngModel', waitForAsync(() => { const fixture = initTest(NgModelForm); fixture.componentInstance.name = 'aa'; fixture.detectChanges(); @@ -212,7 +212,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat }); })); - it('should set status classes with ngModelGroup and ngForm', async(() => { + it('should set status classes with ngModelGroup and ngForm', waitForAsync(() => { const fixture = initTest(NgModelGroupForm); fixture.componentInstance.first = ''; fixture.detectChanges(); @@ -1179,7 +1179,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat expect(input.nativeElement.disabled).toBe(true); })); - it('should disable a custom control if disabled attr is added', async(() => { + it('should disable a custom control if disabled attr is added', waitForAsync(() => { const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp); fixture.componentInstance.name = 'Nancy'; fixture.componentInstance.isDisabled = true; diff --git a/packages/forms/test/value_accessor_integration_spec.ts b/packages/forms/test/value_accessor_integration_spec.ts index f6cce04653..23e1e23f8f 100644 --- a/packages/forms/test/value_accessor_integration_spec.ts +++ b/packages/forms/test/value_accessor_integration_spec.ts @@ -7,7 +7,7 @@ */ import {Component, Directive, EventEmitter, Input, Output, Type, ViewChild} from '@angular/core'; -import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; +import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, NgModel, ReactiveFormsModule, Validators} from '@angular/forms'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; @@ -1078,7 +1078,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util' }); describe('in template-driven forms', () => { - it('should support standard writing to view and model', async(() => { + it('should support standard writing to view and model', waitForAsync(() => { const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp); fixture.componentInstance.name = 'Nancy'; fixture.detectChanges(); diff --git a/packages/misc/angular-in-memory-web-api/test/http-client-backend-service_spec.ts b/packages/misc/angular-in-memory-web-api/test/http-client-backend-service_spec.ts index 8b45bffdb5..9f727b0c16 100644 --- a/packages/misc/angular-in-memory-web-api/test/http-client-backend-service_spec.ts +++ b/packages/misc/angular-in-memory-web-api/test/http-client-backend-service_spec.ts @@ -10,7 +10,7 @@ import {HTTP_INTERCEPTORS, HttpBackend, HttpClient, HttpClientModule, HttpEvent, HttpEventType, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; import {Injectable} from '@angular/core'; -import {async, TestBed} from '@angular/core/testing'; +import {TestBed, waitForAsync} from '@angular/core/testing'; import {HttpClientBackendService, HttpClientInMemoryWebApiModule} from 'angular-in-memory-web-api'; import {Observable, zip} from 'rxjs'; import {concatMap, map, tap} from 'rxjs/operators'; @@ -37,14 +37,14 @@ describe('HttpClient Backend Service', () => { http = TestBed.get(HttpClient); }); - it('can get heroes', async(() => { + it('can get heroes', waitForAsync(() => { http.get('api/heroes') .subscribe( heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'), failRequest); })); - it('GET should be a "cold" observable', async(() => { + it('GET should be a "cold" observable', waitForAsync(() => { const httpBackend = TestBed.get(HttpBackend); const spy = spyOn(httpBackend, 'collectionHandler').and.callThrough(); const get$ = http.get('api/heroes'); @@ -58,7 +58,7 @@ describe('HttpClient Backend Service', () => { }, failRequest); })); - it('GET should wait until after delay to respond', async(() => { + it('GET should wait until after delay to respond', waitForAsync(() => { // to make test fail, set `delay=0` above let gotResponse = false; @@ -70,7 +70,7 @@ describe('HttpClient Backend Service', () => { expect(gotResponse).toBe(false, 'should delay before response'); })); - it('Should only initialize the db once', async(() => { + it('Should only initialize the db once', waitForAsync(() => { const httpBackend = TestBed.get(HttpBackend); const spy = spyOn(httpBackend, 'resetDb').and.callThrough(); @@ -86,13 +86,13 @@ describe('HttpClient Backend Service', () => { expect(spy.calls.count()).toBe(1); })); - it('can get heroes (w/ a different base path)', async(() => { + it('can get heroes (w/ a different base path)', waitForAsync(() => { http.get('some-base-path/heroes').subscribe(heroes => { expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); }, failRequest); })); - it('should 404 when GET unknown collection (after delay)', async(() => { + it('should 404 when GET unknown collection (after delay)', waitForAsync(() => { let gotError = false; const url = 'api/unknown-collection'; http.get(url).subscribe( @@ -104,46 +104,46 @@ describe('HttpClient Backend Service', () => { expect(gotError).toBe(false, 'should not get error until after delay'); })); - it('should return the hero w/id=1 for GET app/heroes/1', async(() => { + it('should return the hero w/id=1 for GET app/heroes/1', waitForAsync(() => { http.get('api/heroes/1') .subscribe( hero => expect(hero).toBeDefined('should find hero with id=1'), failRequest); })); // test where id is string that looks like a number - it('should return the stringer w/id="10" for GET app/stringers/10', async(() => { + it('should return the stringer w/id="10" for GET app/stringers/10', waitForAsync(() => { http.get('api/stringers/10') .subscribe( hero => expect(hero).toBeDefined('should find string with id="10"'), failRequest); })); - it('should return 1-item array for GET app/heroes/?id=1', async(() => { + it('should return 1-item array for GET app/heroes/?id=1', waitForAsync(() => { http.get('api/heroes/?id=1') .subscribe( heroes => expect(heroes.length).toBe(1, 'should find one hero w/id=1'), failRequest); })); - it('should return 1-item array for GET app/heroes?id=1', async(() => { + it('should return 1-item array for GET app/heroes?id=1', waitForAsync(() => { http.get('api/heroes?id=1') .subscribe( heroes => expect(heroes.length).toBe(1, 'should find one hero w/id=1'), failRequest); })); - it('should return undefined for GET app/heroes?id=not-found-id', async(() => { + it('should return undefined for GET app/heroes?id=not-found-id', waitForAsync(() => { http.get('api/heroes?id=123456') .subscribe(heroes => expect(heroes.length).toBe(0), failRequest); })); - it('should return 404 for GET app/heroes/not-found-id', async(() => { + it('should return 404 for GET app/heroes/not-found-id', waitForAsync(() => { const url = 'api/heroes/123456'; http.get(url).subscribe( () => fail(`should not have found data for '${url}'`), err => expect(err.status).toBe(404, 'should have 404 status')); })); - it('can generate the id when add a hero with no id', async(() => { + it('can generate the id when add a hero with no id', waitForAsync(() => { const hero = new Hero(undefined, 'SuperDooper'); http.post('api/heroes', hero).subscribe(replyHero => { expect(replyHero.id).toBeDefined('added hero should have an id'); @@ -151,13 +151,13 @@ describe('HttpClient Backend Service', () => { }, failRequest); })); - it('can get nobodies (empty collection)', async(() => { + it('can get nobodies (empty collection)', waitForAsync(() => { http.get('api/nobodies').subscribe(nobodies => { expect(nobodies.length).toBe(0, 'should have no nobodies'); }, failRequest); })); - it('can add a nobody with an id to empty nobodies collection', async(() => { + it('can add a nobody with an id to empty nobodies collection', waitForAsync(() => { const id = 'g-u-i-d'; http.post('api/nobodies', {id, name: 'Noman'}) @@ -169,7 +169,8 @@ describe('HttpClient Backend Service', () => { }, failRequest); })); - it('should fail when add a nobody without an id to empty nobodies collection', async(() => { + it('should fail when add a nobody without an id to empty nobodies collection', + waitForAsync(() => { http.post('api/nobodies', {name: 'Noman'}) .subscribe( () => fail(`should not have been able to add 'Norman' to 'nobodies'`), err => { @@ -179,11 +180,11 @@ describe('HttpClient Backend Service', () => { })); describe('can reset the database', () => { - it('to empty (object db)', async(() => resetDatabaseTest('object'))); + it('to empty (object db)', waitForAsync(() => resetDatabaseTest('object'))); - it('to empty (observable db)', async(() => resetDatabaseTest('observable'))); + it('to empty (observable db)', waitForAsync(() => resetDatabaseTest('observable'))); - it('to empty (promise db)', async(() => resetDatabaseTest('promise'))); + it('to empty (promise db)', waitForAsync(() => resetDatabaseTest('promise'))); function resetDatabaseTest(returnType: string) { // Observable of the number of heroes and nobodies @@ -223,28 +224,29 @@ describe('HttpClient Backend Service', () => { http = TestBed.get(HttpClient); }); - it('can get heroes', async(() => { + it('can get heroes', waitForAsync(() => { http.get('api/heroes') .subscribe( heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'), failRequest); })); - it('can translate `foo/heroes` to `heroes` via `parsedRequestUrl` override', async(() => { + it('can translate `foo/heroes` to `heroes` via `parsedRequestUrl` override', + waitForAsync(() => { http.get('api/foo/heroes') .subscribe( heroes => expect(heroes.length).toBeGreaterThan(0, 'should have heroes'), failRequest); })); - it('can get villains', async(() => { + it('can get villains', waitForAsync(() => { http.get('api/villains') .subscribe( villains => expect(villains.length).toBeGreaterThan(0, 'should have villains'), failRequest); })); - it('should 404 when POST to villains', async(() => { + it('should 404 when POST to villains', waitForAsync(() => { const url = 'api/villains'; http.post(url, {id: 42, name: 'Dr. Evil'}) .subscribe( @@ -252,14 +254,14 @@ describe('HttpClient Backend Service', () => { err => expect(err.status).toBe(404, 'should have 404 status')); })); - it('should 404 when GET unknown collection', async(() => { + it('should 404 when GET unknown collection', waitForAsync(() => { const url = 'api/unknown-collection'; http.get(url).subscribe( () => fail(`should not have found data for '${url}'`), err => expect(err.status).toBe(404, 'should have 404 status')); })); - it('should use genId override to add new hero, "Maxinius"', async(() => { + it('should use genId override to add new hero, "Maxinius"', waitForAsync(() => { http.post('api/heroes', {name: 'Maxinius'}) .pipe(concatMap(() => http.get('api/heroes?name=Maxi'))) .subscribe(heroes => { @@ -269,7 +271,8 @@ describe('HttpClient Backend Service', () => { }, failRequest); })); - it('should use genId override guid generator for a new nobody without an id', async(() => { + it('should use genId override guid generator for a new nobody without an id', + waitForAsync(() => { http.post('api/nobodies', {name: 'Noman'}) .pipe(concatMap(() => http.get<{id: string; name: string}[]>('api/nobodies'))) .subscribe(nobodies => { @@ -280,11 +283,11 @@ describe('HttpClient Backend Service', () => { })); describe('can reset the database', () => { - it('to empty (object db)', async(() => resetDatabaseTest('object'))); + it('to empty (object db)', waitForAsync(() => resetDatabaseTest('object'))); - it('to empty (observable db)', async(() => resetDatabaseTest('observable'))); + it('to empty (observable db)', waitForAsync(() => resetDatabaseTest('observable'))); - it('to empty (promise db)', async(() => resetDatabaseTest('promise'))); + it('to empty (promise db)', waitForAsync(() => resetDatabaseTest('promise'))); function resetDatabaseTest(returnType: string) { // Observable of the number of heroes, nobodies and villains @@ -331,19 +334,19 @@ describe('HttpClient Backend Service', () => { heroService = TestBed.get(HeroService); }); - it('can get heroes', async(() => { + it('can get heroes', waitForAsync(() => { heroService.getHeroes().subscribe(heroes => { expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); }, failRequest); })); - it('can get hero w/ id=1', async(() => { + it('can get hero w/ id=1', waitForAsync(() => { heroService.getHero(1).subscribe(hero => { expect(hero.name).toBe('Windstorm'); }, () => fail('getHero failed')); })); - it('should 404 when hero id not found', async(() => { + it('should 404 when hero id not found', waitForAsync(() => { const id = 123456; heroService.getHero(id).subscribe( () => fail(`should not have found hero for id='${id}'`), err => { @@ -351,7 +354,7 @@ describe('HttpClient Backend Service', () => { }); })); - it('can add a hero', async(() => { + it('can add a hero', waitForAsync(() => { heroService.addHero('FunkyBob') .pipe( tap(hero => expect(hero.name).toBe('FunkyBob')), @@ -363,23 +366,23 @@ describe('HttpClient Backend Service', () => { }), 10000); - it('can delete a hero', async(() => { + it('can delete a hero', waitForAsync(() => { const id = 1; heroService.deleteHero(id).subscribe((_: {}) => expect(_).toBeDefined(), failRequest); })); - it('should allow delete of non-existent hero', async(() => { + it('should allow delete of non-existent hero', waitForAsync(() => { const id = 123456; heroService.deleteHero(id).subscribe((_: {}) => expect(_).toBeDefined(), failRequest); })); - it('can search for heroes by name containing "a"', async(() => { + it('can search for heroes by name containing "a"', waitForAsync(() => { heroService.searchHeroes('a').subscribe((heroes: Hero[]) => { expect(heroes.length).toBe(3, 'should find 3 heroes with letter "a"'); }, failRequest); })); - it('can update existing hero', async(() => { + it('can update existing hero', waitForAsync(() => { const id = 1; heroService.getHero(id) .pipe( @@ -394,7 +397,7 @@ describe('HttpClient Backend Service', () => { }), 10000); - it('should create new hero when try to update non-existent hero', async(() => { + it('should create new hero when try to update non-existent hero', waitForAsync(() => { const falseHero = new Hero(12321, 'DryMan'); heroService.updateHero(falseHero).subscribe( hero => expect(hero.name).toBe(falseHero.name), failRequest); @@ -446,7 +449,7 @@ describe('HttpClient Backend Service', () => { expect(ti).toBeDefined(); }); - it('should have GET request header from test interceptor', async(() => { + it('should have GET request header from test interceptor', waitForAsync(() => { const handle = spyOn(httpBackend, 'handle').and.callThrough(); http.get('api/heroes').subscribe(heroes => { @@ -459,7 +462,7 @@ describe('HttpClient Backend Service', () => { }, failRequest); })); - it('should have GET response header from test interceptor', async(() => { + it('should have GET response header from test interceptor', waitForAsync(() => { let gotResponse = false; const req = new HttpRequest('GET', 'api/heroes'); http.request(req).subscribe(event => { @@ -503,7 +506,7 @@ describe('HttpClient Backend Service', () => { jasmine.Ajax.uninstall(); }); - it('can get heroes (no passthru)', async(() => { + it('can get heroes (no passthru)', waitForAsync(() => { http.get('api/heroes').subscribe(heroes => { expect(createPassThruBackend).not.toHaveBeenCalled(); expect(heroes.length).toBeGreaterThan(0, 'should have heroes'); @@ -513,7 +516,7 @@ describe('HttpClient Backend Service', () => { // `passthru` is NOT a collection in the data store // so requests for it should pass thru to the "real" server - it('can GET passthru', async(() => { + it('can GET passthru', waitForAsync(() => { jasmine.Ajax.stubRequest('api/passthru').andReturn({ 'status': 200, 'contentType': 'application/json', @@ -525,7 +528,7 @@ describe('HttpClient Backend Service', () => { }, failRequest); })); - it('can ADD to passthru', async(() => { + it('can ADD to passthru', waitForAsync(() => { jasmine.Ajax.stubRequest('api/passthru').andReturn({ 'status': 200, 'contentType': 'application/json', @@ -554,7 +557,7 @@ describe('HttpClient Backend Service', () => { http = TestBed.get(HttpClient); }); - it('can get heroes (encapsulated)', async(() => { + it('can get heroes (encapsulated)', waitForAsync(() => { http.get<{data: any}>('api/heroes') .pipe(map(data => data.data as Hero[])) .subscribe( diff --git a/packages/platform-browser-dynamic/test/resource_loader/resource_loader_cache_spec.ts b/packages/platform-browser-dynamic/test/resource_loader/resource_loader_cache_spec.ts index e41b898e9a..9953fa5af8 100644 --- a/packages/platform-browser-dynamic/test/resource_loader/resource_loader_cache_spec.ts +++ b/packages/platform-browser-dynamic/test/resource_loader/resource_loader_cache_spec.ts @@ -8,7 +8,7 @@ import {ResourceLoader, UrlResolver} from '@angular/compiler'; import {Component} from '@angular/core'; -import {async, fakeAsync, TestBed, tick} from '@angular/core/testing'; +import {fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {CachedResourceLoader} from '@angular/platform-browser-dynamic/src/resource_loader/resource_loader_cache'; import {setTemplateCache} from '@angular/platform-browser-dynamic/test/resource_loader/resource_loader_cache_setter'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -29,14 +29,14 @@ if (isBrowser) { }).toThrowError('CachedResourceLoader: Template cache was not found in $templateCache.'); }); - it('should resolve the Promise with the cached file content on success', async(() => { + it('should resolve the Promise with the cached file content on success', waitForAsync(() => { resourceLoader = createCachedResourceLoader(); resourceLoader.get('test.html').then((text) => { expect(text).toBe('
            Hello
            '); }); })); - it('should reject the Promise on failure', async(() => { + it('should reject the Promise on failure', waitForAsync(() => { resourceLoader = createCachedResourceLoader(); resourceLoader.get('unknown.html').then(() => { throw new Error('Not expected to succeed.'); diff --git a/packages/platform-browser-dynamic/test/testing_public_browser_spec.ts b/packages/platform-browser-dynamic/test/testing_public_browser_spec.ts index 19cfa20180..d8b51bea02 100644 --- a/packages/platform-browser-dynamic/test/testing_public_browser_spec.ts +++ b/packages/platform-browser-dynamic/test/testing_public_browser_spec.ts @@ -8,7 +8,7 @@ import {ResourceLoader} from '@angular/compiler'; import {Compiler, Component, NgModule} from '@angular/core'; -import {async, fakeAsync, inject, TestBed, tick} from '@angular/core/testing'; +import {fakeAsync, inject, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {ResourceLoaderImpl} from '@angular/platform-browser-dynamic/src/resource_loader/resource_loader_impl'; @@ -54,7 +54,7 @@ if (isBrowser) { expect(actuallyDone).toEqual(true); }); - it('should run async tests with ResourceLoaders', async(() => { + it('should run async tests with ResourceLoaders', waitForAsync(() => { const resourceLoader = new ResourceLoaderImpl(); resourceLoader .get('/base/angular/packages/platform-browser/test/static_assets/test.html') @@ -132,7 +132,7 @@ if (isBrowser) { it('should fail when an ResourceLoader fails', done => { const itPromise = patchJasmineIt(); - it('should fail with an error from a promise', async(() => { + it('should fail with an error from a promise', waitForAsync(() => { TestBed.configureTestingModule({declarations: [BadTemplateUrl]}); TestBed.compileComponents(); })); @@ -151,7 +151,7 @@ if (isBrowser) { }); describe('TestBed createComponent', function() { - it('should allow an external templateUrl', async(() => { + it('should allow an external templateUrl', waitForAsync(() => { TestBed.configureTestingModule({declarations: [ExternalTemplateComp]}); TestBed.compileComponents().then(() => { const componentFixture = TestBed.createComponent(ExternalTemplateComp); diff --git a/packages/platform-browser/test/testing_public_spec.ts b/packages/platform-browser/test/testing_public_spec.ts index a9c84895bb..073592ac46 100644 --- a/packages/platform-browser/test/testing_public_spec.ts +++ b/packages/platform-browser/test/testing_public_spec.ts @@ -8,7 +8,7 @@ import {CompilerConfig, ResourceLoader} from '@angular/compiler'; import {Compiler, Component, ComponentFactoryResolver, CUSTOM_ELEMENTS_SCHEMA, Directive, Inject, Injectable, InjectionToken, Injector, Input, NgModule, Optional, Pipe, SkipSelf, ɵstringify as stringify} from '@angular/core'; -import {async, fakeAsync, getTestBed, inject, TestBed, tick, withModule} from '@angular/core/testing'; +import {fakeAsync, getTestBed, inject, TestBed, tick, waitForAsync, withModule} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {ivyEnabled, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing'; @@ -146,11 +146,11 @@ const bTok = new InjectionToken('b'); }, 0); }); - it('should run async tests with tasks', async(function(this: TestContext) { + it('should run async tests with tasks', waitForAsync(function(this: TestContext) { setTimeout(() => this.actuallyDone = true, 0); })); - it('should run async tests with promises', async(function(this: TestContext) { + it('should run async tests with promises', waitForAsync(function(this: TestContext) { const p = new Promise((resolve, reject) => setTimeout(resolve, 10)); p.then(() => this.actuallyDone = true); })); @@ -192,7 +192,7 @@ const bTok = new InjectionToken('b'); })); it('should preserve context when async and inject helpers are combined', - async(inject([], function(this: TestContext) { + waitForAsync(inject([], function(this: TestContext) { setTimeout(() => this.contextModified = true, 0); }))); @@ -214,7 +214,7 @@ const bTok = new InjectionToken('b'); })); it('should wait until returned promises', - async(inject([FancyService], (service: FancyService) => { + waitForAsync(inject([FancyService], (service: FancyService) => { service.getAsyncValue().then((value) => expect(value).toEqual('async value')); service.getTimeoutValue().then((value) => expect(value).toEqual('timeout value')); }))); @@ -251,7 +251,7 @@ const bTok = new InjectionToken('b'); }); describe('using async beforeEach', () => { - beforeEach(async(inject([FancyService], (service: FancyService) => { + beforeEach(waitForAsync(inject([FancyService], (service: FancyService) => { service.getAsyncValue().then((value) => service.value = value); }))); @@ -335,7 +335,7 @@ const bTok = new InjectionToken('b'); }); describe('components with template url', () => { - beforeEach(async(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({declarations: [CompWithUrlTemplate]}); TestBed.compileComponents(); })); @@ -892,9 +892,9 @@ const bTok = new InjectionToken('b'); const itPromise = patchJasmineIt(); const barError = new Error('bar'); - it('throws an async error', async(inject([], () => setTimeout(() => { - throw barError; - }, 0)))); + it('throws an async error', waitForAsync(inject([], () => setTimeout(() => { + throw barError; + }, 0)))); itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => { expect(err).toEqual(barError); @@ -906,7 +906,7 @@ const bTok = new InjectionToken('b'); it('should fail when a returned promise is rejected', (done) => { const itPromise = patchJasmineIt(); - it('should fail with an error from a promise', async(inject([], () => { + it('should fail with an error from a promise', waitForAsync(inject([], () => { let reject: (error: any) => void = undefined!; const promise = new Promise((_, rej) => reject = rej); const p = promise.then(() => expect(1).toEqual(2)); @@ -1011,14 +1011,14 @@ Did you run and wait for 'resolveComponentResources()'?` : }); }); - it('should instantiate a component with valid DOM', async(() => { + it('should instantiate a component with valid DOM', waitForAsync(() => { const fixture = TestBed.createComponent(ChildComp); fixture.detectChanges(); expect(fixture.nativeElement).toHaveText('Original Child'); })); - it('should allow changing members of the component', async(() => { + it('should allow changing members of the component', waitForAsync(() => { const componentFixture = TestBed.createComponent(MyIfComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('MyIf()'); @@ -1028,14 +1028,14 @@ Did you run and wait for 'resolveComponentResources()'?` : expect(componentFixture.nativeElement).toHaveText('MyIf(More)'); })); - it('should override a template', async(() => { + it('should override a template', waitForAsync(() => { TestBed.overrideComponent(ChildComp, {set: {template: 'Mock'}}); const componentFixture = TestBed.createComponent(ChildComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Mock'); })); - it('should override a provider', async(() => { + it('should override a provider', waitForAsync(() => { TestBed.overrideComponent( TestProvidersComp, {set: {providers: [{provide: FancyService, useClass: MockFancyService}]}}); @@ -1045,7 +1045,7 @@ Did you run and wait for 'resolveComponentResources()'?` : })); - it('should override a viewProvider', async(() => { + it('should override a viewProvider', waitForAsync(() => { TestBed.overrideComponent( TestViewProvidersComp, {set: {viewProviders: [{provide: FancyService, useClass: MockFancyService}]}}); @@ -1065,7 +1065,7 @@ Did you run and wait for 'resolveComponentResources()'?` : }); }); - it('should override component dependencies', async(() => { + it('should override component dependencies', waitForAsync(() => { const componentFixture = TestBed.createComponent(ParentComp); componentFixture.detectChanges(); expect(componentFixture.nativeElement).toHaveText('Parent(Mock)'); diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index 5703c00885..8fc8c91325 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -11,7 +11,7 @@ import {DOCUMENT, isPlatformServer, PlatformLocation, ɵgetDOM as getDOM} from ' import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {ApplicationRef, CompilerFactory, Component, destroyPlatform, getPlatform, HostListener, Inject, Injectable, Input, NgModule, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation} from '@angular/core'; -import {async, inject} from '@angular/core/testing'; +import {inject, waitForAsync} from '@angular/core/testing'; import {BrowserModule, makeStateKey, Title, TransferState} from '@angular/platform-browser'; import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, platformDynamicServer, PlatformState, renderModule, renderModuleFactory, ServerModule, ServerTransferStateModule} from '@angular/platform-server'; import {ivyEnabled, modifiedInIvy} from '@angular/private/testing'; @@ -409,7 +409,7 @@ describe('platform-server integration', () => { if (getPlatform()) destroyPlatform(); }); - it('should bootstrap', async(() => { + it('should bootstrap', waitForAsync(() => { const platform = platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: ''}}]); @@ -426,7 +426,7 @@ describe('platform-server integration', () => { }); })); - it('should allow multiple platform instances', async(() => { + it('should allow multiple platform instances', waitForAsync(() => { const platform = platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: ''}}]); @@ -447,7 +447,7 @@ describe('platform-server integration', () => { }); })); - it('adds title to the document using Title service', async(() => { + it('adds title to the document using Title service', waitForAsync(() => { const platform = platformDynamicServer([{ provide: INITIAL_CONFIG, useValue: {document: ''} @@ -461,7 +461,7 @@ describe('platform-server integration', () => { }); })); - it('should get base href from document', async(() => { + it('should get base href from document', waitForAsync(() => { const platform = platformDynamicServer([{ provide: INITIAL_CONFIG, useValue: {document: ''} @@ -473,7 +473,7 @@ describe('platform-server integration', () => { }); })); - it('adds styles with ng-transition attribute', async(() => { + it('adds styles with ng-transition attribute', waitForAsync(() => { const platform = platformDynamicServer([{ provide: INITIAL_CONFIG, useValue: {document: ''} @@ -488,7 +488,7 @@ describe('platform-server integration', () => { }); })); - it('copies known properties to attributes', async(() => { + it('copies known properties to attributes', waitForAsync(() => { const platform = platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: ''}}]); platform.bootstrapModule(ImageExampleModule).then(ref => { @@ -500,7 +500,7 @@ describe('platform-server integration', () => { })); describe('PlatformLocation', () => { - it('is injectable', async(() => { + it('is injectable', waitForAsync(() => { const platform = platformDynamicServer( [{provide: INITIAL_CONFIG, useValue: {document: ''}}]); platform.bootstrapModule(ExampleModule).then(appRef => { @@ -551,7 +551,7 @@ describe('platform-server integration', () => { expect(location.hash).toBe(''); }); }); - it('pushState causes the URL to update', async(() => { + it('pushState causes the URL to update', waitForAsync(() => { const platform = platformDynamicServer( [{provide: INITIAL_CONFIG, useValue: {document: ''}}]); platform.bootstrapModule(ExampleModule).then(appRef => { @@ -601,7 +601,7 @@ describe('platform-server integration', () => { expect(called).toBe(true); }); - it('using long form should work', async(() => { + it('using long form should work', waitForAsync(() => { const platform = platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]); @@ -618,7 +618,7 @@ describe('platform-server integration', () => { }); })); - it('using renderModule should work', async(() => { + it('using renderModule should work', waitForAsync(() => { renderModule(AsyncServerModule, {document: doc}).then(output => { expect(output).toBe(expectedOutput); called = true; @@ -626,7 +626,7 @@ describe('platform-server integration', () => { })); modifiedInIvy('Will not support binding to innerText in Ivy since domino does not') - .it('should support binding to innerText', async(() => { + .it('should support binding to innerText', waitForAsync(() => { renderModule(InnerTextModule, {document: doc}).then(output => { expect(output).toBe( '
            Some text
            '); @@ -635,7 +635,7 @@ describe('platform-server integration', () => { })); it('using renderModuleFactory should work', - async(inject([PlatformRef], (defaultPlatform: PlatformRef) => { + waitForAsync(inject([PlatformRef], (defaultPlatform: PlatformRef) => { const compilerFactory: CompilerFactory = defaultPlatform.injector.get(CompilerFactory, null)!; const moduleFactory = @@ -646,7 +646,7 @@ describe('platform-server integration', () => { }); }))); - it('works with SVG elements', async(() => { + it('works with SVG elements', waitForAsync(() => { renderModule(SVGServerModule, {document: doc}).then(output => { expect(output).toBe( '' + @@ -655,7 +655,7 @@ describe('platform-server integration', () => { }); })); - it('works with animation', async(() => { + it('works with animation', waitForAsync(() => { renderModule(AnimationServerModule, {document: doc}).then(output => { expect(output).toContain('Works!'); expect(output).toContain('ng-trigger-myAnimation'); @@ -666,7 +666,7 @@ describe('platform-server integration', () => { }); })); - it('should handle ViewEncapsulation.Native', async(() => { + it('should handle ViewEncapsulation.Native', waitForAsync(() => { renderModule(NativeExampleModule, {document: doc}).then(output => { expect(output).not.toBe(''); expect(output).toContain('color: red'); @@ -675,7 +675,7 @@ describe('platform-server integration', () => { })); - it('sets a prefix for the _nghost and _ngcontent attributes', async(() => { + it('sets a prefix for the _nghost and _ngcontent attributes', waitForAsync(() => { renderModule(ExampleStylesModule, {document: doc}).then(output => { expect(output).toMatch( /