Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
41698bf5fd | |||
23c50e27fc | |||
0ae8ea254a | |||
19deca159b | |||
dc3e8aa0fb | |||
a9222c0ade | |||
ea3127e3f9 | |||
8e30f7b1aa | |||
46d81b48ac | |||
3e51a2cb26 | |||
faf60d9310 | |||
0639cb9de1 | |||
810d025488 | |||
efe49c141a | |||
a3d9878c9d | |||
10213d0ca0 | |||
0e1919c2db | |||
d579b8ce05 | |||
d69ba735ee | |||
2991b1b217 | |||
b18cf21e99 | |||
c17098dae6 | |||
43e3073687 | |||
f28878f92f | |||
a10bdefa8b | |||
2f377dbcdd | |||
e2e7b4943e | |||
b8a1363bb2 | |||
e9e6a58dd0 | |||
5932a79713 | |||
7c6a082afd | |||
8a2fe5b7c9 | |||
c9eb4910eb | |||
a634a5abbc | |||
41225026e4 | |||
e9f2203347 | |||
906b3ec8e7 | |||
1cb0c4644a | |||
9d28a27215 | |||
f04aef48f2 | |||
d249852f4a | |||
707dd59767 | |||
4d2570576a | |||
76e58e6cca | |||
a27066b9d2 | |||
29dfc8f3ac | |||
b5533e0ee5 | |||
b275d378df | |||
96655f7aac | |||
253f509493 | |||
5a02ae2f84 | |||
f860752902 | |||
efa126f157 | |||
715135b117 | |||
c30fc52d99 | |||
434eb971e4 | |||
6e31e22d41 | |||
8fef926cd2 | |||
7698afedb1 | |||
a583d12660 | |||
a867d71ece | |||
84b43daf65 | |||
9b3423b50d | |||
d6041d83ec | |||
a638f504eb | |||
a29c756251 | |||
f206eb94bb | |||
3d6e82eccd |
@ -80,7 +80,7 @@ jobs:
|
||||
|
||||
- run: ls /home/circleci/bazel_repository_cache || true
|
||||
- run: bazel info release
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel run @nodejs//:yarn
|
||||
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
||||
# This avoids waiting for the slowest build target to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
@ -131,7 +131,7 @@ jobs:
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel run @nodejs//:yarn
|
||||
- run: scripts/build-packages-dist.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
|
2
.github/angular-robot.yml
vendored
2
.github/angular-robot.yml
vendored
@ -45,10 +45,12 @@ merge:
|
||||
- "packages/language-service/**"
|
||||
- "**/.gitignore"
|
||||
- "**/.gitkeep"
|
||||
- "**/package.json"
|
||||
- "**/tsconfig-build.json"
|
||||
- "**/tsconfig.json"
|
||||
- "**/rollup.config.js"
|
||||
- "**/BUILD.bazel"
|
||||
- "packages/**/integrationtest/**"
|
||||
- "packages/**/test/**"
|
||||
|
||||
# comment that will be added to a PR when there is a conflict, leave empty or set to false to disable
|
||||
|
10
BUILD.bazel
10
BUILD.bazel
@ -11,7 +11,7 @@ exports_files([
|
||||
# This ensures that package.json in subdirectories get installed as well.
|
||||
alias(
|
||||
name = "install",
|
||||
actual = "@yarn//:yarn",
|
||||
actual = "@nodejs//:yarn",
|
||||
)
|
||||
|
||||
node_modules_filegroup(
|
||||
@ -44,14 +44,16 @@ filegroup(
|
||||
"//:node_modules/zone.js/dist/zone.js",
|
||||
"//:node_modules/zone.js/dist/zone-testing.js",
|
||||
"//:node_modules/zone.js/dist/task-tracking.js",
|
||||
"//:test-events.js",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "angularjs",
|
||||
# do not sort
|
||||
name = "angularjs_scripts",
|
||||
srcs = [
|
||||
"//:node_modules/angular/angular.js",
|
||||
"//:node_modules/angular-1.5/angular.js",
|
||||
"//:node_modules/angular-mocks-1.5/angular-mocks.js",
|
||||
"//:node_modules/angular-mocks/angular-mocks.js",
|
||||
"//:node_modules/angular/angular.js",
|
||||
],
|
||||
)
|
||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,3 +1,18 @@
|
||||
<a name="6.0.4"></a>
|
||||
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([c9eb491](https://github.com/angular/angular/commit/c9eb491))
|
||||
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([19deca1](https://github.com/angular/angular/commit/19deca1))
|
||||
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([dc3e8aa](https://github.com/angular/angular/commit/dc3e8aa)), closes [#17195](https://github.com/angular/angular/issues/17195)
|
||||
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([e9f2203](https://github.com/angular/angular/commit/e9f2203))
|
||||
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([2991b1b](https://github.com/angular/angular/commit/2991b1b)), closes [#23023](https://github.com/angular/angular/issues/23023)
|
||||
* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([c17098d](https://github.com/angular/angular/commit/c17098d)), closes [#19278](https://github.com/angular/angular/issues/19278)
|
||||
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([906b3ec](https://github.com/angular/angular/commit/906b3ec)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
|
||||
|
||||
|
||||
<a name="6.0.3"></a>
|
||||
## [6.0.3](https://github.com/angular/angular/compare/6.0.2...6.0.3) (2018-05-22)
|
||||
|
||||
|
24
WORKSPACE
24
WORKSPACE
@ -6,23 +6,23 @@ workspace(name = "angular")
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.8.0.zip",
|
||||
strip_prefix = "rules_nodejs-0.8.0",
|
||||
sha256 = "4e40dd49ae7668d245c3107645f2a138660fcfd975b9310b91eda13f0c973953",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip",
|
||||
strip_prefix = "rules_nodejs-0.9.1",
|
||||
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_webtesting",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/cfcaaf98553fee8e7063b5f5c11fd1b77e43d683.zip",
|
||||
strip_prefix = "rules_webtesting-cfcaaf98553fee8e7063b5f5c11fd1b77e43d683",
|
||||
sha256 = "636c7a9ac2ca13a04d982c2f9c874876ecc90a7b9ccfe4188156122b26ada7b3",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip",
|
||||
strip_prefix = "rules_webtesting-0.2.0",
|
||||
sha256 = "cecc12f07e95740750a40d38e8b14b76fefa1551bef9332cb432d564d693723c",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/v0.13.0.zip",
|
||||
strip_prefix = "rules_typescript-0.13.0",
|
||||
sha256 = "8f2767ff56ad68c80c62e9a1cdc2ba2c2ba0b19d350f713365e5333045df02e3",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.0.zip",
|
||||
strip_prefix = "rules_typescript-0.15.0",
|
||||
sha256 = "1aa75917330b820cb239b3c10a936a10f0a46fe215063d4492dd76dc6e1616f4",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
@ -66,9 +66,9 @@ http_archive(
|
||||
|
||||
http_archive(
|
||||
name = "org_brotli",
|
||||
url = "https://github.com/google/brotli/archive/c6333e1e79fb62ea088443f192293f964409b04e.zip",
|
||||
strip_prefix = "brotli-c6333e1e79fb62ea088443f192293f964409b04e",
|
||||
sha256 = "3f781988dee7dd3bcce2bf238294663cfaaf3b6433505bdb762e24d0a284d1dc",
|
||||
url = "https://github.com/google/brotli/archive/f9b8c02673c576a3e807edbf3a9328e9e7af6d7c.zip",
|
||||
strip_prefix = "brotli-f9b8c02673c576a3e807edbf3a9328e9e7af6d7c",
|
||||
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
|
||||
)
|
||||
|
||||
#
|
||||
|
@ -3,7 +3,7 @@
|
||||
set -eux -o pipefail
|
||||
exec 3>&1
|
||||
|
||||
echo "\n\n[`date`] - Updating the preview server..."
|
||||
echo -e "\n\n[`date`] - Updating the preview server..."
|
||||
|
||||
# Input
|
||||
readonly HOST_REPO_DIR=$1
|
||||
|
@ -5,7 +5,7 @@ import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } fr
|
||||
// #docregion custom-validator
|
||||
/** A hero's name can't match the given regular expression */
|
||||
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
return (control: AbstractControl): {[key: string]: any} | null => {
|
||||
const forbidden = nameRe.test(control.value);
|
||||
return forbidden ? {'forbiddenName': {value: control.value}} : null;
|
||||
};
|
||||
@ -22,7 +22,7 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
export class ForbiddenValidatorDirective implements Validator {
|
||||
@Input('appForbiddenName') forbiddenName: string;
|
||||
|
||||
validate(control: AbstractControl): {[key: string]: any} {
|
||||
validate(control: AbstractControl): {[key: string]: any} | null {
|
||||
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
|
||||
: null;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ from the [The Tour of Heroes](tutorial/).
|
||||
</code-tabs>
|
||||
|
||||
The `HeroesComponent` is the top-level heroes component.
|
||||
It's only purpose is to display the `HeroListComponent`
|
||||
Its only purpose is to display the `HeroListComponent`
|
||||
which displays a list of hero names.
|
||||
|
||||
This version of the `HeroListComponent` gets its `heroes` from the `HEROES` array, an in-memory collection
|
||||
|
@ -44,25 +44,25 @@ of some of the things they contain:
|
||||
<tr>
|
||||
<td><code>FormsModule</code></td>
|
||||
<td><code>@angular/forms</code></td>
|
||||
<td>When you build template driven forms (includes <code>NgModel</code>)</td>
|
||||
<td>When you want to build template driven forms (includes <code>NgModel</code>)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>ReactiveFormsModule</code></td>
|
||||
<td><code>@angular/forms</code></td>
|
||||
<td>When building reactive forms</td>
|
||||
<td>When you want to build reactive forms</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>RouterModule</code></td>
|
||||
<td><code>@angular/router</code></td>
|
||||
<td>For Routing and when you want to use <code>RouterLink</code>,<code>.forRoot()</code>, and <code>.forChild()</code></td>
|
||||
<td>When you want to use <code>RouterLink</code>, <code>.forRoot()</code>, and <code>.forChild()</code></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>HttpClientModule</code></td>
|
||||
<td><code>@angular/common/http</code></td>
|
||||
<td>When you to talk to a server</td>
|
||||
<td>When you want to talk to a server</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
@ -450,7 +450,7 @@ Here is a `searchHeroes` method that queries for heroes whose names contain the
|
||||
|
||||
If there is a search term, the code constructs an options object with an HTML URL-encoded search parameter. If the term were "foo", the GET request URL would be `api/heroes/?name=foo`.
|
||||
|
||||
The `HttpParms` are immutable so you'll have to use the `set()` method to update the options.
|
||||
The `HttpParams` are immutable so you'll have to use the `set()` method to update the options.
|
||||
|
||||
### Debouncing requests
|
||||
|
||||
|
@ -74,22 +74,58 @@ The following table contains our current target release dates for the next two m
|
||||
|
||||
|
||||
{@a lts}
|
||||
## Long-term support
|
||||
{@a support}
|
||||
## Support policy
|
||||
|
||||
All of our releases are supported actively for about 6 months (until the next major release), and then they are supported through long-term support (LTS) for another 12 months.
|
||||
All of our major releases are supported for 18 months.
|
||||
|
||||
During the LTS period, only critical fixes and security patches will be merged and released.
|
||||
* 6 months of active support, during which regularly-scheduled updates and patches are released, as described above in [Release frequency](#frequency "Release frequency").
|
||||
|
||||
The LTS state of one major version starts on the day of the next major release. LTS status ends approximately one year later, when we release another major version.
|
||||
* 12 months of long-term support (LTS). During the LTS period, only critical fixes and security patches will be released.
|
||||
|
||||
The following table provides the support status and key dates for Angular version 4.0.0 and higher.
|
||||
|
||||
<style>
|
||||
|
||||
Version | LTS Start Date | LTS End Date
|
||||
----------- | -------------- | ------------
|
||||
^4.0.0 | October 2017 | October 2018
|
||||
^5.0.0 | April 2018 | April 2019
|
||||
^6.0.0 | October 2018 | October 2019
|
||||
td, th {vertical-align: top}
|
||||
|
||||
</style>
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th>Release Date</th>
|
||||
<th>LTS Start Date</th>
|
||||
<th>LTS End Date</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^4.0.0</td>
|
||||
<td>LTS</td>
|
||||
<td>March 23, 2017</td>
|
||||
<td>September 23, 2017</td>
|
||||
<td>September 23, 2018</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^5.0.0</td>
|
||||
<td>LTS</td>
|
||||
<td>November 1, 2017</td>
|
||||
<td>May 1, 2018</td>
|
||||
<td>May 1, 2019</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^6.0.0</td>
|
||||
<td>Active</td>
|
||||
<td>May 3, 2018</td>
|
||||
<td>November 3, 2018</td>
|
||||
<td>November 3, 2019</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
@ -106,7 +142,7 @@ To make these transitions as easy as possible, we make two commitments to you:
|
||||
|
||||
To help ensure that you have sufficient time and a clear path to update, this is our deprecation policy:
|
||||
|
||||
* When announce deprecated features in the [change log](https://github.com/angular/angular/blob/master/CHANGELOG.md "Angular change log").
|
||||
* We announce deprecated features in the [change log](https://github.com/angular/angular/blob/master/CHANGELOG.md "Angular change log").
|
||||
|
||||
* When we announce a deprecation, we also announce a recommended update path.
|
||||
|
||||
@ -130,5 +166,3 @@ Any changes to the public API surface will be done using the versioning, support
|
||||
Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team.
|
||||
|
||||
Angular Labs projects are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects.
|
||||
|
||||
Angular Labs projects typically are in separate branches in the Angular repo, clearly separated from the main Angular codebase.
|
||||
|
@ -1,3 +0,0 @@
|
||||
# Testing
|
||||
|
||||
TBD. Original content [here](https://docs.google.com/document/d/1gGP5sqWNCHAWWV_GLdZQ1XyMO4K-CHksUxux0BFtVxk/edit#heading=h.ohqykkhzdhb2).
|
@ -204,8 +204,7 @@ The test consumes that spy in the same way it did earlier.
|
||||
Most test suites in this guide call `beforeEach()` to set the preconditions for each `it()` test
|
||||
and rely on the `TestBed` to create classes and inject services.
|
||||
|
||||
There's another school of testing that never calls `beforeEach()` and
|
||||
and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
There's another school of testing that never calls `beforeEach()` and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
|
||||
Here's how you might rewrite one of the `MasterService` tests in that style.
|
||||
|
||||
@ -347,7 +346,7 @@ It appears within the template of a parent component,
|
||||
which binds a _hero_ to the `@Input` property and
|
||||
listens for an event raised through the _selected_ `@Output` property.
|
||||
|
||||
You can test that the class code works without creating the the `DashboardHeroComponent`
|
||||
You can test that the class code works without creating the `DashboardHeroComponent`
|
||||
or its parent component.
|
||||
|
||||
<code-example
|
||||
@ -2367,9 +2366,9 @@ The [override metadata object](#metadata-override-object) is a generic defined a
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
type MetadataOverride<T> = {
|
||||
add?: T;
|
||||
remove?: T;
|
||||
set?: T;
|
||||
add?: Partial<T>;
|
||||
remove?: Partial<T>;
|
||||
set?: Partial<T>;
|
||||
};
|
||||
</code-example>
|
||||
|
||||
@ -2725,9 +2724,9 @@ appropriate to the method, that is, the parameter of an `@NgModule`,
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
type MetadataOverride<T> = {
|
||||
add?: T;
|
||||
remove?: T;
|
||||
set?: T;
|
||||
add?: Partial<T>;
|
||||
remove?: Partial<T>;
|
||||
set?: Partial<T>;
|
||||
};
|
||||
</code-example>
|
||||
|
||||
@ -3379,11 +3378,11 @@ next to their corresponding helper files.
|
||||
{@a q-e2e}
|
||||
#### Why not rely on E2E tests of DOM integration?
|
||||
|
||||
The component DOM tests describe in this guide often require extensive setup and
|
||||
advanced techniques where as the [class-only test](#component-class-testing)
|
||||
were comparatively simple.
|
||||
The component DOM tests described in this guide often require extensive setup and
|
||||
advanced techniques whereas the [unit tests](#component-class-testing)
|
||||
are comparatively simple.
|
||||
|
||||
Why not defer DOM integration tests to end-to-end (E2E) testing?
|
||||
#### Why not defer DOM integration tests to end-to-end (E2E) testing?
|
||||
|
||||
E2E tests are great for high-level validation of the entire system.
|
||||
But they can't give you the comprehensive test coverage that you'd expect from unit tests.
|
||||
|
BIN
aio/content/images/bios/juristr.jpg
Normal file
BIN
aio/content/images/bios/juristr.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 207 KiB |
BIN
aio/content/images/bios/kimmaida.jpg
Normal file
BIN
aio/content/images/bios/kimmaida.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
BIN
aio/content/images/bios/mashhood.jpg
Normal file
BIN
aio/content/images/bios/mashhood.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
@ -618,5 +618,32 @@
|
||||
"website": "https://mhartington.io",
|
||||
"bio": "Mike is a Developer Advocate for the Ionic Framework and a GDE in Angular. He spends most of his time making fast PWAs and exploring emerging web standards. When not behind a keyboard, you'll probably find him with a guitar and beer.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"juristr": {
|
||||
"name": "Juri Strumpflohner",
|
||||
"picture": "juristr.jpg",
|
||||
"twitter": "juristr",
|
||||
"website": "https://juristr.com",
|
||||
"bio": "Juri is a software engineer and freelance trainer and consultant currently mostly focusing on the frontend side using JavaScript, TypeScript and Angular. He has a passion for teaching and sharing his knowledge and experiences with others. This mostly happens by writing tech articles for his personal blog, by creating video courses for Egghead.io, during on-site workshops at companies or by speaking at conferences. In his free time he enjoys practicing Yoseikan Budo, a martial art where he currently owns the 3rd DAN black belt.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"mashhoodr": {
|
||||
"name": "Mashhood Rastgar",
|
||||
"picture": "mashhood.jpg",
|
||||
"twitter": "mashhoodr",
|
||||
"website": "http://imars.info/",
|
||||
"bio": "Mashhood is the principal technical consultant at Recurship and a Google Developer Expert. He works with different startups in US and EU to helps them crawl through the technical maze and quickly build amazing products focused around the problems they are trying to solve. He specializes in using the latest web technologies available to execute the best possible solutions.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"kimmaida": {
|
||||
"name": "Kim Maida",
|
||||
"picture": "kimmaida.jpg",
|
||||
"twitter": "KimMaida",
|
||||
"website": "https://kmaida.io/",
|
||||
"bio": "Kim is an an Angular consultant, developer, speaker, writer, and Google Developer Expert. She is passionate about learning from and sharing knowledge with other developers through blogging, speaking, workshops, and open source.",
|
||||
"group": "GDE"
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +160,7 @@
|
||||
"desc": "Lightweight yet powerful IDE, perfectly equipped for complex client-side development and server-side development with Node.js",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Webstorm",
|
||||
"title": "WebStorm",
|
||||
"url": "https://www.jetbrains.com/webstorm/"
|
||||
},
|
||||
"ab3": {
|
||||
@ -345,6 +345,13 @@
|
||||
"title": "Angular Material",
|
||||
"url": "https://github.com/angular/material2"
|
||||
},
|
||||
"mcc": {
|
||||
"desc": "Material components made by the community",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Material Community Components",
|
||||
"url": "https://github.com/tiaguinho/material-community-components"
|
||||
},
|
||||
"ngzorro": {
|
||||
"desc": "A set of enterprise-class UI components based on Ant Design and Angular",
|
||||
"rev": true,
|
||||
@ -370,6 +377,13 @@
|
||||
"url": "http://www.amexio.tech/",
|
||||
"logo": "http://www.amexio.org/amexio-logo.png"
|
||||
},
|
||||
"bm": {
|
||||
"desc": "A lightweight Material Design library for Angular, based upon Google's Material Components for the Web",
|
||||
"logo": "https://blox.src.zone/assets/bloxmaterial.03ecfe4fa0147a781487749dc1cc4580.svg",
|
||||
"rev": true,
|
||||
"title": "Blox Material",
|
||||
"url": "https://github.com/src-zone/material"
|
||||
},
|
||||
"essentialjs2": {
|
||||
"desc": "Essential JS 2 for Angular is a collection modern TypeScript based true Angular Components. It has support for Ahead Of Time (AOT) compilation and Tree-Shaking. All the components are developed from the ground up to be lightweight, responsive, modular and touch friendly.",
|
||||
"rev": true,
|
||||
@ -528,13 +542,6 @@
|
||||
"title": "Pluralsight",
|
||||
"url": "https://www.pluralsight.com/search?q=angular+2&categories=all"
|
||||
},
|
||||
"ab": {
|
||||
"desc": "Take this introduction to Angular course, to learn the fundamentals in just two days, free of charge.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Rangle.io",
|
||||
"url": "https://rangle.io/services/javascript-training/training-angular1-angular2-with-ngupgrade/"
|
||||
},
|
||||
"ab3": {
|
||||
"desc": "Angular courses hosted by Udemy",
|
||||
"logo": "",
|
||||
|
@ -175,7 +175,7 @@ This information is called _metadata_
|
||||
Some of the metadata is in the `@Component` decorators that you added to your component classes.
|
||||
Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators.
|
||||
|
||||
The most important `@NgModule`decorator annotates the top-level **AppModule** class.
|
||||
The most important `@NgModule` decorator annotates the top-level **AppModule** class.
|
||||
|
||||
The Angular CLI generated an `AppModule` class in `src/app/app.module.ts` when it created the project.
|
||||
This is where you _opt-in_ to the `FormsModule`.
|
||||
|
@ -2,8 +2,8 @@
|
||||
"aio": {
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 2689,
|
||||
"main": 478529,
|
||||
"runtime": 2768,
|
||||
"main": 475855,
|
||||
"polyfills": 38453,
|
||||
"prettify": 14913
|
||||
}
|
||||
|
@ -58,7 +58,7 @@
|
||||
</mat-sidenav-container>
|
||||
|
||||
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
|
||||
<aio-toc></aio-toc>
|
||||
<aio-lazy-ce selector="aio-toc"></aio-lazy-ce>
|
||||
</div>
|
||||
|
||||
<footer class="no-print">
|
||||
|
@ -6,7 +6,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { MatProgressBar, MatSidenav } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { timer } from 'rxjs';
|
||||
import { of, timer } from 'rxjs';
|
||||
import { first, mapTo } from 'rxjs/operators';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -14,6 +14,7 @@ import { AppModule } from './app.module';
|
||||
import { DocumentService } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Deployment } from 'app/shared/deployment.service';
|
||||
import { ElementsLoader } from 'app/custom-elements/elements-loader';
|
||||
import { GaService } from 'app/shared/ga.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
@ -26,7 +27,6 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { SelectComponent } from 'app/shared/select/select.component';
|
||||
import { TocComponent } from 'app/layout/toc/toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
||||
const sideBySideBreakPoint = 992;
|
||||
@ -92,11 +92,11 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('hasFloatingToc', () => {
|
||||
it('should initially be true', () => {
|
||||
it('should initially be false', () => {
|
||||
const fixture2 = TestBed.createComponent(AppComponent);
|
||||
const component2 = fixture2.componentInstance;
|
||||
|
||||
expect(component2.hasFloatingToc).toBe(true);
|
||||
expect(component2.hasFloatingToc).toBe(false);
|
||||
});
|
||||
|
||||
it('should be false on narrow screens', () => {
|
||||
@ -621,55 +621,65 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('aio-toc', () => {
|
||||
let tocDebugElement: DebugElement;
|
||||
let tocContainer: DebugElement|null;
|
||||
let tocContainer: HTMLElement|null;
|
||||
let toc: HTMLElement|null;
|
||||
|
||||
const setHasFloatingToc = (hasFloatingToc: boolean) => {
|
||||
component.hasFloatingToc = hasFloatingToc;
|
||||
fixture.detectChanges();
|
||||
|
||||
tocDebugElement = fixture.debugElement.query(By.directive(TocComponent));
|
||||
tocContainer = tocDebugElement && tocDebugElement.parent;
|
||||
tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
|
||||
toc = tocContainer && tocContainer.querySelector('aio-toc');
|
||||
};
|
||||
|
||||
beforeEach(() => setHasFloatingToc(true));
|
||||
|
||||
|
||||
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
|
||||
expect(tocDebugElement).toBeTruthy();
|
||||
expect(tocContainer).toBeTruthy();
|
||||
|
||||
setHasFloatingToc(false);
|
||||
expect(tocDebugElement).toBeFalsy();
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(tocDebugElement).toBeTruthy();
|
||||
expect(tocContainer).toBeTruthy();
|
||||
expect(toc).toBeTruthy();
|
||||
|
||||
setHasFloatingToc(false);
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should have a non-embedded `<aio-toc>` element', () => {
|
||||
expect(tocDebugElement.classes['embedded']).toBeFalsy();
|
||||
setHasFloatingToc(true);
|
||||
expect(toc!.classList.contains('embedded')).toBe(false);
|
||||
});
|
||||
|
||||
it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => {
|
||||
expect(tocContainer!.styles['max-height']).toBeNull();
|
||||
setHasFloatingToc(true);
|
||||
|
||||
expect(tocContainer!.style['max-height']).toBe('');
|
||||
|
||||
component.tocMaxHeight = '100';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(tocContainer!.styles['max-height']).toBe('100px');
|
||||
expect(tocContainer!.style['max-height']).toBe('100px');
|
||||
});
|
||||
|
||||
it('should restrain scrolling inside the ToC container', () => {
|
||||
const restrainScrolling = spyOn(component, 'restrainScrolling');
|
||||
const evt = {};
|
||||
const evt = new MouseEvent('mousewheel');
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(restrainScrolling).not.toHaveBeenCalled();
|
||||
|
||||
tocContainer!.triggerEventHandler('mousewheel', evt);
|
||||
tocContainer!.dispatchEvent(evt);
|
||||
expect(restrainScrolling).toHaveBeenCalledWith(evt);
|
||||
});
|
||||
|
||||
it('should not be loaded/registered until necessary', () => {
|
||||
const loader: TestElementsLoader = fixture.debugElement.injector.get(ElementsLoader);
|
||||
expect(loader.loadCustomElement).not.toHaveBeenCalled();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(loader.loadCustomElement).toHaveBeenCalledWith('aio-toc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('footer', () => {
|
||||
@ -1280,6 +1290,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||
imports: [ AppModule ],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
{ provide: ElementsLoader, useClass: TestElementsLoader },
|
||||
{ provide: GaService, useClass: TestGaService },
|
||||
{ provide: HttpClient, useClass: TestHttpClient },
|
||||
{ provide: LocationService, useFactory: () => mockLocationService },
|
||||
@ -1294,6 +1305,14 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||
});
|
||||
}
|
||||
|
||||
class TestElementsLoader {
|
||||
loadContainedCustomElements = jasmine.createSpy('loadContainedCustomElements')
|
||||
.and.returnValue(of(undefined));
|
||||
|
||||
loadCustomElement = jasmine.createSpy('loadCustomElement')
|
||||
.and.returnValue(Promise.resolve());
|
||||
}
|
||||
|
||||
class TestGaService {
|
||||
locationChanged = jasmine.createSpy('locationChanged');
|
||||
}
|
||||
@ -1368,7 +1387,7 @@ class TestHttpClient {
|
||||
const id = match[1]!;
|
||||
// Make up a title for test purposes
|
||||
const title = id.split('/').pop()!.replace(/^([a-z])/, (_, letter) => letter.toUpperCase());
|
||||
const h1 = (id === 'no-title') ? '' : `<h1>${title}</h1>`;
|
||||
const h1 = (id === 'no-title') ? '' : `<h1 class="no-toc">${title}</h1>`;
|
||||
const contents = `${h1}<h2 id="#somewhere">Some heading</h2>`;
|
||||
data = { id, contents };
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ export class AppComponent implements OnInit {
|
||||
topMenuNodes: NavigationNode[];
|
||||
topMenuNarrowNodes: NavigationNode[];
|
||||
|
||||
hasFloatingToc = true;
|
||||
hasFloatingToc = false;
|
||||
private showFloatingToc = new BehaviorSubject(false);
|
||||
private showFloatingTocWidth = 800;
|
||||
tocMaxHeight: string;
|
||||
|
@ -32,7 +32,6 @@ import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { NotificationComponent } from 'app/layout/notification/notification.component';
|
||||
import { TocComponent } from 'app/layout/toc/toc.component';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date';
|
||||
import { WindowToken, windowProvider } from 'app/shared/window';
|
||||
@ -111,7 +110,6 @@ export const svgIconProviders = [
|
||||
NavItemComponent,
|
||||
SearchBoxComponent,
|
||||
NotificationComponent,
|
||||
TocComponent,
|
||||
TopMenuComponent,
|
||||
],
|
||||
providers: [
|
||||
@ -133,7 +131,6 @@ export const svgIconProviders = [
|
||||
{ provide: CurrentDateToken, useFactory: currentDateProvider },
|
||||
{ provide: WindowToken, useFactory: windowProvider },
|
||||
],
|
||||
entryComponents: [ TocComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -25,20 +25,22 @@ export interface TabInfo {
|
||||
<!-- Use content projection so that the provided HTML's code-panes can be split into tabs -->
|
||||
<div #content style="display: none"><ng-content></ng-content></div>
|
||||
|
||||
<mat-tab-group class="code-tab-group" disableRipple>
|
||||
<mat-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
|
||||
<ng-template mat-tab-label>
|
||||
<span class="{{ tab.class }}">{{ tab.title }}</span>
|
||||
</ng-template>
|
||||
<aio-code class="{{ tab.class }}"
|
||||
[language]="tab.language"
|
||||
[linenums]="tab.linenums"
|
||||
[path]="tab.path"
|
||||
[region]="tab.region"
|
||||
[title]="tab.title">
|
||||
</aio-code>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<mat-card>
|
||||
<mat-tab-group class="code-tab-group" disableRipple>
|
||||
<mat-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
|
||||
<ng-template mat-tab-label>
|
||||
<span class="{{ tab.class }}">{{ tab.title }}</span>
|
||||
</ng-template>
|
||||
<aio-code class="{{ tab.class }}"
|
||||
[language]="tab.language"
|
||||
[linenums]="tab.linenums"
|
||||
[path]="tab.path"
|
||||
[region]="tab.region"
|
||||
[title]="tab.title">
|
||||
</aio-code>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card>
|
||||
`,
|
||||
})
|
||||
export class CodeTabsComponent implements OnInit, AfterViewInit {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CodeTabsComponent } from './code-tabs.component';
|
||||
import { MatTabsModule } from '@angular/material';
|
||||
import { MatCardModule, MatTabsModule } from '@angular/material';
|
||||
import { CodeModule } from './code.module';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatTabsModule, CodeModule ],
|
||||
imports: [ CommonModule, MatCardModule, MatTabsModule, CodeModule ],
|
||||
declarations: [ CodeTabsComponent ],
|
||||
exports: [ CodeTabsComponent ],
|
||||
entryComponents: [ CodeTabsComponent ]
|
||||
|
@ -6,8 +6,11 @@ import {
|
||||
ELEMENT_MODULE_PATHS_AS_ROUTES,
|
||||
ELEMENT_MODULE_PATHS_TOKEN
|
||||
} from './element-registry';
|
||||
import { LazyCustomElementComponent } from './lazy-custom-element.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ LazyCustomElementComponent ],
|
||||
exports: [ LazyCustomElementComponent ],
|
||||
providers: [
|
||||
ElementsLoader,
|
||||
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
|
||||
|
@ -13,8 +13,8 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||
loadChildren: './api/api-list.module#ApiListModule'
|
||||
},
|
||||
{
|
||||
selector: 'live-example',
|
||||
loadChildren: './live-example/live-example.module#LiveExampleModule'
|
||||
selector: 'aio-contributor-list',
|
||||
loadChildren: './contributor/contributor-list.module#ContributorListModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-file-not-found-search',
|
||||
@ -25,25 +25,29 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||
loadChildren: './resource/resource-list.module#ResourceListModule'
|
||||
},
|
||||
{
|
||||
selector: 'current-location',
|
||||
loadChildren: './current-location/current-location.module#CurrentLocationModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-contributor-list',
|
||||
loadChildren: './contributor/contributor-list.module#ContributorListModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-tabs',
|
||||
loadChildren: './code/code-tabs.module#CodeTabsModule'
|
||||
selector: 'aio-toc',
|
||||
loadChildren: './toc/toc.module#TocModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-example',
|
||||
loadChildren: './code/code-example.module#CodeExampleModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-tabs',
|
||||
loadChildren: './code/code-tabs.module#CodeTabsModule'
|
||||
},
|
||||
{
|
||||
selector: 'current-location',
|
||||
loadChildren: './current-location/current-location.module#CurrentLocationModule'
|
||||
},
|
||||
{
|
||||
selector: 'expandable-section',
|
||||
loadChildren: './expandable-section/expandable-section.module#ExpandableSectionModule'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'live-example',
|
||||
loadChildren: './live-example/live-example.module#LiveExampleModule'
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -4,49 +4,19 @@ import {
|
||||
NgModuleRef,
|
||||
Type
|
||||
} from '@angular/core';
|
||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import { TestBed, fakeAsync, flushMicrotasks } from '@angular/core/testing';
|
||||
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
import { ELEMENT_MODULE_PATHS_TOKEN, WithCustomElementComponent } from './element-registry';
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
ngContentSelectors: string[];
|
||||
inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}];
|
||||
outputs = [];
|
||||
|
||||
constructor(private identifyingInput: string) { super(); }
|
||||
|
||||
create(injector: Injector,
|
||||
projectableNodes?: any[][],
|
||||
rootSelectorOrNode?: string | any,
|
||||
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||
return (jasmine.createSpy('ComponentRef') as any) as ComponentRef<any>;
|
||||
};
|
||||
interface Deferred {
|
||||
resolve(): void;
|
||||
reject(err: any): void;
|
||||
}
|
||||
|
||||
const FAKE_COMPONENT_FACTORIES = new Map([
|
||||
['element-a-module-path', new FakeComponentFactory('element-a-input')],
|
||||
['element-b-module-path', new FakeComponentFactory('element-b-input')],
|
||||
]);
|
||||
|
||||
describe('ElementsLoader', () => {
|
||||
let elementsLoader: ElementsLoader;
|
||||
let actualCustomElementsDefine;
|
||||
let fakeCustomElementsDefine;
|
||||
|
||||
// ElementsLoader uses the window's customElements API. Provide a fake for this test.
|
||||
beforeEach(() => {
|
||||
actualCustomElementsDefine = window.customElements.define;
|
||||
|
||||
fakeCustomElementsDefine = jasmine.createSpy('define');
|
||||
|
||||
window.customElements.define = fakeCustomElementsDefine;
|
||||
});
|
||||
afterEach(() => {
|
||||
window.customElements.define = actualCustomElementsDefine;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const injector = TestBed.configureTestingModule({
|
||||
@ -63,63 +33,196 @@ describe('ElementsLoader', () => {
|
||||
elementsLoader = injector.get(ElementsLoader);
|
||||
});
|
||||
|
||||
it('should be able to register an element', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
describe('loadContainedCustomElements()', () => {
|
||||
let loadCustomElementSpy: jasmine.Spy;
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
beforeEach(() => loadCustomElementSpy = spyOn(elementsLoader, 'loadCustomElement'));
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
it('should attempt to load and register all contained elements', fakeAsync(() => {
|
||||
expect(loadCustomElementSpy).not.toHaveBeenCalled();
|
||||
|
||||
const defineArgs = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineArgs[0]).toBe('element-a-selector');
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
// Verify the right component was loaded/created
|
||||
expect(defineArgs[1].observedAttributes[0]).toBe('element-a-input');
|
||||
elementsLoader.loadContainedCustomElements(hostEl);
|
||||
flushMicrotasks();
|
||||
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
}));
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledTimes(2);
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-a-selector');
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
}));
|
||||
|
||||
it('should be able to register multiple elements', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
it('should attempt to load and register only contained elements', fakeAsync(() => {
|
||||
expect(loadCustomElementSpy).not.toHaveBeenCalled();
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
elementsLoader.loadContainedCustomElements(hostEl);
|
||||
flushMicrotasks();
|
||||
|
||||
const defineElementA = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineElementA[0]).toBe('element-a-selector');
|
||||
expect(defineElementA[1].observedAttributes[0]).toBe('element-a-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledTimes(1);
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
}));
|
||||
|
||||
const defineElementB = fakeCustomElementsDefine.calls.argsFor(1);
|
||||
expect(defineElementB[0]).toBe('element-b-selector');
|
||||
expect(defineElementB[1].observedAttributes[0]).toBe('element-b-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-b-selector')).toBeFalsy();
|
||||
}));
|
||||
it('should wait for all contained elements to load and register', fakeAsync(() => {
|
||||
const deferreds = returnPromisesFromSpy(loadCustomElementSpy);
|
||||
|
||||
it('should only register an element one time', fakeAsync(() => {
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick(); // Tick for the module factory loader's async `load` function
|
||||
const log: any[] = [];
|
||||
elementsLoader.loadContainedCustomElements(hostEl).subscribe(
|
||||
v => log.push(`emitted: ${v}`),
|
||||
e => log.push(`errored: ${e}`),
|
||||
() => log.push('completed'),
|
||||
);
|
||||
|
||||
// Call again to to check how many times customElements.define was called.
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick(); // Tick for the module factory loader's async `load` function
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
// Should have only been called once, since the second load would not query for element-a
|
||||
expect(window.customElements.define).toHaveBeenCalledTimes(1);
|
||||
}));
|
||||
deferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[1].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['emitted: undefined', 'completed']);
|
||||
}));
|
||||
|
||||
it('should fail if any of the contained elements fails to load and register', fakeAsync(() => {
|
||||
const deferreds = returnPromisesFromSpy(loadCustomElementSpy);
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
const log: any[] = [];
|
||||
elementsLoader.loadContainedCustomElements(hostEl).subscribe(
|
||||
v => log.push(`emitted: ${v}`),
|
||||
e => log.push(`errored: ${e}`),
|
||||
() => log.push('completed'),
|
||||
);
|
||||
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[1].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['errored: foo']);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('loadCustomElement()', () => {
|
||||
let definedSpy: jasmine.Spy;
|
||||
let whenDefinedSpy: jasmine.Spy;
|
||||
let whenDefinedDeferreds: Deferred[];
|
||||
|
||||
beforeEach(() => {
|
||||
// `loadCustomElement()` uses the `window.customElements` API. Provide mocks for these tests.
|
||||
definedSpy = spyOn(window.customElements, 'define');
|
||||
whenDefinedSpy = spyOn(window.customElements, 'whenDefined');
|
||||
whenDefinedDeferreds = returnPromisesFromSpy(whenDefinedSpy);
|
||||
});
|
||||
|
||||
it('should be able to load and register an element', fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(definedSpy).toHaveBeenCalledWith('element-a-selector', jasmine.any(Function));
|
||||
|
||||
// Verify the right component was loaded/registered.
|
||||
const Ctor = definedSpy.calls.argsFor(0)[1];
|
||||
expect(Ctor.observedAttributes).toEqual(['element-a-module-path']);
|
||||
}));
|
||||
|
||||
it('should wait until the element is defined', fakeAsync(() => {
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-b-selector').then(() => state = 'resolved');
|
||||
flushMicrotasks();
|
||||
|
||||
expect(state).toBe('pending');
|
||||
expect(whenDefinedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(whenDefinedSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
|
||||
whenDefinedDeferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('resolved');
|
||||
}));
|
||||
|
||||
it('should not load and register the same element more than once', fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
definedSpy.calls.reset();
|
||||
|
||||
// While loading/registering is still in progress:
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
definedSpy.calls.reset();
|
||||
whenDefinedDeferreds[0].resolve();
|
||||
|
||||
// Once loading/registering is already completed:
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-a-selector').then(() => state = 'resolved');
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('resolved');
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should fail if defining the the custom element fails', fakeAsync(() => {
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-b-selector').catch(e => state = `rejected: ${e}`);
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('pending');
|
||||
|
||||
whenDefinedDeferreds[0].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('rejected: foo');
|
||||
}));
|
||||
|
||||
it('should be able to load and register an element again if previous attempt failed',
|
||||
fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
definedSpy.calls.reset();
|
||||
|
||||
// While loading/registering is still in progress:
|
||||
elementsLoader.loadCustomElement('element-a-selector').catch(() => undefined);
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
whenDefinedDeferreds[0].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
// Once loading/registering has already failed:
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// TEST CLASSES/HELPERS
|
||||
@ -128,11 +231,28 @@ class FakeCustomElementModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any>;
|
||||
}
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
ngContentSelectors: string[];
|
||||
inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}];
|
||||
outputs = [];
|
||||
|
||||
constructor(private identifyingInput: string) { super(); }
|
||||
|
||||
create(injector: Injector,
|
||||
projectableNodes?: any[][],
|
||||
rootSelectorOrNode?: string | any,
|
||||
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||
return jasmine.createSpy('ComponentRef') as any;
|
||||
};
|
||||
}
|
||||
|
||||
class FakeComponentFactoryResolver extends ComponentFactoryResolver {
|
||||
constructor(private modulePath) { super(); }
|
||||
|
||||
resolveComponentFactory(component: Type<any>): ComponentFactory<any> {
|
||||
return FAKE_COMPONENT_FACTORIES.get(this.modulePath)!;
|
||||
return new FakeComponentFactory(this.modulePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,3 +288,9 @@ class FakeModuleFactoryLoader extends NgModuleFactoryLoader {
|
||||
return Promise.resolve(fakeModuleFactory);
|
||||
}
|
||||
}
|
||||
|
||||
function returnPromisesFromSpy(spy: jasmine.Spy): Deferred[] {
|
||||
const deferreds: Deferred[] = [];
|
||||
spy.and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject})));
|
||||
return deferreds;
|
||||
}
|
||||
|
@ -11,11 +11,13 @@ import { createCustomElement } from '@angular/elements';
|
||||
@Injectable()
|
||||
export class ElementsLoader {
|
||||
/** Map of unregistered custom elements and their respective module paths to load. */
|
||||
elementsToLoad: Map<string, string>;
|
||||
private elementsToLoad: Map<string, string>;
|
||||
/** Map of custom elements that are in the process of being loaded and registered. */
|
||||
private elementsLoading = new Map<string, Promise<void>>();
|
||||
|
||||
constructor(private moduleFactoryLoader: NgModuleFactoryLoader,
|
||||
private moduleRef: NgModuleRef<any>,
|
||||
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths) {
|
||||
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths: Map<string, string>) {
|
||||
this.elementsToLoad = new Map(elementModulePaths);
|
||||
}
|
||||
|
||||
@ -24,31 +26,57 @@ export class ElementsLoader {
|
||||
* the browser. Custom elements that are registered will be removed from the list of unregistered
|
||||
* elements so that they will not be queried in subsequent calls.
|
||||
*/
|
||||
loadContainingCustomElements(element: HTMLElement): Observable<void> {
|
||||
const selectors: any[] = Array.from(this.elementsToLoad.keys())
|
||||
loadContainedCustomElements(element: HTMLElement): Observable<void> {
|
||||
const unregisteredSelectors = Array.from(this.elementsToLoad.keys())
|
||||
.filter(s => element.querySelector(s));
|
||||
|
||||
if (!selectors.length) { return of(undefined); }
|
||||
if (!unregisteredSelectors.length) { return of(undefined); }
|
||||
|
||||
// Returns observable that completes when all discovered elements have been registered.
|
||||
return fromPromise(Promise.all(selectors.map(s => this.register(s))).then(result => undefined));
|
||||
const allRegistered = Promise.all(unregisteredSelectors.map(s => this.loadCustomElement(s)));
|
||||
return fromPromise(allRegistered.then(() => undefined));
|
||||
}
|
||||
|
||||
/** Registers the custom element defined on the WithCustomElement module factory. */
|
||||
private register(selector: string) {
|
||||
const modulePath = this.elementsToLoad.get(selector)!;
|
||||
return this.moduleFactoryLoader.load(modulePath).then(elementModuleFactory => {
|
||||
if (!this.elementsToLoad.has(selector)) { return; }
|
||||
/** Loads and registers the custom element defined on the `WithCustomElement` module factory. */
|
||||
loadCustomElement(selector: string): Promise<void> {
|
||||
if (this.elementsLoading.has(selector)) {
|
||||
// The custom element is in the process of being loaded and registered.
|
||||
return this.elementsLoading.get(selector)!;
|
||||
}
|
||||
|
||||
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
|
||||
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
|
||||
const CustomElement =
|
||||
createCustomElement(CustomElementComponent, {injector: elementModuleRef.injector});
|
||||
if (this.elementsToLoad.has(selector)) {
|
||||
// Load and register the custom element (for the first time).
|
||||
const modulePath = this.elementsToLoad.get(selector)!;
|
||||
const loadedAndRegistered = this.moduleFactoryLoader
|
||||
.load(modulePath)
|
||||
.then(elementModuleFactory => {
|
||||
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
|
||||
const injector = elementModuleRef.injector;
|
||||
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
|
||||
const CustomElement = createCustomElement(CustomElementComponent, {injector});
|
||||
|
||||
customElements!.define(selector, CustomElement);
|
||||
this.elementsToLoad.delete(selector);
|
||||
customElements!.define(selector, CustomElement);
|
||||
return customElements.whenDefined(selector);
|
||||
})
|
||||
.then(() => {
|
||||
// The custom element has been successfully loaded and registered.
|
||||
// Remove from `elementsLoading` and `elementsToLoad`.
|
||||
this.elementsLoading.delete(selector);
|
||||
this.elementsToLoad.delete(selector);
|
||||
})
|
||||
.catch(err => {
|
||||
// The custom element has failed to load and register.
|
||||
// Remove from `elementsLoading`.
|
||||
// (Do not remove from `elementsToLoad` in case it was a temporary error.)
|
||||
this.elementsLoading.delete(selector);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
return customElements.whenDefined(selector);
|
||||
});
|
||||
this.elementsLoading.set(selector, loadedAndRegistered);
|
||||
return loadedAndRegistered;
|
||||
}
|
||||
|
||||
// The custom element has already been loaded and registered.
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { LazyCustomElementComponent } from './lazy-custom-element.component';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
|
||||
describe('LazyCustomElementComponent', () => {
|
||||
let mockElementsLoader: jasmine.SpyObj<ElementsLoader>;
|
||||
let mockLogger: MockLogger;
|
||||
let fixture: ComponentFixture<LazyCustomElementComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockElementsLoader = jasmine.createSpyObj<ElementsLoader>('ElementsLoader', [
|
||||
'loadContainedCustomElements',
|
||||
'loadCustomElement',
|
||||
]);
|
||||
|
||||
const injector = TestBed.configureTestingModule({
|
||||
declarations: [ LazyCustomElementComponent ],
|
||||
providers: [
|
||||
{ provide: ElementsLoader, useValue: mockElementsLoader },
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
],
|
||||
});
|
||||
|
||||
mockLogger = injector.get(Logger);
|
||||
fixture = TestBed.createComponent(LazyCustomElementComponent);
|
||||
});
|
||||
|
||||
it('should set the HTML content based on the selector', () => {
|
||||
const elem = fixture.nativeElement;
|
||||
|
||||
expect(elem.innerHTML).toBe('');
|
||||
|
||||
fixture.componentInstance.selector = 'foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(elem.innerHTML).toBe('<foo-bar></foo-bar>');
|
||||
});
|
||||
|
||||
it('should load the specified custom element', () => {
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
|
||||
fixture.componentInstance.selector = 'foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).toHaveBeenCalledWith('foo-bar');
|
||||
});
|
||||
|
||||
it('should log an error (and abort) if the selector is empty', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]);
|
||||
expect(mockLogger.output.error[0][0].message).toBe('Invalid selector for \'aio-lazy-ce\': ');
|
||||
});
|
||||
|
||||
it('should log an error (and abort) if the selector is invalid', () => {
|
||||
fixture.componentInstance.selector = 'foo-bar><script></script><foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]);
|
||||
expect(mockLogger.output.error[0][0].message).toBe(
|
||||
'Invalid selector for \'aio-lazy-ce\': foo-bar><script></script><foo-bar');
|
||||
});
|
||||
});
|
27
aio/src/app/custom-elements/lazy-custom-element.component.ts
Normal file
27
aio/src/app/custom-elements/lazy-custom-element.component.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Component, ElementRef, Input, OnInit } from '@angular/core';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
|
||||
@Component({
|
||||
selector: 'aio-lazy-ce',
|
||||
template: '',
|
||||
})
|
||||
export class LazyCustomElementComponent implements OnInit {
|
||||
@Input() selector = '';
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private elementsLoader: ElementsLoader,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.selector || /[^\w-]/.test(this.selector)) {
|
||||
this.logger.error(new Error(`Invalid selector for 'aio-lazy-ce': ${this.selector}`));
|
||||
return;
|
||||
}
|
||||
|
||||
this.elementRef.nativeElement.innerHTML = `<${this.selector}></${this.selector}>`;
|
||||
this.elementsLoader.loadCustomElement(this.selector);
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser';
|
||||
import { asapScheduler as asap, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { TocComponent } from './toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
import { TocComponent } from './toc.component';
|
||||
|
||||
describe('TocComponent', () => {
|
||||
let tocComponentDe: DebugElement;
|
14
aio/src/app/custom-elements/toc/toc.module.ts
Normal file
14
aio/src/app/custom-elements/toc/toc.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
import { TocComponent } from './toc.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatIconModule ],
|
||||
declarations: [ TocComponent ],
|
||||
entryComponents: [ TocComponent ],
|
||||
})
|
||||
export class TocModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any> = TocComponent;
|
||||
}
|
@ -300,7 +300,7 @@ describe('DocViewerComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const elementsLoader = TestBed.get(ElementsLoader) as MockElementsLoader;
|
||||
loadElementsSpy = elementsLoader.loadContainingCustomElements.and.returnValue(of(undefined));
|
||||
loadElementsSpy = elementsLoader.loadContainedCustomElements.and.returnValue(of(undefined));
|
||||
prepareTitleAndTocSpy = spyOn(docViewer, 'prepareTitleAndToc');
|
||||
swapViewsSpy = spyOn(docViewer, 'swapViews').and.returnValue(of(undefined));
|
||||
});
|
||||
|
@ -136,7 +136,7 @@ export class DocViewerComponent implements OnDestroy {
|
||||
// and is considered to be safe.
|
||||
tap(() => this.nextViewContainer.innerHTML = doc.contents || ''),
|
||||
tap(() => addTitleAndToc = this.prepareTitleAndToc(this.nextViewContainer, doc.id)),
|
||||
switchMap(() => this.elementsLoader.loadContainingCustomElements(this.nextViewContainer)),
|
||||
switchMap(() => this.elementsLoader.loadContainedCustomElements(this.nextViewContainer)),
|
||||
tap(() => this.docReady.emit()),
|
||||
switchMap(() => this.swapViews(addTitleAndToc)),
|
||||
tap(() => this.docRendered.emit()),
|
||||
|
@ -27,7 +27,7 @@ aio-shell.page-docs {
|
||||
}
|
||||
|
||||
.sidenav-content button {
|
||||
min-width: 50px;
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
#guide-change-log h2::before {
|
||||
|
@ -93,15 +93,21 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
|
||||
|
||||
// HOME NAV-LINK
|
||||
.nav-link.home img {
|
||||
position: relative;
|
||||
margin-top: -21px;
|
||||
top: 12px;
|
||||
height: 40px;
|
||||
.nav-link.home {
|
||||
cursor: pointer;
|
||||
margin: 0 16px 0 0;
|
||||
padding: 21px 0;
|
||||
|
||||
@media(max-width: 992px) {
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
img {
|
||||
position: relative;
|
||||
margin-top: -21px;
|
||||
top: 12px;
|
||||
height: 40px;
|
||||
|
||||
@media(max-width: 992px) {
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,10 +149,6 @@ aio-top-menu {
|
||||
padding: 24px 16px;
|
||||
cursor: pointer;
|
||||
|
||||
&.home{
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: rgba($white, 0.15);
|
||||
border-radius: 4px;
|
||||
|
@ -3,8 +3,9 @@ code-example, code-tabs {
|
||||
display: block;
|
||||
}
|
||||
|
||||
code-example,
|
||||
code-tabs mat-tab-body {
|
||||
code-example {
|
||||
|
||||
|
||||
&:not(.no-box) {
|
||||
background-color: rgba($backgroundgray, 0.2);
|
||||
border: 0.5px solid $lightgray;
|
||||
@ -27,6 +28,16 @@ code-tabs mat-tab-body {
|
||||
}
|
||||
}
|
||||
|
||||
code-example, code-tabs {
|
||||
.mat-card {
|
||||
padding: 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
code {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// TERMINAL / SHELL TEXT STYLES
|
||||
|
||||
code-example.code-shell, code-example[language=sh], code-example[language=bash] {
|
||||
@ -86,8 +97,9 @@ aio-code pre {
|
||||
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -32px;
|
||||
top: -7px;
|
||||
right: -19px;
|
||||
padding: 0;
|
||||
|
||||
color: $blue-grey-200;
|
||||
background-color: transparent;
|
||||
@ -106,6 +118,9 @@ aio-code pre {
|
||||
}
|
||||
|
||||
.code-tab-group .mat-tab-label {
|
||||
&:hover {
|
||||
background: rgba(black, 0.04);
|
||||
}
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
@ -56,8 +56,8 @@ export class MockTocService {
|
||||
}
|
||||
|
||||
export class MockElementsLoader {
|
||||
loadContainingCustomElements =
|
||||
jasmine.createSpy('MockElementsLoader#loadContainingCustomElements');
|
||||
loadContainedCustomElements =
|
||||
jasmine.createSpy('MockElementsLoader#loadContainedCustomElements');
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
|
@ -119,6 +119,17 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
|
||||
addNotYetDocumentedProperty.docTypes = API_DOC_TYPES;
|
||||
})
|
||||
|
||||
.config(function(mergeDecoratorDocs) {
|
||||
mergeDecoratorDocs.propertiesToMerge = [
|
||||
'shortDescription',
|
||||
'description',
|
||||
'security',
|
||||
'deprecated',
|
||||
'see',
|
||||
'usageNotes',
|
||||
];
|
||||
})
|
||||
|
||||
.config(function(checkContentRules, EXPORT_DOC_TYPES) {
|
||||
// Min length rules
|
||||
const createMinLengthRule = require('./content-rules/minLength');
|
||||
|
@ -1,3 +1,5 @@
|
||||
const {mergeProperties} = require('../../helpers/utils');
|
||||
|
||||
/**
|
||||
* Decorators in the Angular code base are made up from three code items:
|
||||
*
|
||||
@ -47,6 +49,7 @@ module.exports = function mergeDecoratorDocs(log) {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
propertiesToMerge: [],
|
||||
makeDecoratorCalls: [
|
||||
{type: '', description: 'toplevel', functionName: 'makeDecorator'},
|
||||
{type: 'Prop', description: 'property', functionName: 'makePropDecorator'},
|
||||
@ -55,6 +58,7 @@ module.exports = function mergeDecoratorDocs(log) {
|
||||
$process: function(docs) {
|
||||
|
||||
var makeDecoratorCalls = this.makeDecoratorCalls;
|
||||
var propertiesToMerge = this.propertiesToMerge;
|
||||
var docsToMerge = Object.create(null);
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
@ -88,9 +92,9 @@ module.exports = function mergeDecoratorDocs(log) {
|
||||
log.debug(
|
||||
'mergeDecoratorDocs: merging', doc.name, 'into', decoratorDoc.name,
|
||||
callMember.description.substring(0, 50));
|
||||
|
||||
// Merge the documentation found in this call signature into the original decorator
|
||||
decoratorDoc.description = callMember.description;
|
||||
decoratorDoc.usageNotes = callMember.usageNotes;
|
||||
mergeProperties(decoratorDoc, callMember, propertiesToMerge);
|
||||
|
||||
// remove doc from its module doc's exports
|
||||
doc.moduleDoc.exports =
|
||||
|
@ -9,17 +9,21 @@ describe('mergeDecoratorDocs processor', () => {
|
||||
const injector = dgeni.configureInjector();
|
||||
processor = injector.get('mergeDecoratorDocs');
|
||||
|
||||
// Note that we do not include usageNotes in the tests.
|
||||
processor.propertiesToMerge = ['description', 'shortDescription'];
|
||||
|
||||
moduleDoc = {};
|
||||
|
||||
decoratorDoc = {
|
||||
name: 'Component',
|
||||
docType: 'const',
|
||||
description: 'A description of the metadata for the Component decorator',
|
||||
shortDescription: 'decorator - short description',
|
||||
description: 'decorator - description',
|
||||
symbol: {
|
||||
valueDeclaration: { initializer: { expression: { text: 'makeDecorator' }, arguments: [{ text: 'X' }] } }
|
||||
},
|
||||
members: [
|
||||
{ name: 'templateUrl', description: 'A description of the templateUrl property' }
|
||||
{ name: 'templateUrl', description: 'templateUrl - description' }
|
||||
],
|
||||
moduleDoc
|
||||
};
|
||||
@ -27,15 +31,15 @@ describe('mergeDecoratorDocs processor', () => {
|
||||
metadataDoc = {
|
||||
name: 'ComponentDecorator',
|
||||
docType: 'interface',
|
||||
description: 'A description of the interface for the call signature for the Component decorator',
|
||||
description: 'call interface - description',
|
||||
members: [
|
||||
{
|
||||
isCallMember: true,
|
||||
description: 'The actual description of the call signature',
|
||||
usageNotes: 'Use it like this...'
|
||||
description: 'call interface - call member - description',
|
||||
usageNotes: 'call interface - call member - usageNotes',
|
||||
},
|
||||
{
|
||||
description: 'Some other member'
|
||||
description: 'call interface - non call member - description'
|
||||
}
|
||||
],
|
||||
moduleDoc
|
||||
@ -65,10 +69,13 @@ describe('mergeDecoratorDocs processor', () => {
|
||||
expect(decoratorDoc.decoratorType).toEqual('X');
|
||||
});
|
||||
|
||||
it('should copy across properties from the call signature doc', () => {
|
||||
it('should copy across specified properties from the call signature doc', () => {
|
||||
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
|
||||
expect(decoratorDoc.description).toEqual('The actual description of the call signature');
|
||||
expect(decoratorDoc.usageNotes).toEqual('Use it like this...');
|
||||
expect(decoratorDoc.description).toEqual('call interface - call member - description');
|
||||
// Since usageNotes is not in `propertiesToMerge` it will not get copied over in these tests.
|
||||
expect(decoratorDoc.usageNotes).toBeUndefined();
|
||||
// Since `shortDescription` does not exist on the call-member this will not get overridden.
|
||||
expect(decoratorDoc.shortDescription).toEqual('decorator - short description');
|
||||
});
|
||||
|
||||
it('should remove the metadataDoc from the module exports', () => {
|
||||
|
@ -34,6 +34,7 @@ module.exports = function checkContentRules(log, createDocMessage) {
|
||||
$runAfter: ['tags-extracted'],
|
||||
$runBefore: ['processing-docs'],
|
||||
$process(docs) {
|
||||
const logMessage = this.failOnContentErrors ? log.error.bind(log) : log.warn.bind(log);
|
||||
const errors = [];
|
||||
docs.forEach(doc => {
|
||||
const docErrors = [];
|
||||
@ -55,10 +56,10 @@ module.exports = function checkContentRules(log, createDocMessage) {
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
log.error('Content contains errors');
|
||||
logMessage('Content contains errors');
|
||||
errors.forEach(docError => {
|
||||
const errors = docError.errors.join('\n ');
|
||||
log.error(createDocMessage(errors + '\n ', docError.doc));
|
||||
logMessage(createDocMessage(errors + '\n ', docError.doc));
|
||||
});
|
||||
if (this.failOnContentErrors) {
|
||||
throw new Error('Stopping due to content errors.');
|
||||
|
@ -67,10 +67,11 @@ describe('checkContentRules processor', function() {
|
||||
expect(descriptionSpy3).toHaveBeenCalledWith(docs[0], 'description', 'test doc 1');
|
||||
});
|
||||
|
||||
it('should log errors if the rule returns error messages', () => {
|
||||
it('should log warnings if the rule returns error messages and `failOnContentErrors` is false', () => {
|
||||
const nameSpy1 = jasmine.createSpy('name 1').and.returnValue('name error message');
|
||||
const descriptionSpy1 = jasmine.createSpy('description 1').and.returnValue('description error message');
|
||||
|
||||
processor.failOnContentErrors = false;
|
||||
processor.docTypeRules = {
|
||||
'test1': {
|
||||
name: [nameSpy1],
|
||||
@ -85,6 +86,32 @@ describe('checkContentRules processor', function() {
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledTimes(2);
|
||||
expect(logger.warn).toHaveBeenCalledWith('Content contains errors');
|
||||
expect(logger.warn).toHaveBeenCalledWith(`name error message
|
||||
description error message
|
||||
- doc "test-1" (test1) `);
|
||||
});
|
||||
|
||||
it('should log errors and then throw if `failOnContentErrors` is true and errors are found', () => {
|
||||
const nameSpy1 = jasmine.createSpy('name 1').and.returnValue('name error message');
|
||||
const descriptionSpy1 = jasmine.createSpy('description 1').and.returnValue('description error message');
|
||||
|
||||
processor.failOnContentErrors = true;
|
||||
processor.docTypeRules = {
|
||||
'test1': {
|
||||
name: [nameSpy1],
|
||||
description: [descriptionSpy1]
|
||||
}
|
||||
};
|
||||
|
||||
const docs = [
|
||||
{ docType: 'test1', description: 'test doc 1', name: 'test-1' },
|
||||
{ docType: 'test2', description: 'test doc 2', name: 'test-2' }
|
||||
];
|
||||
|
||||
expect(() => processor.$process(docs)).toThrowError('Stopping due to content errors.');
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(2);
|
||||
expect(logger.error).toHaveBeenCalledWith('Content contains errors');
|
||||
expect(logger.error).toHaveBeenCalledWith(`name error message
|
||||
@ -92,17 +119,4 @@ describe('checkContentRules processor', function() {
|
||||
- doc "test-1" (test1) `);
|
||||
});
|
||||
|
||||
it('should throw an error if `failOnContentErrors` is true and errors are found', () => {
|
||||
const errorRule = jasmine.createSpy('error rule').and.returnValue('some error');
|
||||
processor.docTypeRules = {
|
||||
'test': { description: [errorRule] }
|
||||
};
|
||||
processor.failOnContentErrors = true;
|
||||
|
||||
const docs = [
|
||||
{ docType: 'test', description: 'test doc' },
|
||||
];
|
||||
expect(() => processor.$process(docs)).toThrowError('Stopping due to content errors.');
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -96,5 +96,19 @@ module.exports = {
|
||||
attrMap[key] === false ? '' :
|
||||
attrMap[key] === true ? ` ${key}` :
|
||||
` ${key}="${attrMap[key].replace(/"/g, '"')}"`).join('');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Merge the specified properties from the source to the target document
|
||||
* @param {Document} target The document to receive the properties from the source
|
||||
* @param {Document} source The document from which to get the properties to merge
|
||||
* @param {string[]} properties A collection of the names of the properties to merge
|
||||
*/
|
||||
mergeProperties(target, source, properties) {
|
||||
properties.forEach(property => {
|
||||
if (source.hasOwnProperty(property)) {
|
||||
target[property] = source[property];
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { mapObject, parseAttributes, renderAttributes } = require('./utils');
|
||||
const { mergeProperties, mapObject, parseAttributes, renderAttributes } = require('./utils');
|
||||
|
||||
describe('utils', () => {
|
||||
describe('mapObject', () => {
|
||||
@ -96,4 +96,34 @@ describe('utils', () => {
|
||||
expect(renderAttributes({ })).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeProperties', () => {
|
||||
it('should write specified properties from the source to the target', () => {
|
||||
const source = { a: 1, b: 2, c: 3 };
|
||||
const target = { };
|
||||
mergeProperties(target, source, ['a', 'b']);
|
||||
expect(target).toEqual({ a: 1, b: 2 });
|
||||
});
|
||||
|
||||
it('should not overwrite target properties that are not specified', () => {
|
||||
const source = { a: 1, b: 2, c: 3 };
|
||||
const target = { b: 10 };
|
||||
mergeProperties(target, source, ['a']);
|
||||
expect(target).toEqual({ a: 1, b: 10 });
|
||||
});
|
||||
|
||||
it('should not overwrite target properties that are specified but do not exist in the source', () => {
|
||||
const source = { a: 1 };
|
||||
const target = { b: 10 };
|
||||
mergeProperties(target, source, ['a', 'b']);
|
||||
expect(target).toEqual({ a: 1, b: 10 });
|
||||
});
|
||||
|
||||
it('should overwrite target properties even if they are `undefined` in the source', () => {
|
||||
const source = { a: undefined };
|
||||
const target = { a: 10 };
|
||||
mergeProperties(target, source, ['a']);
|
||||
expect(target).toEqual({ a: undefined });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -37,7 +37,7 @@ That repository defines dependencies on specific versions of
|
||||
all the tools. You can run the tools Bazel installed, for
|
||||
example rather than `yarn install` (which depends on whatever
|
||||
version you have installed on your machine), you can
|
||||
`bazel run @yarn//:yarn`.
|
||||
`bazel run @nodejs//:yarn`.
|
||||
|
||||
Bazel accepts a lot of options. We check in some options in the
|
||||
`.bazelrc` file. See the [bazelrc doc]. For example, if you don't
|
||||
|
@ -12,7 +12,7 @@ The owner of the component is then responsible for the secondary / component-lev
|
||||
The caretaker should be able to determine which component the issue belongs to.
|
||||
The components have a clear piece of source code associated with it within the `/packages/` folder of this repo.
|
||||
|
||||
* `comp: aio` - the angular.io application
|
||||
* `comp: docs-infra` - the angular.io application
|
||||
* `comp: animations`
|
||||
* `comp: bazel` - @angular/bazel rules
|
||||
* `comp: benchpress`
|
||||
|
@ -1,3 +0,0 @@
|
||||
# Workaround https://github.com/bazelbuild/bazel/issues/3645
|
||||
# Limit Bazel to consuming 3072K of RAM
|
||||
build --local_resources=3072,2.0,1.0
|
@ -17,3 +17,25 @@ filegroup(
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
ANGULAR_TESTING = [
|
||||
"node_modules/@angular/*/bundles/*-testing.umd.js",
|
||||
# We use AOT, so the compiler and the dynamic platform-browser should be
|
||||
# visible only in tests
|
||||
"node_modules/@angular/compiler/bundles/*.umd.js",
|
||||
"node_modules/@angular/platform-browser-dynamic/bundles/*.umd.js",
|
||||
]
|
||||
|
||||
filegroup(
|
||||
name = "angular_bundles",
|
||||
srcs = glob(
|
||||
["node_modules/@angular/*/bundles/*.umd.js"],
|
||||
exclude = ANGULAR_TESTING,
|
||||
),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "angular_test_bundles",
|
||||
testonly = 1,
|
||||
srcs = glob(ANGULAR_TESTING),
|
||||
)
|
||||
|
@ -1,24 +1,77 @@
|
||||
workspace(name = "bazel_integration_test")
|
||||
|
||||
#
|
||||
# Download Bazel toolchain dependencies as needed by build actions
|
||||
#
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.8.0.zip",
|
||||
strip_prefix = "rules_nodejs-0.8.0",
|
||||
sha256 = "4e40dd49ae7668d245c3107645f2a138660fcfd975b9310b91eda13f0c973953",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip",
|
||||
strip_prefix = "rules_nodejs-0.9.1",
|
||||
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
|
||||
http_archive(
|
||||
name = "io_bazel_rules_webtesting",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip",
|
||||
strip_prefix = "rules_webtesting-0.2.0",
|
||||
sha256 = "cecc12f07e95740750a40d38e8b14b76fefa1551bef9332cb432d564d693723c",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.0.zip",
|
||||
strip_prefix = "rules_typescript-0.15.0",
|
||||
sha256 = "1aa75917330b820cb239b3c10a936a10f0a46fe215063d4492dd76dc6e1616f4",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_go",
|
||||
url = "https://github.com/bazelbuild/rules_go/releases/download/0.11.0/rules_go-0.11.0.tar.gz",
|
||||
sha256 = "f70c35a8c779bb92f7521ecb5a1c6604e9c3edd431e50b6376d7497abc8ad3c1",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
url = "https://github.com/bazelbuild/rules_sass/archive/0.0.3.zip",
|
||||
strip_prefix = "rules_sass-0.0.3",
|
||||
sha256 = "8fa98e7b48a5837c286a1ea254b5a5c592fced819ee9fe4fdd759768d97be868",
|
||||
)
|
||||
|
||||
#
|
||||
# Load and install our dependencies downloaded above.
|
||||
#
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
|
||||
|
||||
check_bazel_version("0.13.0")
|
||||
node_repositories(package_json = ["//:package.json"])
|
||||
|
||||
local_repository(
|
||||
name = "build_bazel_rules_typescript",
|
||||
path = "node_modules/@bazel/typescript",
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
|
||||
go_rules_dependencies()
|
||||
go_register_toolchains()
|
||||
|
||||
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
|
||||
|
||||
web_test_repositories()
|
||||
browser_repositories(
|
||||
chromium = True,
|
||||
firefox = True,
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
|
||||
|
||||
ts_setup_workspace()
|
||||
|
||||
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_repositories")
|
||||
|
||||
sass_repositories()
|
||||
|
||||
#
|
||||
# Point Bazel to WORKSPACEs that live in subdirectories
|
||||
#
|
||||
|
||||
local_repository(
|
||||
name = "angular",
|
||||
path = "node_modules/@angular/bazel",
|
||||
@ -28,14 +81,3 @@ local_repository(
|
||||
name = "rxjs",
|
||||
path = "node_modules/rxjs/src",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
url = "https://github.com/bazelbuild/rules_sass/archive/0.0.3.zip",
|
||||
strip_prefix = "rules_sass-0.0.3",
|
||||
sha256 = "8fa98e7b48a5837c286a1ea254b5a5c592fced819ee9fe4fdd759768d97be868",
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_repositories")
|
||||
|
||||
sass_repositories()
|
||||
|
@ -9,17 +9,26 @@
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"zone.js": "file:../../node_modules/zone.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/bazel": "file:../../dist/packages-dist/bazel",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"typescript": "file:../../node_modules/typescript",
|
||||
"@types/source-map": "0.5.1"
|
||||
"@types/jasmine": "2.8.7",
|
||||
"@types/source-map": "0.5.1",
|
||||
"concurrently": "3.5.1",
|
||||
"http-server": "0.11.1",
|
||||
"protractor": "file:../../node_modules/protractor",
|
||||
"typescript": "file:../../node_modules/typescript"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "ngc -p angular.tsconfig.json",
|
||||
"test": "bazel build //... --noshow_progress"
|
||||
"postinstall": "concurrently \"webdriver-manager update $CHROMEDRIVER_VERSION_ARG\" \"ngc -p angular.tsconfig.json\"",
|
||||
"test": "bazel build //... --noshow_progress && yarn e2e",
|
||||
"pree2e": "bazel build test/...",
|
||||
"e2e": "yarn e2e-prodserver && yarn e2e-devserver",
|
||||
"e2e-prodserver": "concurrently \"bazel run //src:prodserver\" \"while ! nc -z 127.0.0.1 5432; do sleep 1; done && protractor\" --kill-others --success first",
|
||||
"e2e-devserver": "concurrently \"bazel run //src:devserver\" \"while ! nc -z 127.0.0.1 5432; do sleep 1; done && protractor\" --kill-others --success first"
|
||||
}
|
||||
}
|
9
integration/bazel/protractor.conf.js
Normal file
9
integration/bazel/protractor.conf.js
Normal file
@ -0,0 +1,9 @@
|
||||
exports.config = {
|
||||
specs: ['bazel-bin/test/e2e/*.spec.js'],
|
||||
capabilities: {browserName: 'chrome', chromeOptions: {args: ['--no-sandbox']}},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:5432/',
|
||||
framework: 'jasmine',
|
||||
getPageTimeout: 60 * 1000,
|
||||
allScriptsTimeout: 60 * 1000,
|
||||
};
|
@ -9,3 +9,52 @@ ng_module(
|
||||
tsconfig = ":tsconfig.json",
|
||||
deps = ["//src/hello-world"],
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_devserver")
|
||||
|
||||
ts_devserver(
|
||||
name = "devserver",
|
||||
additional_root_paths = [
|
||||
"bazel_integration_test/node_modules/zone.js/dist",
|
||||
],
|
||||
entry_module = "bazel_integration_test/src/main",
|
||||
scripts = ["//:angular_bundles"],
|
||||
serving_path = "/bundle.min.js",
|
||||
static_files = [
|
||||
"//:node_modules/zone.js/dist/zone.min.js",
|
||||
"index.html",
|
||||
],
|
||||
deps = ["//src"],
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "rollup_bundle", "nodejs_binary")
|
||||
|
||||
rollup_bundle(
|
||||
name = "bundle",
|
||||
entry_point = "src/main",
|
||||
deps = ["//src"],
|
||||
)
|
||||
|
||||
# Needed because the prodserver only loads static files that appear under this
|
||||
# package.
|
||||
genrule(
|
||||
name = "zone.js",
|
||||
srcs = ["//:node_modules/zone.js/dist/zone.min.js"],
|
||||
outs = ["zone.min.js"],
|
||||
cmd = "cp $< $@",
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = "prodserver",
|
||||
args = [
|
||||
"./src",
|
||||
"-p",
|
||||
"5432",
|
||||
],
|
||||
data = [
|
||||
"index.html",
|
||||
":bundle",
|
||||
":zone.js",
|
||||
],
|
||||
entry_point = "http-server/bin/http-server",
|
||||
)
|
||||
|
19
integration/bazel/src/app.component.ts
Normal file
19
integration/bazel/src/app.component.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
import {map, startWith} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-component',
|
||||
template: `
|
||||
<hello-world-app></hello-world-app>
|
||||
<div>The current time is {{ time$ | async }}</div>
|
||||
`})
|
||||
export class AppComponent {
|
||||
constructor(private http: HttpClient) {
|
||||
}
|
||||
|
||||
time$ = this.http.get('http://worldclockapi.com/api/json/pst/now').pipe(
|
||||
map((result: any) => result.currentDateTime),
|
||||
startWith(['...']));
|
||||
}
|
@ -1,9 +1,14 @@
|
||||
import {HelloWorldModule} from './hello-world/hello-world.module';
|
||||
|
||||
import {NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {HttpClientModule} from '@angular/common/http';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
import {AppComponent} from './app.component';
|
||||
import {HelloWorldModule} from './hello-world/hello-world.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, HelloWorldModule]
|
||||
imports: [CommonModule, BrowserModule, HttpClientModule, HelloWorldModule],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
@ -2,6 +2,7 @@ package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@angular//:index.bzl", "ng_module", "ng_package")
|
||||
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary")
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test_suite")
|
||||
|
||||
sass_binary(
|
||||
name = "hello-world-styles",
|
||||
@ -10,7 +11,10 @@ sass_binary(
|
||||
|
||||
ng_module(
|
||||
name = "hello-world",
|
||||
srcs = glob(["*.ts"]),
|
||||
srcs = glob(
|
||||
["*.ts"],
|
||||
exclude = ["*.spec.ts"],
|
||||
),
|
||||
assets = [":hello-world-styles.css"],
|
||||
tsconfig = "//src:tsconfig.json",
|
||||
# FIXME(alexeagle): the rxjs dep should come from Angular, but if we use the
|
||||
@ -23,3 +27,24 @@ ng_package(
|
||||
entry_point = "src/hello-world/index.js",
|
||||
deps = [":hello-world"],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = 1,
|
||||
srcs = glob(["*.spec.ts"]),
|
||||
deps = [":hello-world"],
|
||||
)
|
||||
|
||||
ts_web_test_suite(
|
||||
name = "test",
|
||||
bootstrap = ["//:node_modules/zone.js/dist/zone-testing-bundle.js"],
|
||||
browsers = [
|
||||
"@io_bazel_rules_webtesting//browsers:chromium-local",
|
||||
"@io_bazel_rules_webtesting//browsers:firefox-local",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
"//:angular_bundles",
|
||||
"//:angular_test_bundles",
|
||||
],
|
||||
)
|
||||
|
@ -0,0 +1,45 @@
|
||||
import {DebugElement} from '@angular/core';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
import {HelloWorldComponent} from './hello-world.component';
|
||||
import {HelloWorldModuleNgSummary} from './hello-world.module.ngsummary';
|
||||
|
||||
// TODO(alexeagle): this helper should be in @angular/platform-browser-dynamic/testing
|
||||
try {
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
} catch {
|
||||
// Ignore exceptions when calling it multiple times.
|
||||
}
|
||||
|
||||
describe('BannerComponent (inline template)', () => {
|
||||
let comp: HelloWorldComponent;
|
||||
let fixture: ComponentFixture<HelloWorldComponent>;
|
||||
let el: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [HelloWorldComponent], // declare the test component
|
||||
aotSummaries: HelloWorldModuleNgSummary,
|
||||
});
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HelloWorldComponent);
|
||||
comp = fixture.componentInstance;
|
||||
el = fixture.debugElement.query(By.css('div')).nativeElement;
|
||||
});
|
||||
|
||||
it('should display original title', () => {
|
||||
fixture.detectChanges();
|
||||
expect(el.textContent).toContain(comp.name);
|
||||
});
|
||||
|
||||
it('should display a different test title', () => {
|
||||
comp.name = 'Test';
|
||||
fixture.detectChanges();
|
||||
expect(el.textContent).toContain('Test');
|
||||
});
|
||||
});
|
@ -3,6 +3,6 @@ import {NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [HelloWorldComponent],
|
||||
bootstrap: [HelloWorldComponent],
|
||||
exports: [HelloWorldComponent],
|
||||
})
|
||||
export class HelloWorldModule {}
|
||||
|
12
integration/bazel/src/index.html
Normal file
12
integration/bazel/src/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Bazel Integration Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<app-component></app-component>
|
||||
<script src="/zone.min.js"></script>
|
||||
<script src="/bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
7
integration/bazel/test/e2e/BUILD.bazel
Normal file
7
integration/bazel/test/e2e/BUILD.bazel
Normal file
@ -0,0 +1,7 @@
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "e2e",
|
||||
testonly = 1,
|
||||
srcs = glob(["*.ts"]),
|
||||
)
|
12
integration/bazel/test/e2e/app.spec.ts
Normal file
12
integration/bazel/test/e2e/app.spec.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {browser, by, element, ExpectedConditions} from 'protractor';
|
||||
|
||||
describe('angular example application', () => {
|
||||
it('should display: Hello World!', (done) => {
|
||||
browser.get('');
|
||||
const div = element(by.css('div'));
|
||||
div.getText().then(t => expect(t).toEqual(`Hello world!`));
|
||||
element(by.css('input')).sendKeys('!');
|
||||
div.getText().then(t => expect(t).toEqual(`Hello world!!`));
|
||||
done();
|
||||
});
|
||||
});
|
10
integration/bazel/tools/bazel.rc
Normal file
10
integration/bazel/tools/bazel.rc
Normal file
@ -0,0 +1,10 @@
|
||||
# Make TypeScript and Angular compilation fast, by keeping a few copies of the
|
||||
# compiler running as daemons, and cache SourceFile AST's to reduce parse time.
|
||||
build --strategy=TypeScriptCompile=worker
|
||||
build --strategy=AngularTemplateCompile=worker
|
||||
|
||||
test --test_output=errors
|
||||
|
||||
# Workaround https://github.com/bazelbuild/bazel/issues/3645
|
||||
# Limit Bazel to consuming 3072K of RAM
|
||||
build --local_resources=3072,2.0,1.0
|
File diff suppressed because it is too large
Load Diff
@ -92,6 +92,16 @@ module.exports = function(config) {
|
||||
'**/*.js': ['sourcemap'],
|
||||
},
|
||||
|
||||
// Bazel inter-op: Allow tests to request resources from either
|
||||
// /base/node_modules/path/to/thing
|
||||
// or
|
||||
// /base/angular/node_modules/path/to/thing
|
||||
// This can be removed when all karma tests are run under Bazel, then we
|
||||
// don't need this entire config file.
|
||||
proxies: {
|
||||
'/base/angular/': '/base/',
|
||||
},
|
||||
|
||||
reporters: ['internal-angular'],
|
||||
sauceLabs: {
|
||||
testName: 'Angular2',
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "6.0.3",
|
||||
"version": "6.0.4",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
@ -110,7 +110,7 @@
|
||||
"source-map": "0.5.7",
|
||||
"source-map-support": "0.4.18",
|
||||
"systemjs": "0.18.10",
|
||||
"tsickle": "^0.27.2",
|
||||
"tsickle": "^0.29.0",
|
||||
"tslint": "5.7.0",
|
||||
"tslint-eslint-rules": "4.1.1",
|
||||
"tsutils": "2.20.0",
|
||||
|
@ -10,6 +10,10 @@ import {AUTO_STYLE, AnimationEvent, AnimationPlayer, NoopAnimationPlayer, ɵAnim
|
||||
import {AnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer';
|
||||
import {AnimationDriver} from '../../src/render/animation_driver';
|
||||
|
||||
export function isBrowser() {
|
||||
return (typeof window !== 'undefined' && typeof window.document !== 'undefined');
|
||||
}
|
||||
|
||||
export function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer {
|
||||
switch (players.length) {
|
||||
case 0:
|
||||
@ -138,7 +142,7 @@ let _query: (element: any, selector: string, multi: boolean) => any[] =
|
||||
return [];
|
||||
};
|
||||
|
||||
if (typeof Element != 'undefined') {
|
||||
if (isBrowser()) {
|
||||
// this is well supported in all browsers
|
||||
_contains = (elm1: any, elm2: any) => { return elm1.contains(elm2) as boolean; };
|
||||
|
||||
|
@ -104,7 +104,6 @@ export class StateValue {
|
||||
|
||||
export const VOID_VALUE = 'void';
|
||||
export const DEFAULT_STATE_VALUE = new StateValue(VOID_VALUE);
|
||||
export const DELETED_STATE_VALUE = new StateValue('DELETED');
|
||||
|
||||
export class AnimationTransitionNamespace {
|
||||
public players: TransitionAnimationPlayer[] = [];
|
||||
@ -208,8 +207,6 @@ export class AnimationTransitionNamespace {
|
||||
|
||||
if (!fromState) {
|
||||
fromState = DEFAULT_STATE_VALUE;
|
||||
} else if (fromState === DELETED_STATE_VALUE) {
|
||||
return player;
|
||||
}
|
||||
|
||||
const isRemoval = toState.value === VOID_VALUE;
|
||||
@ -773,10 +770,6 @@ export class TransitionAnimationEngine {
|
||||
}
|
||||
});
|
||||
}
|
||||
const stateMap = this.statesByElement.get(element);
|
||||
if (stateMap) {
|
||||
Object.keys(stateMap).forEach(triggerName => stateMap[triggerName] = DELETED_STATE_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
finishActiveQueriedAnimationOnElement(element: any) {
|
||||
|
@ -10,7 +10,7 @@ import {AnimationPlayer, ɵStyleData} from '@angular/animations';
|
||||
import {allowPreviousPlayerStylesMerge, balancePreviousStylesIntoKeyframes, copyStyles} from '../../util';
|
||||
import {AnimationDriver} from '../animation_driver';
|
||||
import {CssKeyframesDriver} from '../css_keyframes/css_keyframes_driver';
|
||||
import {containsElement, invokeQuery, matchesElement, validateStyleProperty} from '../shared';
|
||||
import {containsElement, invokeQuery, isBrowser, matchesElement, validateStyleProperty} from '../shared';
|
||||
|
||||
import {WebAnimationsPlayer} from './web_animations_player';
|
||||
|
||||
@ -75,5 +75,5 @@ export function supportsWebAnimations() {
|
||||
}
|
||||
|
||||
function getElementAnimateFn(): any {
|
||||
return (typeof Element !== 'undefined' && (<any>Element).prototype['animate']) || {};
|
||||
return (isBrowser() && (<any>Element).prototype['animate']) || {};
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ function createDiv() {
|
||||
{
|
||||
describe('Animation', () => {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
if (typeof Element == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
let rootElement: any;
|
||||
let subElement1: any;
|
||||
|
@ -17,7 +17,7 @@ import {makeTrigger} from '../shared';
|
||||
{
|
||||
describe('AnimationTrigger', () => {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
if (typeof Element == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
let element: any;
|
||||
beforeEach(() => {
|
||||
|
@ -16,9 +16,7 @@ import {assertElementExistsInDom, createElement, findKeyframeDefinition, forceRe
|
||||
const CSS_KEYFRAME_RULE_TYPE = 7;
|
||||
|
||||
describe('CssKeyframesDriver tests', () => {
|
||||
if (typeof Element == 'undefined' || typeof document == 'undefined' ||
|
||||
typeof(window as any)['AnimationEvent'] == 'undefined')
|
||||
return;
|
||||
if (isNode || typeof(window as any)['AnimationEvent'] == 'undefined') return;
|
||||
|
||||
describe('building keyframes', () => {
|
||||
it('should build CSS keyframe style object containing the keyframe styles', () => {
|
||||
|
@ -14,7 +14,7 @@ import {assertStyle, createElement} from './shared';
|
||||
const CSS_KEYFRAME_RULE_TYPE = 7;
|
||||
|
||||
describe('DirectStylePlayer tests', () => {
|
||||
if (typeof Element == 'undefined' || typeof document == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
it('should apply the styling to the given element when the animation starts and remove when destroyed',
|
||||
() => {
|
||||
|
@ -13,9 +13,7 @@ import {assertStyle, createElement, makeAnimationEvent, supportsAnimationEventCr
|
||||
const EMPTY_FN = () => {};
|
||||
{
|
||||
describe('ElementAnimationStyleHandler', () => {
|
||||
if (typeof Element == 'undefined' || typeof document == 'undefined' ||
|
||||
typeof(window as any)['AnimationEvent'] == 'undefined')
|
||||
return;
|
||||
if (isNode || typeof(window as any)['AnimationEvent'] == 'undefined') return;
|
||||
|
||||
it('should add and remove an animation on to an element\'s styling', () => {
|
||||
const element = createElement();
|
||||
|
@ -21,7 +21,7 @@ import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_a
|
||||
}
|
||||
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (typeof Element == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
describe('TimelineAnimationEngine', () => {
|
||||
let element: any;
|
||||
|
@ -20,7 +20,7 @@ const DEFAULT_NAMESPACE_ID = 'id';
|
||||
const driver = new MockAnimationDriver();
|
||||
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (typeof Element == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
describe('TransitionAnimationEngine', () => {
|
||||
let element: any;
|
||||
|
@ -12,7 +12,7 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat
|
||||
|
||||
{
|
||||
describe('WebAnimationsDriver', () => {
|
||||
if (typeof Element == 'undefined' || typeof document == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
describe('when web-animations are not supported natively', () => {
|
||||
it('should return an instance of a CssKeyframePlayer if scrubbing is not requested', () => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@angular/animations",
|
||||
"version": "0.0.0-PLACEHOLDER",
|
||||
"description": "Angular - animations integration with web-animationss",
|
||||
"description": "Angular - animations integration with web-animations",
|
||||
"main": "./bundles/animations.umd.js",
|
||||
"module": "./fesm5/animations.js",
|
||||
"es2015": "./fesm2015/animations.js",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"author": "angular",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bazel/typescript": "^0.11.1",
|
||||
"@bazel/typescript": "^0.15.0",
|
||||
"@types/node": "6.0.84",
|
||||
"@types/shelljs": "0.7.7",
|
||||
"shelljs": "0.7.8",
|
||||
|
@ -211,7 +211,8 @@ function main(args: string[]): number {
|
||||
const entryPointName = entryPointPackageName.substr(rootPackageName.length + 1);
|
||||
if (!entryPointName) return;
|
||||
|
||||
createMetadataReexportFile(entryPointName, modulesManifest[entryPointPackageName]['metadata']);
|
||||
createMetadataReexportFile(
|
||||
entryPointName, modulesManifest[entryPointPackageName]['metadata'], entryPointPackageName);
|
||||
createTypingsReexportFile(
|
||||
entryPointName, licenseBanner, modulesManifest[entryPointPackageName]['typings']);
|
||||
|
||||
@ -274,6 +275,8 @@ function main(args: string[]): number {
|
||||
// So ignore package.json files when we are missing data.
|
||||
console.error('WARNING: no module metadata for package', packageName);
|
||||
console.error(' Not updating the package.json file to point to it');
|
||||
console.error(
|
||||
' The ng_module for this package is possibly missing the module_name attribute ');
|
||||
return JSON.stringify(parsedPackage, null, 2);
|
||||
}
|
||||
|
||||
@ -316,7 +319,8 @@ function main(args: string[]): number {
|
||||
}
|
||||
|
||||
/** Creates metadata re-export file for a secondary entry-point. */
|
||||
function createMetadataReexportFile(entryPointName: string, metadataFile: string) {
|
||||
function createMetadataReexportFile(
|
||||
entryPointName: string, metadataFile: string, packageName: string) {
|
||||
const inputPath = path.join(srcDir, `${entryPointName}.metadata.json`);
|
||||
writeFileFromInputPath(inputPath, JSON.stringify({
|
||||
'__symbolic': 'module',
|
||||
@ -325,6 +329,7 @@ function main(args: string[]): number {
|
||||
'exports':
|
||||
[{'from': `${srcDirRelative(inputPath, metadataFile.replace(/.metadata.json$/, ''))}`}],
|
||||
'flatModuleIndexRedirect': true,
|
||||
'importAs': packageName
|
||||
}) + '\n');
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ export {HttpClient} from './src/client';
|
||||
export {HttpHeaders} from './src/headers';
|
||||
export {HTTP_INTERCEPTORS, HttpInterceptor} from './src/interceptor';
|
||||
export {JsonpClientBackend, JsonpInterceptor} from './src/jsonp';
|
||||
export {HttpClientJsonpModule, HttpClientModule, HttpClientXsrfModule, interceptingHandler as ɵinterceptingHandler} from './src/module';
|
||||
export {HttpClientJsonpModule, HttpClientModule, HttpClientXsrfModule, HttpInterceptingHandler as ɵHttpInterceptingHandler} from './src/module';
|
||||
export {HttpParameterCodec, HttpParams, HttpUrlEncodingCodec} from './src/params';
|
||||
export {HttpRequest} from './src/request';
|
||||
export {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpResponseBase, HttpSentEvent, HttpUserEvent} from './src/response';
|
||||
|
@ -42,23 +42,6 @@ export class HttpInterceptingHandler implements HttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an `HttpHandler` that applies a bunch of `HttpInterceptor`s
|
||||
* to a request before passing it to the given `HttpBackend`.
|
||||
*
|
||||
* Meant to be used as a factory function within `HttpClientModule`.
|
||||
*
|
||||
*
|
||||
*/
|
||||
export function interceptingHandler(
|
||||
backend: HttpBackend, interceptors: HttpInterceptor[] | null = []): HttpHandler {
|
||||
if (!interceptors) {
|
||||
return backend;
|
||||
}
|
||||
return interceptors.reduceRight(
|
||||
(next, interceptor) => new HttpInterceptorHandler(next, interceptor), backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function that determines where to store JSONP callbacks.
|
||||
*
|
||||
|
@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0",
|
||||
"tsickle": "^0.27.2",
|
||||
"tsickle": "^0.29.0",
|
||||
"chokidar": "^1.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -312,7 +312,7 @@ export interface Program {
|
||||
* and CSS.
|
||||
*
|
||||
* Note it is important to displaying TypeScript semantic diagnostics along with Angular
|
||||
* structural diagnostics as an error in the program strucutre might cause errors detected in
|
||||
* structural diagnostics as an error in the program structure might cause errors detected in
|
||||
* semantic analysis and a semantic error might cause errors in specifying the program structure.
|
||||
*
|
||||
* Angular structural information is required to produce these diagnostics.
|
||||
|
@ -672,7 +672,7 @@ describe('ngc transformer command-line', () => {
|
||||
expect(mymoduleSource).toContain('args: [{ declarations: [] },] }');
|
||||
expect(mymoduleSource).not.toContain(`__metadata`);
|
||||
expect(mymoduleSource).toContain(`import { AClass } from './aclass';`);
|
||||
expect(mymoduleSource).toContain(`{ type: AClass, }`);
|
||||
expect(mymoduleSource).toContain(`{ type: AClass }`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -12,7 +12,6 @@ import {ValueTransformer, visitValue} from '../util';
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
import {isGeneratedFile, stripSummaryForJitFileSuffix, stripSummaryForJitNameSuffix, summaryForJitFileName, summaryForJitName} from './util';
|
||||
|
||||
const DTS = /\.d\.ts$/;
|
||||
const TS = /^(?!.*\.d\.ts$).*\.ts$/;
|
||||
|
||||
export class ResolvedStaticSymbol {
|
||||
@ -334,7 +333,7 @@ export class StaticSymbolResolver {
|
||||
}
|
||||
|
||||
// handle the actual metadata. Has to be after the exports
|
||||
// as there migth be collisions in the names, and we want the symbols
|
||||
// as there might be collisions in the names, and we want the symbols
|
||||
// of the current module to win ofter reexports.
|
||||
if (metadata['metadata']) {
|
||||
// handle direct declarations of the symbol
|
||||
@ -387,7 +386,7 @@ export class StaticSymbolResolver {
|
||||
let _originalFileMemo: string|undefined;
|
||||
const getOriginalName: () => string = () => {
|
||||
if (!_originalFileMemo) {
|
||||
// Guess what hte original file name is from the reference. If it has a `.d.ts` extension
|
||||
// Guess what the original file name is from the reference. If it has a `.d.ts` extension
|
||||
// replace it with `.ts`. If it already has `.ts` just leave it in place. If it doesn't have
|
||||
// .ts or .d.ts, append `.ts'. Also, if it is in `node_modules`, trim the `node_module`
|
||||
// location as it is not important to finding the file.
|
||||
|
@ -993,7 +993,7 @@ export class CompileMetadataResolver {
|
||||
providerMeta = new cpl.ProviderMeta(provider, {useClass: provider});
|
||||
} else if (provider === void 0) {
|
||||
this._reportError(syntaxError(
|
||||
`Encountered undefined provider! Usually this means you have a circular dependencies (might be caused by using 'barrel' index.ts files.`));
|
||||
`Encountered undefined provider! Usually this means you have a circular dependencies. This might be caused by using 'barrel' index.ts files.`));
|
||||
return;
|
||||
} else {
|
||||
const providersInfo =
|
||||
|
@ -11,8 +11,6 @@ import {CollectorOptions, METADATA_VERSION} from '@angular/compiler-cli';
|
||||
import {MetadataCollector} from '@angular/compiler-cli/src/metadata/collector';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
|
||||
|
||||
// This matches .ts files but not .d.ts files.
|
||||
const TS_EXT = /(^.|(?!\.d)..)\.ts$/;
|
||||
|
||||
|
@ -20,7 +20,7 @@ const DEFAULT_COMPONENT_ID = '1';
|
||||
|
||||
(function() {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
if (typeof Element == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
describe('animation tests', function() {
|
||||
function getLog(): MockAnimationPlayer[] {
|
||||
@ -2379,6 +2379,65 @@ const DEFAULT_COMPONENT_ID = '1';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should animate nodes properly when they have been re-ordered', () => {
|
||||
@Component({
|
||||
selector: 'if-cmp',
|
||||
template: `
|
||||
<div *ngFor="let item of items" [class]="'class-' + item.value">
|
||||
<div [@myAnimation]="item.count">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
state('0', style({opacity: 0})), state('1', style({opacity: 0.4})),
|
||||
state('2', style({opacity: 0.8})), transition('* => 1, * => 2', [animate(1000)])
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
items = [
|
||||
{value: '1', count: 0},
|
||||
{value: '2', count: 0},
|
||||
{value: '3', count: 0},
|
||||
{value: '4', count: 0},
|
||||
{value: '5', count: 0},
|
||||
];
|
||||
|
||||
reOrder() {
|
||||
this.items = [
|
||||
this.items[4],
|
||||
this.items[1],
|
||||
this.items[3],
|
||||
this.items[0],
|
||||
this.items[2],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
const one = cmp.items[0];
|
||||
const two = cmp.items[1];
|
||||
one.count++;
|
||||
fixture.detectChanges();
|
||||
|
||||
cmp.reOrder();
|
||||
fixture.detectChanges();
|
||||
resetLog();
|
||||
|
||||
one.count++;
|
||||
two.count++;
|
||||
fixture.detectChanges();
|
||||
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('animation listeners', () => {
|
||||
|
@ -21,7 +21,7 @@ import {fakeAsync, flushMicrotasks} from '../../testing/src/fake_async';
|
||||
|
||||
(function() {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
if (typeof Element == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
describe('animation query tests', function() {
|
||||
function getLog(): MockAnimationPlayer[] {
|
||||
|
@ -16,7 +16,7 @@ import {RouterTestingModule} from '@angular/router/testing';
|
||||
|
||||
(function() {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
if (typeof Element == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
describe('Animation Router Tests', function() {
|
||||
function getLog(): MockAnimationPlayer[] {
|
||||
|
@ -17,7 +17,7 @@ import {TestBed} from '../../testing';
|
||||
(function() {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
// Buggy in Chromium 39, see https://github.com/angular/angular/issues/15793
|
||||
if (typeof Element == 'undefined') return;
|
||||
if (isNode) return;
|
||||
|
||||
describe('animation integration tests using css keyframe animations', function() {
|
||||
|
||||
|
@ -18,7 +18,7 @@ import {TestBed} from '../../testing';
|
||||
(function() {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
// Buggy in Chromium 39, see https://github.com/angular/angular/issues/15793
|
||||
if (typeof Element == 'undefined' || !ɵsupportsWebAnimations()) return;
|
||||
if (isNode || !ɵsupportsWebAnimations()) return;
|
||||
|
||||
describe('animation integration tests using web animations', function() {
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
* @experimental
|
||||
*/
|
||||
export type MetadataOverride<T> = {
|
||||
add?: T,
|
||||
remove?: T,
|
||||
set?: T
|
||||
add?: Partial<T>,
|
||||
remove?: Partial<T>,
|
||||
set?: Partial<T>
|
||||
};
|
||||
|
@ -44,7 +44,7 @@ function _find(control: AbstractControl, path: Array<string|number>| string, del
|
||||
|
||||
return (<Array<string|number>>path).reduce((v: AbstractControl, name) => {
|
||||
if (v instanceof FormGroup) {
|
||||
return v.controls[name] || null;
|
||||
return v.controls.hasOwnProperty(name as string) ? v.controls[name] : null;
|
||||
}
|
||||
|
||||
if (v instanceof FormArray) {
|
||||
@ -363,7 +363,7 @@ export abstract class AbstractControl {
|
||||
*
|
||||
* An event will be emitted by `statusChanges` by default.
|
||||
*
|
||||
* Passing `false` for `emitEvent` will cause `statusChanges` to not event an event.
|
||||
* Passing `false` for `emitEvent` will cause `statusChanges` to not emit an event.
|
||||
*/
|
||||
markAsPending(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
(this as{status: string}).status = PENDING;
|
||||
|
@ -629,6 +629,18 @@ import {of } from 'rxjs';
|
||||
});
|
||||
});
|
||||
|
||||
describe('retrieve', () => {
|
||||
let group: FormGroup;
|
||||
|
||||
beforeEach(() => {
|
||||
group = new FormGroup({
|
||||
'required': new FormControl('requiredValue'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get inherited properties',
|
||||
() => { expect(group.get('constructor')).toBe(null); });
|
||||
});
|
||||
|
||||
describe('statusChanges', () => {
|
||||
let control: FormControl;
|
||||
|
@ -13,7 +13,7 @@ import {Injectable} from '@angular/core';
|
||||
*
|
||||
* Take care not to evaluate this in non-browser contexts.
|
||||
*
|
||||
* @deprecated use @angular/common/http instead
|
||||
* @deprecated see https://angular.io/guide/http
|
||||
*/
|
||||
@Injectable()
|
||||
export class BrowserXhr {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user