Compare commits
81 Commits
5.0.0-rc.2
...
5.0.0-rc.7
Author | SHA1 | Date | |
---|---|---|---|
5542517b9c | |||
fef3539608 | |||
f4d5729cb3 | |||
d343bf7885 | |||
9ce7f0e538 | |||
4a23df3909 | |||
14016c781f | |||
47caebfe86 | |||
5cfd9c6020 | |||
47bc6f105d | |||
40fa2593a9 | |||
680bcf7b8a | |||
ef08330341 | |||
6cc042e2ba | |||
9b26455740 | |||
18bce5987c | |||
f1108fea76 | |||
64b3e3e41a | |||
a82f863e24 | |||
bde57016c6 | |||
b16f4bce98 | |||
6bed189e37 | |||
4abacb58f1 | |||
04200150d5 | |||
fc0b1d5b61 | |||
8d45fefc31 | |||
5da96c75a2 | |||
90d1423fb4 | |||
910735d732 | |||
fc86352adf | |||
441e01c568 | |||
14380ff086 | |||
820bb7bd8c | |||
230b98d4dd | |||
065ea926c0 | |||
9b9820858e | |||
b922743f6e | |||
04ab9f1917 | |||
25cbc98979 | |||
3861ba2929 | |||
3bcf0cf472 | |||
396c2417d9 | |||
fcfb1544e8 | |||
56774dfb79 | |||
c0cc6eeca1 | |||
6f2939da62 | |||
7d1abd9adb | |||
81173b0d29 | |||
b0c7ea8181 | |||
30ecb6e88a | |||
8d735da5d8 | |||
5a9ed2de27 | |||
41f57affb6 | |||
c569b75249 | |||
01e4aa5427 | |||
ad130d62d8 | |||
621f87b2bd | |||
64b36190de | |||
507290d30d | |||
15a8429b96 | |||
9723a362b6 | |||
d75a9fabdc | |||
9b264c5c78 | |||
60bdcd6f5f | |||
d035175cdb | |||
f42d317d2f | |||
405ccc7195 | |||
836c889baa | |||
43f9d917d9 | |||
62c7b7842b | |||
97969a85cd | |||
91fcfcb042 | |||
88c46feb20 | |||
c3f07b329f | |||
6121083ba5 | |||
717c68089d | |||
09b4244baf | |||
2e45267705 | |||
e81d1fc361 | |||
02394d2d80 | |||
653a211743 |
@ -29,7 +29,7 @@ jobs:
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: yarn install --freeze-lockfile --non-interactive
|
||||
- run: yarn install --frozen-lockfile --non-interactive
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
|
||||
build:
|
||||
@ -42,6 +42,7 @@ jobs:
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel build packages/...
|
||||
- run: bazel test @angular//...
|
||||
- save_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
|
@ -7,9 +7,12 @@
|
||||
#
|
||||
# alexeagle - Alex Eagle
|
||||
# alxhub - Alex Rickabaugh
|
||||
# brocco - Mike Brocchi
|
||||
# chuckjaz - Chuck Jazdzewski
|
||||
# filipesilva - Filipe Silva
|
||||
# Foxandxss - Jesús Rodríguez
|
||||
# gkalpak - George Kalpakas
|
||||
# hansl - Hans Larsen
|
||||
# IgorMinar - Igor Minar
|
||||
# jasonaden - Jason Aden
|
||||
# juleskremer - Jules Kremer
|
||||
@ -101,7 +104,6 @@ groups:
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
||||
|
||||
core:
|
||||
conditions:
|
||||
files:
|
||||
@ -116,7 +118,7 @@ groups:
|
||||
animations:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/animation/*"
|
||||
- "packages/animations/*"
|
||||
- "packages/platform-browser/animations/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
@ -145,11 +147,23 @@ groups:
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
|
||||
compiler-cli/ngtools:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/compiler-cli/src/ngtools*"
|
||||
users:
|
||||
- hansl
|
||||
- filipesilva #fallback
|
||||
- brocco #fallback
|
||||
|
||||
compiler-cli:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/compiler-cli/*"
|
||||
- "packages/bazel/*"
|
||||
include:
|
||||
- "packages/compiler-cli/*"
|
||||
- "packages/bazel/*"
|
||||
exclude:
|
||||
- "packages/compiler-cli/src/ngtools*"
|
||||
users:
|
||||
- alexeagle
|
||||
- chuckjaz
|
||||
@ -255,7 +269,15 @@ groups:
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
|
||||
service-worker:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/service-worker/*"
|
||||
users:
|
||||
- alxhub #primary
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
benchpress:
|
||||
conditions:
|
||||
|
@ -1,12 +1,10 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
# force trusty as Google Chrome addon is not supported on Precise
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '6.9.5'
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
# firefox: "38.0"
|
||||
apt:
|
||||
sources:
|
||||
@ -50,7 +48,8 @@ env:
|
||||
- CI_MODE=e2e_2
|
||||
- CI_MODE=js
|
||||
- CI_MODE=saucelabs_required
|
||||
- CI_MODE=browserstack_required
|
||||
# deactivated, see #19768
|
||||
# - CI_MODE=browserstack_required
|
||||
- CI_MODE=saucelabs_optional
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=aio_tools_test
|
||||
|
@ -12,6 +12,7 @@ filegroup(
|
||||
# This won't scale in the general case.
|
||||
# TODO(alexeagle): figure out what to do
|
||||
srcs = glob(["/".join(["node_modules", pkg, "**", ext]) for pkg in [
|
||||
"jasmine",
|
||||
"typescript",
|
||||
"zone.js",
|
||||
"rxjs",
|
||||
|
93
CHANGELOG.md
93
CHANGELOG.md
@ -1,3 +1,94 @@
|
||||
<a name="5.0.0-rc.7"></a>
|
||||
# [5.0.0-rc.7](https://github.com/angular/angular/compare/5.0.0-rc.6...5.0.0-rc.7) (2017-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** don’t store invalid state when using `listLazyRoutes` ([#19953](https://github.com/angular/angular/issues/19953)) ([4a23df3](https://github.com/angular/angular/commit/4a23df3))
|
||||
* **compiler:** make watch mode work on windows ([#19953](https://github.com/angular/angular/issues/19953)) ([f4d5729](https://github.com/angular/angular/commit/f4d5729)), closes [#19951](https://github.com/angular/angular/issues/19951)
|
||||
* **compiler:** recover from structural errors in watch mode ([#19953](https://github.com/angular/angular/issues/19953)) ([d343bf7](https://github.com/angular/angular/commit/d343bf7))
|
||||
* **compiler:** translate emit diagnostics with `noEmitOnError: true`. ([#19953](https://github.com/angular/angular/issues/19953)) ([9ce7f0e](https://github.com/angular/angular/commit/9ce7f0e)), closes [#19935](https://github.com/angular/angular/issues/19935)
|
||||
* **service-worker:** don't block initialization on registration ([#19936](https://github.com/angular/angular/issues/19936)) ([47caebf](https://github.com/angular/angular/commit/47caebf))
|
||||
* **service-worker:** fix improper call of Observable.merge ([#19962](https://github.com/angular/angular/issues/19962)) ([14016c7](https://github.com/angular/angular/commit/14016c7))
|
||||
* **service-worker:** listen for messages on the right event source ([#19954](https://github.com/angular/angular/issues/19954)) ([5cfd9c6](https://github.com/angular/angular/commit/5cfd9c6))
|
||||
|
||||
|
||||
|
||||
<a name="5.0.0-rc.6"></a>
|
||||
# [5.0.0-rc.6](https://github.com/angular/angular/compare/5.0.0-rc.5...5.0.0-rc.6) (2017-10-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** automatically set `emitDecoratorMetadata` when `"annotationsAs": "static fields”` ([#19927](https://github.com/angular/angular/issues/19927)) ([ef08330](https://github.com/angular/angular/commit/ef08330)), closes [#19916](https://github.com/angular/angular/issues/19916)
|
||||
* **compiler:** don’t type check templates with `skipTemplateCodegen` ([#19909](https://github.com/angular/angular/issues/19909)) ([18bce59](https://github.com/angular/angular/commit/18bce59))
|
||||
* **compiler-cli:** only use error collector when needed. ([#19912](https://github.com/angular/angular/issues/19912)) ([9b26455](https://github.com/angular/angular/commit/9b26455))
|
||||
* **compiler-cli:** produce correct paths for windows output ([#19915](https://github.com/angular/angular/issues/19915)) ([6cc042e](https://github.com/angular/angular/commit/6cc042e))
|
||||
|
||||
|
||||
|
||||
<a name="5.0.0-rc.5"></a>
|
||||
# [5.0.0-rc.5](https://github.com/angular/angular/compare/5.0.0-rc.4...5.0.0-rc.5) (2017-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** report all diagnostic error messages ([#19886](https://github.com/angular/angular/issues/19886)) ([a82f863](https://github.com/angular/angular/commit/a82f863))
|
||||
|
||||
|
||||
|
||||
<a name="5.0.0-rc.4"></a>
|
||||
# [5.0.0-rc.4](https://github.com/angular/angular/compare/5.0.0-rc.3...5.0.0-rc.4) (2017-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** don't console.error from the compile helper ([#19879](https://github.com/angular/angular/issues/19879)) ([5da96c7](https://github.com/angular/angular/commit/5da96c7))
|
||||
* **compiler:** correctly calculate the outDir if it repeats a parts of the `rootDir`. ([#19836](https://github.com/angular/angular/issues/19836)) ([fc0b1d5](https://github.com/angular/angular/commit/fc0b1d5)), closes [#19718](https://github.com/angular/angular/issues/19718)
|
||||
* **service-worker:** include versionedFiles in the manifest hashTable ([#19837](https://github.com/angular/angular/issues/19837)) ([90d1423](https://github.com/angular/angular/commit/90d1423))
|
||||
|
||||
|
||||
|
||||
<a name="5.0.0-rc.3"></a>
|
||||
# [5.0.0-rc.3](https://github.com/angular/angular/compare/5.0.0-rc.2...5.0.0-rc.3) (2017-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always fire inner trigger callbacks even if blocked by parent animations ([#19753](https://github.com/angular/angular/issues/19753)) ([5a9ed2d](https://github.com/angular/angular/commit/5a9ed2d)), closes [#19100](https://github.com/angular/angular/issues/19100)
|
||||
* **animations:** ensure animateChild() works with all inner leave animations ([#19006](https://github.com/angular/angular/issues/19006)) ([#19532](https://github.com/angular/angular/issues/19532)) ([#19693](https://github.com/angular/angular/issues/19693)) ([f42d317](https://github.com/angular/angular/commit/f42d317))
|
||||
* **animations:** ensure inner :leave animations do not remove node when skipped ([#19532](https://github.com/angular/angular/issues/19532)) ([#19693](https://github.com/angular/angular/issues/19693)) ([d035175](https://github.com/angular/angular/commit/d035175))
|
||||
* **bazel:** fix the output directory for extractor to be genfiles/ instead of bin/ ([#19716](https://github.com/angular/angular/issues/19716)) ([405ccc7](https://github.com/angular/angular/commit/405ccc7))
|
||||
* **common:** attempt to JSON.parse errors for JSON responses ([#19773](https://github.com/angular/angular/issues/19773)) ([04ab9f1](https://github.com/angular/angular/commit/04ab9f1))
|
||||
* **compiler:** generate correct imports for type check blocks ([#19582](https://github.com/angular/angular/issues/19582)) ([60bdcd6](https://github.com/angular/angular/commit/60bdcd6))
|
||||
* **compiler:** prepare for future Bazel semantics of += ([#19717](https://github.com/angular/angular/issues/19717)) ([836c889](https://github.com/angular/angular/commit/836c889))
|
||||
* **compiler-cli:** diagnostics file paths relative to cwd, not tsconfig ([#19748](https://github.com/angular/angular/issues/19748)) ([56774df](https://github.com/angular/angular/commit/56774df))
|
||||
* **compiler-cli:** do not add references to files outside of `rootDir` ([#19770](https://github.com/angular/angular/issues/19770)) ([25cbc98](https://github.com/angular/angular/commit/25cbc98))
|
||||
* **router:** RouterLinkActive should update its state right after checking the children ([#19449](https://github.com/angular/angular/issues/19449)) ([6f2939d](https://github.com/angular/angular/commit/6f2939d)), closes [#18983](https://github.com/angular/angular/issues/18983)
|
||||
* **service-worker:** add missing annotation for SwPush ([#19721](https://github.com/angular/angular/issues/19721)) ([15a8429](https://github.com/angular/angular/commit/15a8429))
|
||||
* **service-worker:** freshness strategy should clone response for cache ([#19764](https://github.com/angular/angular/issues/19764)) ([396c241](https://github.com/angular/angular/commit/396c241))
|
||||
* **service-worker:** PushEvent.data has to be decoded ([#19764](https://github.com/angular/angular/issues/19764)) ([3bcf0cf](https://github.com/angular/angular/commit/3bcf0cf))
|
||||
* **service-worker:** use posix path resolution for generation of ngsw.json ([#19527](https://github.com/angular/angular/issues/19527)) ([621f87b](https://github.com/angular/angular/commit/621f87b))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.6"></a>
|
||||
## [4.4.6](https://github.com/angular/angular/compare/4.4.5...4.4.6) (2017-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** properly support boolean-based transitions and state changes ([#19672](https://github.com/angular/angular/issues/19672)) ([f983a6c](https://github.com/angular/angular/commit/f983a6c)), closes [#9396](https://github.com/angular/angular/issues/9396) [#12337](https://github.com/angular/angular/issues/12337)
|
||||
* **common:** attempt to JSON.parse errors for JSON responses ([#19773](https://github.com/angular/angular/issues/19773)) ([269f5ac](https://github.com/angular/angular/commit/269f5ac))
|
||||
* **router:** RouterLinkActive should update its state right after checking the children ([53a807a](https://github.com/angular/angular/commit/53a807a)), closes [#18983](https://github.com/angular/angular/issues/18983)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **animations:** reduce size of bundle by removing AST classes ([#19673](https://github.com/angular/angular/issues/19673)) ([76d2496](https://github.com/angular/angular/commit/76d2496))
|
||||
|
||||
|
||||
|
||||
<a name="5.0.0-rc.2"></a>
|
||||
# [5.0.0-rc.2](https://github.com/angular/angular/compare/5.0.0-rc.1...5.0.0-rc.2) (2017-10-12)
|
||||
|
||||
@ -119,7 +210,7 @@
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **compiler:** The method `ngGetConentSelectors()`, deprecated in Angular 4.0, has been removed.
|
||||
* **compiler:** The method `ngGetContentSelectors()`, deprecated in Angular 4.0, has been removed.
|
||||
Use `ComponentFactory.ngContentSelectors` instead.
|
||||
|
||||
|
||||
|
@ -220,6 +220,7 @@ The following is the list of supported scopes:
|
||||
* **platform-webworker**
|
||||
* **platform-webworker-dynamic**
|
||||
* **router**
|
||||
* **service-worker**
|
||||
* **upgrade**
|
||||
|
||||
There are currently a few exceptions to the "use package name" rule:
|
||||
|
@ -5,7 +5,8 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
git_repository(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
remote = "https://github.com/bazelbuild/rules_nodejs.git",
|
||||
tag = "0.1.6",
|
||||
# TODO(alexeagle): use the correct tag here.
|
||||
commit = "2c6243df53fd33fdab283ebdd01582e4eb815db8",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
|
||||
@ -20,4 +21,4 @@ local_repository(
|
||||
local_repository(
|
||||
name = "angular",
|
||||
path = "packages/bazel",
|
||||
)
|
||||
)
|
||||
|
@ -156,7 +156,7 @@ RUN find $AIO_SCRIPTS_SH_DIR -maxdepth 1 -type f -printf "%P\n" \
|
||||
# Set up the Node.js scripts
|
||||
COPY scripts-js/ $AIO_SCRIPTS_JS_DIR/
|
||||
WORKDIR $AIO_SCRIPTS_JS_DIR/
|
||||
RUN yarn install --production --freeze-lockfile
|
||||
RUN yarn install --production --frozen-lockfile
|
||||
|
||||
|
||||
# Set up health check
|
||||
|
@ -9,7 +9,7 @@ readonly defaultImageNameAndTag="aio-builds:latest"
|
||||
# (Necessary, because only `scripts-js/dist/` is copied to the docker image.)
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install --freeze-lockfile --non-interactive
|
||||
yarn install --frozen-lockfile --non-interactive
|
||||
yarn build
|
||||
)
|
||||
|
||||
|
@ -7,6 +7,6 @@ source "`dirname $0`/_env.sh"
|
||||
# Test `scripts-js/`
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install --freeze-lockfile --non-interactive
|
||||
yarn install --frozen-lockfile --non-interactive
|
||||
yarn test
|
||||
)
|
||||
|
@ -12,8 +12,10 @@ describe('Component Style Tests', function () {
|
||||
let componentH1 = element(by.css('app-root > h1'));
|
||||
let externalH1 = element(by.css('body > h1'));
|
||||
|
||||
expect(componentH1.getCssValue('fontWeight')).toEqual('normal');
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toEqual('normal');
|
||||
// Note: sometimes webdriver returns the fontWeight as "normal",
|
||||
// othertimes as "400", both of which are equal in CSS terms.
|
||||
expect(componentH1.getCssValue('fontWeight')).toMatch(/normal|400/);
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toMatch(/normal|400/);
|
||||
});
|
||||
|
||||
|
||||
|
@ -7,3 +7,4 @@
|
||||
<p>We're sorry. The page you are looking for cannot be found.</p>
|
||||
</div>
|
||||
</div>
|
||||
<aio-file-not-found-search></aio-file-not-found-search>
|
||||
|
@ -4502,8 +4502,8 @@ helps instantly identify which members of the component serve which purpose.
|
||||
|
||||
**Why?** The property associated with `@HostBinding` or the method associated with `@HostListener`
|
||||
can be modified only in a single place—in the directive's class.
|
||||
If you use the `host` metadata property, you must modify both the property declaration inside the controller,
|
||||
and the metadata associated with the directive.
|
||||
If you use the `host` metadata property, you must modify both the property/method declaration in the
|
||||
directive's class and the metadata in the decorator associated with the directive.
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -13,18 +13,6 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- ngJapan -->
|
||||
<tr>
|
||||
<th><a href="http://ngjapan.org/" title="ng-Japan">ng-Japan</a></th>
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>June 17, 2017</td>
|
||||
</tr>
|
||||
<!-- AngularMix -->
|
||||
<tr>
|
||||
<th><a href="https://angularmix.com/" title="AngularMix">AngularMix</a></th>
|
||||
<td>Universal Studios, Orlando, Florida</td>
|
||||
<td>October 8, 2017</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
@ -35,7 +23,7 @@
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, United Kingdom</td>
|
||||
<td>November 07, 2017</td>
|
||||
<td>November 7-8, 2017</td>
|
||||
</tr>
|
||||
<!-- ngAtlanta-->
|
||||
<tr>
|
||||
@ -43,11 +31,30 @@
|
||||
<td>Atlanta, Georgia</td>
|
||||
<td>January 30, 2018</td>
|
||||
</tr>
|
||||
<!-- ngVikings-->
|
||||
<tr>
|
||||
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
|
||||
<td>Helsinki, Finland</td>
|
||||
<td>March 1-2, 2018</td>
|
||||
</tr>
|
||||
<!-- ngconf 2018-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, UT</td>
|
||||
<td>April 18-20, 2018</td>
|
||||
</tr>
|
||||
<!-- WeRDevs-->
|
||||
<tr>
|
||||
<th><a href="https://www.wearedevelopers.com/" title="WeAreDevs">WeAreDevelopers</a></th>
|
||||
<td>Vienna</td>
|
||||
<td>May 16-18, 2018</td>
|
||||
</tr>
|
||||
<!-- AngularConnect-->
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, United Kingdom</td>
|
||||
<td>November 5-7, 2018</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { browser, element, by, promise } from 'protractor';
|
||||
import { element, by } from 'protractor';
|
||||
import { SitePage } from './app.po';
|
||||
|
||||
describe('site App', function() {
|
||||
@ -41,20 +41,20 @@ describe('site App', function() {
|
||||
|
||||
describe('scrolling to the top', () => {
|
||||
it('should scroll to the top when navigating to another page', () => {
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
|
||||
page.navigateTo('guide/api');
|
||||
page.navigateTo('api');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
});
|
||||
|
||||
it('should scroll to the top when navigating to the same page', () => {
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
});
|
||||
});
|
||||
@ -66,30 +66,35 @@ describe('site App', function() {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(https://github.com/angular/angular/issues/19785): Activate this again
|
||||
// once it is no more flaky.
|
||||
describe('google analytics', () => {
|
||||
beforeEach(done => page.gaReady.then(done));
|
||||
|
||||
it('should call ga', done => {
|
||||
page.ga()
|
||||
.then(calls => {
|
||||
expect(calls.length).toBeGreaterThan(2, 'ga calls');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call ga with initial URL', done => {
|
||||
let path: string;
|
||||
|
||||
page.navigateTo('api');
|
||||
page.locationPath()
|
||||
.then(p => path = p)
|
||||
.then(() => page.ga().then(calls => {
|
||||
expect(calls.length).toBeGreaterThan(2, 'ga calls');
|
||||
expect(calls[1]).toEqual(['set', 'page', path]);
|
||||
// The last call (length-1) will be the `send` command
|
||||
// The second to last call (length-2) will be the command to `set` the page url
|
||||
expect(calls[calls.length - 2]).toEqual(['set', 'page', path]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
// Todo: add test to confirm tracking URL when navigate.
|
||||
it('should call ga with new URL on navigation', done => {
|
||||
let path: string;
|
||||
page.getLink('features').click();
|
||||
page.locationPath()
|
||||
.then(p => path = p)
|
||||
.then(() => page.ga().then(calls => {
|
||||
// The last call (length-1) will be the `send` command
|
||||
// The second to last call (length-2) will be the command to `set` the page url
|
||||
expect(calls[calls.length - 2]).toEqual(['set', 'page', path]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
@ -100,4 +105,12 @@ describe('site App', function() {
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('ControlValueAccessor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('404 page', () => {
|
||||
it('should search the index for words found in the url', () => {
|
||||
page.navigateTo('http/router');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('Http');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('Router');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -12,7 +12,6 @@ export class SitePage {
|
||||
.all(by.css('a'))
|
||||
.filter((a: ElementFinder) => a.getAttribute('href').then(href => githubRegex.test(href)))
|
||||
.first();
|
||||
gaReady: promise.Promise<any>;
|
||||
|
||||
static setWindowWidth(newWidth: number) {
|
||||
const win = browser.driver.manage().window();
|
||||
@ -25,11 +24,14 @@ export class SitePage {
|
||||
.first();
|
||||
}
|
||||
getLink(path) { return element(by.css(`a[href="${path}"]`)); }
|
||||
ga() { return browser.executeScript('return window["gaCalls"]') as promise.Promise<any[][]>; }
|
||||
ga() { return browser.executeScript('return window["ga"].q') as promise.Promise<any[][]>; }
|
||||
locationPath() { return browser.executeScript('return document.location.pathname') as promise.Promise<string>; }
|
||||
|
||||
navigateTo(pageUrl = '') {
|
||||
return browser.get('/' + pageUrl).then(_ => this.replaceGa(_));
|
||||
return browser.get('/' + pageUrl)
|
||||
// We need to tell the index.html not to load the real analytics library
|
||||
// See the GA snippet in index.html
|
||||
.then(() => browser.driver.executeScript('sessionStorage.setItem("__e2e__", true);'));
|
||||
}
|
||||
|
||||
getDocViewerText() {
|
||||
@ -61,31 +63,4 @@ export class SitePage {
|
||||
browser.wait(ExpectedConditions.presenceOf(results.first()), 8000);
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the ambient Google Analytics tracker with homebrew spy
|
||||
* don't send commands to GA during e2e testing!
|
||||
* @param _ - forward's anything passed in
|
||||
*/
|
||||
private replaceGa(_: any) {
|
||||
|
||||
this.gaReady = browser.driver.executeScript(() => {
|
||||
// Give ga() a "ready" callback:
|
||||
// https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference
|
||||
window['ga'](() => {
|
||||
window['gaCalls'] = [];
|
||||
window['ga'] = function() { window['gaCalls'].push(arguments); };
|
||||
});
|
||||
|
||||
})
|
||||
.then(() => {
|
||||
// wait for GaService to start using window.ga after analytics lib loads.
|
||||
const d = promise.defer();
|
||||
setTimeout(() => d.fulfill(), 1000); // GaService.initializeDelay
|
||||
return d.promise;
|
||||
});
|
||||
|
||||
return _;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,17 @@
|
||||
"source": "**/!(*.*)",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"source": "/",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Link",
|
||||
"value": "</generated/navigation.json>;rel=preload;as=fetch,</generated/docs/index.json>;rel=preload;as=fetch"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug --ignore-packages @angular/service-worker",
|
||||
"aio-use-npm": "node tools/ng-packages-installer restore .",
|
||||
"aio-use-npm": "node tools/ng-packages-installer restore . && yarn upgrade @angular/cli@1.3.0",
|
||||
"aio-check-local": "node tools/ng-packages-installer check .",
|
||||
"ng": "yarn check-env && ng",
|
||||
"start": "yarn check-env && ng serve",
|
||||
@ -19,11 +19,11 @@
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn ~~update-webdriver",
|
||||
"e2e": "ng e2e --no-webdriver-update",
|
||||
"presetup": "yarn install --freeze-lockfile && yarn ~~check-env && yarn boilerplate:remove",
|
||||
"presetup": "yarn install --frozen-lockfile && yarn ~~check-env && yarn boilerplate:remove",
|
||||
"setup": "yarn aio-use-npm && yarn example-use-npm",
|
||||
"postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn generate-plunkers && yarn generate-zips && yarn docs",
|
||||
"presetup-local": "yarn presetup",
|
||||
"setup-local": "yarn aio-use-local && yarn example-use-local",
|
||||
"setup-local": "yarn aio-use-local && yarn upgrade @angular/cli@1.5.0-rc.2 && yarn example-use-local",
|
||||
"postsetup-local": "yarn postsetup",
|
||||
"pretest-pwa-score-localhost": "yarn build",
|
||||
"test-pwa-score-localhost": "concurrently --kill-others --success first \"http-server dist -p 4200 --silent\" \"yarn test-pwa-score http://localhost:4200 90\"",
|
||||
@ -43,7 +43,7 @@
|
||||
"docs-watch": "node tools/transforms/authors-package/watchr.js",
|
||||
"docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms",
|
||||
"docs-test": "node tools/transforms/test.js",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer.spec.js",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js",
|
||||
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
|
||||
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
|
||||
"boilerplate:remove": "node ./tools/examples/example-boilerplate remove",
|
||||
|
@ -4,7 +4,7 @@ set -u -e -o pipefail
|
||||
|
||||
declare -A payloadLimits
|
||||
payloadLimits["aio", "uncompressed", "inline"]=1600
|
||||
payloadLimits["aio", "uncompressed", "main"]=525000
|
||||
payloadLimits["aio", "uncompressed", "main"]=525500
|
||||
payloadLimits["aio", "uncompressed", "polyfills"]=38000
|
||||
payloadLimits["aio", "gzip7", "inline"]=1000
|
||||
payloadLimits["aio", "gzip7", "main"]=127000
|
||||
|
@ -19,6 +19,11 @@ const config = require('lighthouse/lighthouse-core/config/default.js');
|
||||
// Constants
|
||||
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
|
||||
|
||||
// Specify the path to Chrome on Travis
|
||||
if (process.env.TRAVIS) {
|
||||
process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN;
|
||||
}
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
||||
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
|
||||
</md-toolbar>
|
||||
<aio-search-results #searchResults *ngIf="showSearchResults" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||
<aio-search-results #searchResultsView *ngIf="showSearchResults" [searchResults]="searchResults | async" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||
|
||||
<md-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { async, inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MdProgressBar, MdSidenav } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -22,9 +21,9 @@ import { MockSearchService } from 'testing/search.service';
|
||||
import { NavigationNode } from 'app/navigation/navigation.service';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { SelectComponent, Option } from 'app/shared/select/select.component';
|
||||
import { SelectComponent } from 'app/shared/select/select.component';
|
||||
import { TocComponent } from 'app/embedded/toc/toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
||||
@ -1054,11 +1053,6 @@ class TestGaService {
|
||||
locationChanged = jasmine.createSpy('locationChanged');
|
||||
}
|
||||
|
||||
class TestSearchService {
|
||||
initWorker = jasmine.createSpy('initWorker');
|
||||
loadIndex = jasmine.createSpy('loadIndex');
|
||||
}
|
||||
|
||||
class TestHttpClient {
|
||||
|
||||
static versionInfo = {
|
||||
|
@ -2,18 +2,18 @@ import { Component, ElementRef, HostBinding, HostListener, OnInit,
|
||||
QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { MdSidenav } from '@angular/material';
|
||||
|
||||
import { CurrentNodes, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { DocumentService, DocumentContents } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Deployment } from 'app/shared/deployment.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
|
||||
@ -89,10 +89,9 @@ export class AppComponent implements OnInit {
|
||||
|
||||
// Search related properties
|
||||
showSearchResults = false;
|
||||
@ViewChildren('searchBox, searchResults', { read: ElementRef })
|
||||
searchResults: Observable<SearchResults>;
|
||||
@ViewChildren('searchBox, searchResultsView', { read: ElementRef })
|
||||
searchElements: QueryList<ElementRef>;
|
||||
@ViewChild(SearchResultsComponent)
|
||||
searchResults: SearchResultsComponent;
|
||||
@ViewChild(SearchBoxComponent)
|
||||
searchBox: SearchBoxComponent;
|
||||
|
||||
@ -332,7 +331,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
doSearch(query) {
|
||||
this.searchService.search(query);
|
||||
this.searchResults = this.searchService.search(query);
|
||||
this.showSearchResults = !!query;
|
||||
}
|
||||
|
||||
|
@ -43,9 +43,9 @@ import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
||||
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { SearchResultsComponent } from './search/search-results/search-results.component';
|
||||
import { SearchBoxComponent } from './search/search-box/search-box.component';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
import { WindowToken, windowProvider } from 'app/shared/window';
|
||||
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
|
||||
@ -95,7 +95,6 @@ export const svgIconProviders = [
|
||||
ModeBannerComponent,
|
||||
NavMenuComponent,
|
||||
NavItemComponent,
|
||||
SearchResultsComponent,
|
||||
SearchBoxComponent,
|
||||
TopMenuComponent,
|
||||
],
|
||||
@ -115,7 +114,8 @@ export const svgIconProviders = [
|
||||
ScrollSpyService,
|
||||
SearchService,
|
||||
svgIconProviders,
|
||||
TocService
|
||||
TocService,
|
||||
{ provide: WindowToken, useFactory: windowProvider },
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { ApiListComponent } from './api-list.component';
|
||||
|
@ -13,7 +13,7 @@ import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { ApiItem, ApiSection, ApiService } from './api.service';
|
||||
import { ApiSection, ApiService } from './api.service';
|
||||
|
||||
import { Option } from 'app/shared/select/select.component';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { Injector } from '@angular/core';
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { Component, ElementRef, ViewChild, OnChanges, OnDestroy, Input } from '@angular/core';
|
||||
import { Component, ElementRef, ViewChild, OnChanges, Input } from '@angular/core';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
import { CopierService } from 'app/shared/copier.service';
|
||||
import { MdSnackBar } from '@angular/material';
|
||||
|
||||
const originalLabel = 'Copy Code';
|
||||
const copiedLabel = 'Copied!';
|
||||
const defaultLineNumsCount = 10; // by default, show linenums over this number
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@ import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ContributorService } from './contributor.service';
|
||||
import { Contributor, ContributorGroup } from './contributors.model';
|
||||
import { ContributorGroup } from './contributors.model';
|
||||
|
||||
describe('ContributorService', () => {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
import { CurrentLocationComponent } from './current-location.component';
|
||||
|
@ -20,6 +20,7 @@ import { CodeTabsComponent } from './code/code-tabs.component';
|
||||
import { ContributorListComponent } from './contributor/contributor-list.component';
|
||||
import { ContributorComponent } from './contributor/contributor.component';
|
||||
import { CurrentLocationComponent } from './current-location.component';
|
||||
import { FileNotFoundSearchComponent } from './search/file-not-found-search.component';
|
||||
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example/live-example.component';
|
||||
import { ResourceListComponent } from './resource/resource-list.component';
|
||||
import { ResourceService } from './resource/resource.service';
|
||||
@ -30,7 +31,8 @@ import { TocComponent } from './toc/toc.component';
|
||||
*/
|
||||
export const embeddedComponents: any[] = [
|
||||
ApiListComponent, CodeExampleComponent, CodeTabsComponent, ContributorListComponent,
|
||||
CurrentLocationComponent, LiveExampleComponent, ResourceListComponent, TocComponent
|
||||
CurrentLocationComponent, FileNotFoundSearchComponent, LiveExampleComponent, ResourceListComponent,
|
||||
TocComponent
|
||||
];
|
||||
|
||||
/** Injectable class w/ property returning components that can be embedded in docs */
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Component, DebugElement, ElementRef } from '@angular/core';
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example.component';
|
||||
@ -71,7 +71,6 @@ describe('LiveExampleComponent', () => {
|
||||
|
||||
describe('when not embedded', () => {
|
||||
function getLiveExampleAnchor() { return getAnchors()[0]; }
|
||||
function getDownloadAnchor() { return getAnchors()[1]; }
|
||||
|
||||
it('should create LiveExampleComponent', () => {
|
||||
testComponent(() => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
import { ResourceListComponent } from './resource-list.component';
|
||||
|
@ -3,7 +3,7 @@ import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ResourceService } from './resource.service';
|
||||
import { Category, SubCategory, Resource } from './resource.model';
|
||||
import { Category } from './resource.model';
|
||||
|
||||
describe('ResourceService', () => {
|
||||
|
||||
|
@ -0,0 +1,49 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { FileNotFoundSearchComponent } from './file-not-found-search.component';
|
||||
|
||||
|
||||
describe('FileNotFoundSearchComponent', () => {
|
||||
let element: HTMLElement;
|
||||
let fixture: ComponentFixture<FileNotFoundSearchComponent>;
|
||||
let searchService: SearchService;
|
||||
let searchResultSubject: Subject<SearchResults>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FileNotFoundSearchComponent, SearchResultsComponent ],
|
||||
providers: [
|
||||
{ provide: LocationService, useValue: new MockLocationService('base/initial-url?some-query') },
|
||||
SearchService
|
||||
]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(FileNotFoundSearchComponent);
|
||||
searchService = TestBed.get(SearchService);
|
||||
searchResultSubject = new Subject<SearchResults>();
|
||||
spyOn(searchService, 'search').and.callFake(() => searchResultSubject.asObservable());
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it('should run a search with a query built from the current url', () => {
|
||||
expect(searchService.search).toHaveBeenCalledWith('base initial url');
|
||||
});
|
||||
|
||||
it('should pass through any results to the `aio-search-results` component', () => {
|
||||
const searchResultsComponent = fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance;
|
||||
expect(searchResultsComponent.searchResults).toBe(null);
|
||||
|
||||
const results = { query: 'base initial url', results: []};
|
||||
searchResultSubject.next(results);
|
||||
fixture.detectChanges();
|
||||
expect(searchResultsComponent.searchResults).toEqual(results);
|
||||
});
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
|
||||
@Component({
|
||||
selector: 'aio-file-not-found-search',
|
||||
template:
|
||||
`<p>Let's see if any of these search results help...</p>
|
||||
<aio-search-results class="embedded" [searchResults]="searchResults | async"></aio-search-results>`
|
||||
})
|
||||
export class FileNotFoundSearchComponent implements OnInit {
|
||||
searchResults: Observable<SearchResults>;
|
||||
constructor(private location: LocationService, private search: SearchService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.searchResults = this.location.currentPath.switchMap(path => {
|
||||
const query = path.split(/\W+/).join(' ');
|
||||
return this.search.search(query);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { Component, CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By, DOCUMENT } from '@angular/platform-browser';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { asap } from 'rxjs/scheduler/asap';
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
Component, ComponentFactoryResolver, DebugElement,
|
||||
ElementRef, Injector, NgModule, OnInit, ViewChild } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Component, DebugElement, ElementRef, NgModule, OnInit, ViewChild } from '@angular/core';
|
||||
import { DocViewerComponent } from './doc-viewer.component';
|
||||
import { DocumentContents } from 'app/documents/document.service';
|
||||
import { EmbeddedModule, embeddedComponents, EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
import { EmbeddedModule, EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Component, ComponentFactory, ComponentFactoryResolver, ComponentRef,
|
||||
DoCheck, ElementRef, EventEmitter, Injector, Input, OnDestroy,
|
||||
Output, ViewEncapsulation
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
import { EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SimpleChange, SimpleChanges, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { NavItemComponent } from './nav-item.component';
|
||||
import { NavigationNode } from 'app/navigation/navigation.model';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { TopMenuComponent } from './top-menu.component';
|
||||
import { NavigationService, NavigationViews, NavigationNode } from 'app/navigation/navigation.service';
|
||||
import { NavigationService, NavigationViews } from 'app/navigation/navigation.service';
|
||||
|
||||
describe('TopMenuComponent', () => {
|
||||
let component: TopMenuComponent;
|
||||
|
@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { AsyncSubject } from 'rxjs/AsyncSubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/publishLast';
|
||||
|
19
aio/src/app/search/interfaces.ts
Normal file
19
aio/src/app/search/interfaces.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export interface SearchResults {
|
||||
query: string;
|
||||
results: SearchResult[];
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
path: string;
|
||||
title: string;
|
||||
type: string;
|
||||
titleWords: string;
|
||||
keywords: string;
|
||||
}
|
||||
|
||||
export interface SearchArea {
|
||||
name: string;
|
||||
pages: SearchResult[];
|
||||
priorityPages: SearchResult[];
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { Component } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SearchBoxComponent } from './search-box.component';
|
||||
import { MockSearchService } from 'testing/search.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ReflectiveInjector, NgZone } from '@angular/core';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/of';
|
||||
import { SearchService } from './search.service';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
@ -36,27 +37,29 @@ describe('SearchService', () => {
|
||||
|
||||
describe('search', () => {
|
||||
beforeEach(() => {
|
||||
// We must initialize the service before calling search
|
||||
service.initWorker('some/url', 100);
|
||||
// We must initialize the service before calling connectSearches
|
||||
service.initWorker('some/url', 1000);
|
||||
// Simulate the index being ready so that searches get sent to the worker
|
||||
(service as any).ready = Observable.of(true);
|
||||
});
|
||||
|
||||
it('should trigger a `loadIndex` synchronously', () => {
|
||||
service.search('some query');
|
||||
it('should trigger a `loadIndex` synchronously (not waiting for the delay)', () => {
|
||||
expect(mockWorker.sendMessage).not.toHaveBeenCalled();
|
||||
service.search('some query').subscribe();
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
|
||||
});
|
||||
|
||||
it('should send a "query-index" message to the worker', () => {
|
||||
service.search('some query');
|
||||
service.search('some query').subscribe();
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('query-index', 'some query');
|
||||
});
|
||||
|
||||
it('should push the response to the `searchResults` observable', () => {
|
||||
it('should push the response to the returned observable', () => {
|
||||
const mockSearchResults = { results: ['a', 'b'] };
|
||||
let actualSearchResults;
|
||||
(mockWorker.sendMessage as jasmine.Spy).and.returnValue(Observable.of(mockSearchResults));
|
||||
let searchResults: any;
|
||||
service.searchResults.subscribe(results => searchResults = results);
|
||||
service.search('some query');
|
||||
expect(searchResults).toEqual(mockSearchResults);
|
||||
service.search('some query').subscribe(results => actualSearchResults = results);
|
||||
expect(actualSearchResults).toEqual(mockSearchResults);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,34 +4,21 @@ Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
*/
|
||||
|
||||
import { NgZone, Injectable, Type } from '@angular/core';
|
||||
import { NgZone, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/observable/race';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import 'rxjs/add/operator/publishReplay';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
export interface SearchResults {
|
||||
query: string;
|
||||
results: SearchResult[];
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
path: string;
|
||||
title: string;
|
||||
type: string;
|
||||
titleWords: string;
|
||||
keywords: string;
|
||||
}
|
||||
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
private ready: Observable<boolean>;
|
||||
private searchesSubject = new ReplaySubject<string>(1);
|
||||
searchResults: Observable<SearchResults>;
|
||||
|
||||
private worker: WebWorkerClient;
|
||||
constructor(private zone: NgZone) {}
|
||||
|
||||
/**
|
||||
@ -43,36 +30,32 @@ export class SearchService {
|
||||
* @param initDelay the number of milliseconds to wait before we load the WebWorker and generate the search index
|
||||
*/
|
||||
initWorker(workerUrl: string, initDelay: number) {
|
||||
const searchResults = Observable
|
||||
const ready = this.ready = Observable
|
||||
// Wait for the initDelay or the first search
|
||||
.race<any>(
|
||||
Observable.timer(initDelay),
|
||||
this.searchesSubject.first()
|
||||
(this.searchesSubject as Observable<string>).first()
|
||||
)
|
||||
.concatMap(() => {
|
||||
// Create the worker and load the index
|
||||
const worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||
return worker.sendMessage('load-index').concatMap(() =>
|
||||
// Once the index has loaded, switch to listening to the searches coming in
|
||||
this.searchesSubject.switchMap((query) =>
|
||||
// Each search gets switched to a web worker message, whose results are returned via an observable
|
||||
worker.sendMessage<SearchResults>('query-index', query)
|
||||
)
|
||||
);
|
||||
}).publish();
|
||||
this.worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||
return this.worker.sendMessage<boolean>('load-index');
|
||||
}).publishReplay(1);
|
||||
|
||||
// Connect to the observable to kick off the timer
|
||||
searchResults.connect();
|
||||
|
||||
// Expose the connected observable to the rest of the world
|
||||
this.searchResults = searchResults;
|
||||
// Connect to the observable to kick off the timer
|
||||
ready.connect();
|
||||
return ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a search query to the index.
|
||||
* The results will appear on the `searchResults` observable.
|
||||
* Search the index using the given query and emit results on the observable that is returned.
|
||||
* @param query The query to run against the index.
|
||||
* @returns an observable collection of search results
|
||||
*/
|
||||
search(query: string) {
|
||||
search(query: string): Observable<SearchResults> {
|
||||
// Trigger the searches subject to override the init delay timer
|
||||
this.searchesSubject.next(query);
|
||||
// Once the index has loaded, switch to listening to the searches coming in.
|
||||
return this.ready.concatMap(() => this.worker.sendMessage<SearchResults>('query-index', query));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { MdIconRegistry } from '@angular/material';
|
||||
import { CustomMdIconRegistry, SVG_ICONS, SvgIconInfo } from './custom-md-icon-registry';
|
||||
import { CustomMdIconRegistry, SvgIconInfo } from './custom-md-icon-registry';
|
||||
|
||||
describe('CustomMdIconRegistry', () => {
|
||||
it('should get the SVG element for a preloaded icon from the cache', () => {
|
||||
|
@ -1,130 +1,87 @@
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
|
||||
import { GaService } from 'app/shared/ga.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { WindowToken } from 'app/shared/window';
|
||||
|
||||
describe('GaService', () => {
|
||||
let gaSpy: jasmine.Spy;
|
||||
let gaService: GaService;
|
||||
let injector: ReflectiveInjector;
|
||||
|
||||
// filter for 'send' which communicates with server
|
||||
// returns the url of the 'send pageview'
|
||||
function gaSpySendCalls() {
|
||||
let lastUrl: string;
|
||||
return gaSpy.calls.all()
|
||||
.reduce((acc, c) => {
|
||||
const args = c.args;
|
||||
if (args[0] === 'set') {
|
||||
lastUrl = args[2];
|
||||
} else if (args[0] === 'send') {
|
||||
acc.push(lastUrl);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
let gaSpy: jasmine.Spy;
|
||||
let mockWindow: any;
|
||||
|
||||
beforeEach(() => {
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
GaService,
|
||||
{ provide: Logger, useClass: TestLogger }
|
||||
]);
|
||||
gaSpy = jasmine.createSpy('ga');
|
||||
mockWindow = { ga: gaSpy };
|
||||
injector = ReflectiveInjector.resolveAndCreate([GaService, { provide: WindowToken, useFactory: () => mockWindow }]);
|
||||
gaService = injector.get(GaService);
|
||||
});
|
||||
|
||||
describe('with ambient GA', () => {
|
||||
let gaService: GaService;
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
this.winGa = window['ga']; // remember current GA tracker just in case
|
||||
|
||||
// Replace Google Analytics tracker with spy after calling "ga ready" callback
|
||||
window['ga'] = (fn: Function) => {
|
||||
window['ga'] = gaSpy = jasmine.createSpy('ga');
|
||||
fn();
|
||||
tick(GaService.initializeDelay); // see GaService#initializeGa
|
||||
};
|
||||
gaService = injector.get(GaService);
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
window['ga'] = this.winGa;
|
||||
});
|
||||
|
||||
it('should initialize ga with "create" when constructed', () => {
|
||||
const first = gaSpy.calls.first().args;
|
||||
expect(first[0]).toBe('create');
|
||||
});
|
||||
|
||||
describe('#locationChanged(url)', () => {
|
||||
it('should send page to url w/ leading slash', () => {
|
||||
gaService.locationChanged('testUrl');
|
||||
let args = gaSpy.calls.all()[1].args;
|
||||
expect(args).toEqual(['set', 'page', '/testUrl']);
|
||||
args = gaSpy.calls.all()[2].args;
|
||||
expect(args).toEqual(['send', 'pageview']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#sendPage(url)', () => {
|
||||
it('should set page to url w/ leading slash', () => {
|
||||
gaService.sendPage('testUrl');
|
||||
const args = gaSpy.calls.all()[1].args;
|
||||
expect(args).toEqual(['set', 'page', '/testUrl']);
|
||||
});
|
||||
|
||||
it('should send "pageview" ', () => {
|
||||
gaService.sendPage('testUrl');
|
||||
const args = gaSpy.calls.all()[2].args;
|
||||
expect(args).toEqual(['send', 'pageview']);
|
||||
});
|
||||
|
||||
it('should not send twice with same URL, back-to-back', () => {
|
||||
gaService.sendPage('testUrl');
|
||||
gaService.sendPage('testUrl');
|
||||
expect(gaSpySendCalls()).toEqual(['/testUrl']);
|
||||
});
|
||||
|
||||
it('should send twice with same URL, back-to-back, even when the hash changes', () => {
|
||||
// Therefore it is up to caller NOT to call it when hash changes if this is unwanted.
|
||||
// See LocationService and its specs
|
||||
gaService.sendPage('testUrl#one');
|
||||
gaService.sendPage('testUrl#two');
|
||||
expect(gaSpySendCalls()).toEqual([
|
||||
'/testUrl#one',
|
||||
'/testUrl#two'
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it('should send same URL twice when other intervening URL', () => {
|
||||
gaService.sendPage('testUrl');
|
||||
gaService.sendPage('testUrl2');
|
||||
gaService.sendPage('testUrl');
|
||||
expect(gaSpySendCalls()).toEqual([
|
||||
'/testUrl',
|
||||
'/testUrl2',
|
||||
'/testUrl'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should initialize ga with "create" when constructed', () => {
|
||||
const first = gaSpy.calls.first().args;
|
||||
expect(first[0]).toBe('create');
|
||||
});
|
||||
|
||||
describe('when no ambient GA', () => {
|
||||
let gaService: GaService;
|
||||
let logger: TestLogger;
|
||||
|
||||
it('should log with "create" when constructed', () => {
|
||||
gaService = injector.get(GaService);
|
||||
logger = injector.get(Logger);
|
||||
expect(logger.log.calls.count()).toBe(1, 'logger.log should be called');
|
||||
const first = logger.log.calls.first().args;
|
||||
expect(first[0]).toBe('ga:');
|
||||
expect(first[1][0]).toBe('create'); // first[1] is the array of args to ga()
|
||||
describe('#locationChanged(url)', () => {
|
||||
it('should send page to url w/ leading slash', () => {
|
||||
gaService.locationChanged('testUrl');
|
||||
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl');
|
||||
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#sendPage(url)', () => {
|
||||
it('should set page to url w/ leading slash', () => {
|
||||
gaService.sendPage('testUrl');
|
||||
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl');
|
||||
});
|
||||
|
||||
it('should send "pageview" ', () => {
|
||||
gaService.sendPage('testUrl');
|
||||
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
|
||||
});
|
||||
|
||||
it('should not send twice with same URL, back-to-back', () => {
|
||||
gaService.sendPage('testUrl');
|
||||
gaSpy.calls.reset();
|
||||
gaService.sendPage('testUrl');
|
||||
expect(gaSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should send again even if only the hash changes', () => {
|
||||
// Therefore it is up to caller NOT to call it when hash changes if this is unwanted.
|
||||
// See LocationService and its specs
|
||||
gaService.sendPage('testUrl#one');
|
||||
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl#one');
|
||||
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
|
||||
gaSpy.calls.reset();
|
||||
gaService.sendPage('testUrl#two');
|
||||
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl#two');
|
||||
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
|
||||
});
|
||||
|
||||
it('should send same URL twice when other intervening URL', () => {
|
||||
gaService.sendPage('testUrl');
|
||||
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl');
|
||||
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
|
||||
gaSpy.calls.reset();
|
||||
gaService.sendPage('testUrl2');
|
||||
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl2');
|
||||
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
|
||||
gaSpy.calls.reset();
|
||||
gaService.sendPage('testUrl');
|
||||
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl');
|
||||
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support replacing the `window.ga` function', () => {
|
||||
const gaSpy2 = jasmine.createSpy('new ga');
|
||||
mockWindow.ga = gaSpy2;
|
||||
gaSpy.calls.reset();
|
||||
|
||||
gaService.sendPage('testUrl');
|
||||
expect(gaSpy).not.toHaveBeenCalled();
|
||||
expect(gaSpy2).toHaveBeenCalledWith('set', 'page', '/testUrl');
|
||||
expect(gaSpy2).toHaveBeenCalledWith('send', 'pageview');
|
||||
});
|
||||
});
|
||||
|
||||
class TestLogger {
|
||||
log = jasmine.createSpy('log');
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { environment } from '../../environments/environment';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { WindowToken } from 'app/shared/window';
|
||||
|
||||
@Injectable()
|
||||
/**
|
||||
@ -10,15 +10,10 @@ import { Logger } from 'app/shared/logger.service';
|
||||
* Associates data with a GA "property" from the environment (`gaId`).
|
||||
*/
|
||||
export class GaService {
|
||||
// ms to wait before acquiring window.ga after analytics library loads
|
||||
// empirically determined to allow time for e2e test setup
|
||||
static initializeDelay = 1000;
|
||||
|
||||
private previousUrl: string;
|
||||
private ga: (...rest: any[]) => void;
|
||||
|
||||
constructor(private logger: Logger) {
|
||||
this.initializeGa();
|
||||
constructor(@Inject(WindowToken) private window: Window) {
|
||||
this.ga('create', environment['gaId'] , 'auto');
|
||||
}
|
||||
|
||||
@ -34,27 +29,7 @@ export class GaService {
|
||||
this.ga('send', 'pageview');
|
||||
}
|
||||
|
||||
// These gyrations are necessary to make the service e2e testable
|
||||
// and to disable ga tracking during e2e tests.
|
||||
private initializeGa() {
|
||||
const ga = window['ga'];
|
||||
if (ga) {
|
||||
// Queue commands until GA analytics script has loaded.
|
||||
const gaQueue: any[][] = [];
|
||||
this.ga = (...rest: any[]) => { gaQueue.push(rest); };
|
||||
|
||||
// Then send queued commands to either real or e2e test ga();
|
||||
// after waiting to allow possible e2e test to replace global ga function
|
||||
ga(() => setTimeout(() => {
|
||||
// this.logger.log('GA fn:', window['ga'].toString());
|
||||
this.ga = window['ga'];
|
||||
gaQueue.forEach((command) => this.ga.apply(null, command));
|
||||
}, GaService.initializeDelay));
|
||||
|
||||
} else {
|
||||
// delegate `ga` calls to the logger if no ga installed
|
||||
this.ga = (...rest: any[]) => { this.logger.log('ga:', rest); };
|
||||
}
|
||||
ga(...args) {
|
||||
this.window['ga'](...args);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Location, PlatformLocation } from '@angular/common';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { DOCUMENT } from '@angular/platform-browser';
|
||||
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollItem, ScrollSpiedElement, ScrollSpiedElementGroup, ScrollSpyInfo, ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { ScrollItem, ScrollSpiedElement, ScrollSpiedElementGroup, ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
|
||||
|
||||
describe('ScrollSpiedElement', () => {
|
||||
@ -197,6 +197,7 @@ describe('ScrollSpyService', () => {
|
||||
.and.callFake(() => actions.push('calibrate'));
|
||||
|
||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||
expect(calibrateSpy).not.toHaveBeenCalled();
|
||||
|
||||
scrollSpyService.spyOn([]);
|
||||
expect(actions).toEqual(['onResize', 'calibrate']);
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { SearchService, SearchResult, SearchResults } from '../search.service';
|
||||
import { SearchResultsComponent, SearchArea } from './search-results.component';
|
||||
import { MockSearchService } from 'testing/search.service';
|
||||
import { SearchResult } from 'app/search/interfaces';
|
||||
import { SearchResultsComponent } from './search-results.component';
|
||||
|
||||
describe('SearchResultsComponent', () => {
|
||||
let component: SearchResultsComponent;
|
||||
let fixture: ComponentFixture<SearchResultsComponent>;
|
||||
let searchResults: Subject<SearchResults>;
|
||||
|
||||
/** Get all text from component element */
|
||||
function getText() { return fixture.debugElement.nativeElement.textContent; }
|
||||
@ -38,27 +34,26 @@ describe('SearchResultsComponent', () => {
|
||||
return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1;
|
||||
}
|
||||
|
||||
function setSearchResults(query: string, results: SearchResult[]) {
|
||||
component.searchResults = {query, results};
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SearchResultsComponent ],
|
||||
providers: [
|
||||
{ provide: SearchService, useFactory: () => new MockSearchService() }
|
||||
]
|
||||
declarations: [ SearchResultsComponent ]
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SearchResultsComponent);
|
||||
component = fixture.componentInstance;
|
||||
searchResults = TestBed.get(SearchService).searchResults;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should map the search results into groups based on their containing folder', () => {
|
||||
const results = getTestResults(3);
|
||||
|
||||
searchResults.next({ query: '', results: results});
|
||||
setSearchResults('', getTestResults(3));
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'api', priorityPages: [
|
||||
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' }
|
||||
@ -71,10 +66,10 @@ describe('SearchResultsComponent', () => {
|
||||
});
|
||||
|
||||
it('should special case results that are top level folders', () => {
|
||||
searchResults.next({ query: '', results: [
|
||||
setSearchResults('', [
|
||||
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
|
||||
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
||||
]});
|
||||
]);
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'tutorial', priorityPages: [
|
||||
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
|
||||
@ -85,21 +80,21 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
it('should put first 5 results for each area into priorityPages', () => {
|
||||
const results = getTestResults();
|
||||
searchResults.next({ query: '', results: results });
|
||||
setSearchResults('', results);
|
||||
expect(component.searchAreas[0].priorityPages).toEqual(results.filter(p => p.path.startsWith('api')).slice(0, 5));
|
||||
expect(component.searchAreas[1].priorityPages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(0, 5));
|
||||
});
|
||||
|
||||
it('should put the nonPriorityPages into the pages array, sorted by title', () => {
|
||||
const results = getTestResults();
|
||||
searchResults.next({ query: '', results: results });
|
||||
setSearchResults('', results);
|
||||
expect(component.searchAreas[0].pages).toEqual([]);
|
||||
expect(component.searchAreas[1].pages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(5).sort(compareTitle));
|
||||
});
|
||||
|
||||
it('should put a total count in the header of each area of search results', () => {
|
||||
const results = getTestResults();
|
||||
searchResults.next({ query: '', results: results });
|
||||
setSearchResults('', results);
|
||||
fixture.detectChanges();
|
||||
const headers = fixture.debugElement.queryAll(By.css('h3'));
|
||||
expect(headers.length).toEqual(2);
|
||||
@ -112,7 +107,7 @@ describe('SearchResultsComponent', () => {
|
||||
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
||||
];
|
||||
|
||||
searchResults.next({ query: '', results: results });
|
||||
setSearchResults('', results);
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'other', priorityPages: [
|
||||
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
||||
@ -125,7 +120,7 @@ describe('SearchResultsComponent', () => {
|
||||
{ path: 'news', title: undefined, type: 'marketing', keywords: '', titleWords: '' }
|
||||
];
|
||||
|
||||
searchResults.next({ query: 'something', results: results });
|
||||
setSearchResults('something', results);
|
||||
expect(component.searchAreas).toEqual([]);
|
||||
});
|
||||
|
||||
@ -144,7 +139,7 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
selected = null;
|
||||
searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' };
|
||||
searchResults.next({ query: 'something', results: [searchResult] });
|
||||
setSearchResults('something', [searchResult]);
|
||||
|
||||
fixture.detectChanges();
|
||||
anchor = fixture.debugElement.query(By.css('a'));
|
||||
@ -179,10 +174,8 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
describe('when no query results', () => {
|
||||
it('should display "not found" message', () => {
|
||||
searchResults.next({ query: 'something', results: [] });
|
||||
fixture.detectChanges();
|
||||
setSearchResults('something', []);
|
||||
expect(getText()).toContain('No results');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,28 +1,20 @@
|
||||
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { SearchResult, SearchResults, SearchService } from '../search.service';
|
||||
|
||||
export interface SearchArea {
|
||||
name: string;
|
||||
pages: SearchResult[];
|
||||
priorityPages: SearchResult[];
|
||||
}
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { SearchResult, SearchResults, SearchArea } from 'app/search/interfaces';
|
||||
|
||||
/**
|
||||
* A component to display the search results
|
||||
* A component to display search results in groups
|
||||
*/
|
||||
@Component({
|
||||
selector: 'aio-search-results',
|
||||
templateUrl: './search-results.component.html',
|
||||
})
|
||||
export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
export class SearchResultsComponent implements OnChanges {
|
||||
|
||||
private resultsSubscription: Subscription;
|
||||
readonly defaultArea = 'other';
|
||||
notFoundMessage = 'Searching ...';
|
||||
readonly topLevelFolders = ['guide', 'tutorial'];
|
||||
/**
|
||||
* The results to display
|
||||
*/
|
||||
@Input()
|
||||
searchResults: SearchResults;
|
||||
|
||||
/**
|
||||
* Emitted when the user selects a search result
|
||||
@ -30,20 +22,13 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
resultSelected = new EventEmitter<SearchResult>();
|
||||
|
||||
/**
|
||||
* A mapping of the search results grouped into areas
|
||||
*/
|
||||
readonly defaultArea = 'other';
|
||||
notFoundMessage = 'Searching ...';
|
||||
readonly topLevelFolders = ['guide', 'tutorial'];
|
||||
searchAreas: SearchArea[] = [];
|
||||
|
||||
constructor(private searchService: SearchService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.resultsSubscription = this.searchService.searchResults
|
||||
.subscribe(search => this.searchAreas = this.processSearchResults(search));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.resultsSubscription.unsubscribe();
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.searchAreas = this.processSearchResults(this.searchResults);
|
||||
}
|
||||
|
||||
onResultSelected(page: SearchResult, event: MouseEvent) {
|
||||
@ -55,6 +40,9 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Map the search results into groups by area
|
||||
private processSearchResults(search: SearchResults) {
|
||||
if (!search) {
|
||||
return [];
|
||||
}
|
||||
this.notFoundMessage = 'No results found.';
|
||||
const searchAreaMap = {};
|
||||
search.results.forEach(result => {
|
||||
@ -84,6 +72,6 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
function compareResults(l: {title: string}, r: {title: string}) {
|
||||
function compareResults(l: SearchResult, r: SearchResult) {
|
||||
return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SelectComponent, Option } from './select.component';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SearchResultsComponent } from './search-results/search-results.component';
|
||||
import { SelectComponent } from './select/select.component';
|
||||
|
||||
@NgModule({
|
||||
@ -7,9 +8,11 @@ import { SelectComponent } from './select/select.component';
|
||||
CommonModule
|
||||
],
|
||||
exports: [
|
||||
SearchResultsComponent,
|
||||
SelectComponent
|
||||
],
|
||||
declarations: [
|
||||
SearchResultsComponent,
|
||||
SelectComponent
|
||||
]
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ReflectiveInjector, SecurityContext } from '@angular/core';
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { DOCUMENT, DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
@ -268,7 +268,6 @@ describe('TocService', () => {
|
||||
});
|
||||
|
||||
it('should calculate and set id of heading without an id', () => {
|
||||
const tocItem = lastTocList.find(item => item.title === 'H2 Two');
|
||||
const id = headings[2].getAttribute('id');
|
||||
expect(id).toEqual('h2-two');
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgZone, Injectable} from '@angular/core';
|
||||
import {NgZone} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
export interface WebWorkerMessage {
|
||||
|
4
aio/src/app/shared/window.ts
Normal file
4
aio/src/app/shared/window.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export const WindowToken = new InjectionToken('Window');
|
||||
export function windowProvider() { return window; }
|
@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable, OnDestroy } from '@angular/core';
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { NgServiceWorker } from '@angular/service-worker';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
@ -34,9 +34,13 @@
|
||||
|
||||
<!-- Google Analytics -->
|
||||
<script>
|
||||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
||||
// Note this is a customised version of the GA tracking snippet to aid e2e testing
|
||||
// See the bit between /**/.../**/
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;/**/i.sessionStorage.__e2e__||/**/m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
</script>
|
||||
<script async src='https://www.google-analytics.com/analytics.js'></script>
|
||||
<!-- End Google Analytics -->
|
||||
|
||||
<script>
|
||||
|
@ -29,6 +29,24 @@ aio-search-results {
|
||||
}
|
||||
}
|
||||
|
||||
aio-search-results.embedded .search-results {
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
width: auto;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
background-color: inherit;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
.search-area a {
|
||||
color: lighten($darkgray, 10);
|
||||
&:hover {
|
||||
color: $accentblue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { SearchResults } from 'app/search/search.service';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
|
||||
export class MockSearchService {
|
||||
searchResults = new Subject<SearchResults>();
|
||||
|
@ -68,7 +68,7 @@
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs@5.0.1',
|
||||
'tslib': 'npm:tslib/tslib.js',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
|
||||
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
|
||||
'typescript': 'npm:typescript@2.3.2/lib/typescript.js',
|
||||
|
||||
|
@ -54,7 +54,7 @@
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs@5.0.1',
|
||||
'tslib': 'npm:tslib/tslib.js',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
|
||||
'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js',
|
||||
'typescript': 'npm:typescript@2.3.2/lib/typescript.js',
|
||||
|
||||
|
@ -20,7 +20,11 @@ exports.config = {
|
||||
|
||||
// Capabilities to be passed to the webdriver instance.
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
'browserName': 'chrome',
|
||||
// For Travis
|
||||
chromeOptions: {
|
||||
binary: process.env.CHROME_BIN
|
||||
}
|
||||
},
|
||||
|
||||
// Framework to use. Jasmine is recommended.
|
||||
|
@ -1027,7 +1027,7 @@ chalk@^1.1.1, chalk@^1.1.3:
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0:
|
||||
chalk@^2.0.0, chalk@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
|
||||
dependencies:
|
||||
@ -2770,6 +2770,10 @@ hoek@2.x.x:
|
||||
version "2.16.3"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||
|
||||
hoek@4.x.x:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
|
||||
|
||||
homedir-polyfill@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
|
||||
|
@ -7,7 +7,6 @@ const shelljs = require('shelljs');
|
||||
const yargs = require('yargs');
|
||||
|
||||
const PACKAGE_JSON = 'package.json';
|
||||
const LOCKFILE = 'yarn.lock';
|
||||
const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
|
||||
const PACKAGE_JSON_REGEX = /^[^/]+\/package\.json$/;
|
||||
|
||||
@ -66,27 +65,61 @@ class NgPackagesInstaller {
|
||||
if (this._checkLocalMarker() !== true || this.force) {
|
||||
const pathToPackageConfig = path.resolve(this.projectDir, PACKAGE_JSON);
|
||||
const packages = this._getDistPackages();
|
||||
const packageConfigFile = fs.readFileSync(pathToPackageConfig);
|
||||
const packageConfig = JSON.parse(packageConfigFile);
|
||||
|
||||
const [dependencies, peers] = this._collectDependencies(packageConfig.dependencies || {}, packages);
|
||||
const [devDependencies, devPeers] = this._collectDependencies(packageConfig.devDependencies || {}, packages);
|
||||
|
||||
this._assignPeerDependencies(peers, dependencies, devDependencies);
|
||||
this._assignPeerDependencies(devPeers, dependencies, devDependencies);
|
||||
|
||||
const localPackageConfig = Object.assign(Object.create(null), packageConfig, { dependencies, devDependencies });
|
||||
localPackageConfig.__angular = { local: true };
|
||||
const localPackageConfigJson = JSON.stringify(localPackageConfig, null, 2);
|
||||
|
||||
try {
|
||||
this._log(`Writing temporary local ${PACKAGE_JSON} to ${pathToPackageConfig}`);
|
||||
fs.writeFileSync(pathToPackageConfig, localPackageConfigJson);
|
||||
this._installDeps('--no-lockfile', '--check-files');
|
||||
this._setLocalMarker(localPackageConfigJson);
|
||||
// Overwrite local Angular packages dependencies to other Angular packages with local files.
|
||||
Object.keys(packages).forEach(key => {
|
||||
const pkg = packages[key];
|
||||
const tmpConfig = JSON.parse(JSON.stringify(pkg.config));
|
||||
|
||||
// Prevent accidental publishing of the package, if something goes wrong.
|
||||
tmpConfig.private = true;
|
||||
|
||||
// Overwrite project dependencies/devDependencies to Angular packages with local files.
|
||||
['dependencies', 'devDependencies'].forEach(prop => {
|
||||
const deps = tmpConfig[prop] || {};
|
||||
Object.keys(deps).forEach(key2 => {
|
||||
const pkg2 = packages[key2];
|
||||
if (pkg2) {
|
||||
// point the core Angular packages at the distributable folder
|
||||
deps[key2] = `file:${pkg2.parentDir}/${key2.replace('@angular/', '')}`;
|
||||
this._log(`Overriding dependency of local ${key} with local package: ${key2}: ${deps[key2]}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(tmpConfig));
|
||||
});
|
||||
|
||||
const packageConfigFile = fs.readFileSync(pathToPackageConfig);
|
||||
const packageConfig = JSON.parse(packageConfigFile);
|
||||
|
||||
const [dependencies, peers] = this._collectDependencies(packageConfig.dependencies || {}, packages);
|
||||
const [devDependencies, devPeers] = this._collectDependencies(packageConfig.devDependencies || {}, packages);
|
||||
|
||||
this._assignPeerDependencies(peers, dependencies, devDependencies);
|
||||
this._assignPeerDependencies(devPeers, dependencies, devDependencies);
|
||||
|
||||
const localPackageConfig = Object.assign(Object.create(null), packageConfig, { dependencies, devDependencies });
|
||||
localPackageConfig.__angular = { local: true };
|
||||
const localPackageConfigJson = JSON.stringify(localPackageConfig, null, 2);
|
||||
|
||||
try {
|
||||
this._log(`Writing temporary local ${PACKAGE_JSON} to ${pathToPackageConfig}`);
|
||||
fs.writeFileSync(pathToPackageConfig, localPackageConfigJson);
|
||||
this._installDeps('--no-lockfile', '--check-files');
|
||||
this._setLocalMarker(localPackageConfigJson);
|
||||
} finally {
|
||||
this._log(`Restoring original ${PACKAGE_JSON} to ${pathToPackageConfig}`);
|
||||
fs.writeFileSync(pathToPackageConfig, packageConfigFile);
|
||||
}
|
||||
} finally {
|
||||
this._log(`Restoring original ${PACKAGE_JSON} to ${pathToPackageConfig}`);
|
||||
fs.writeFileSync(pathToPackageConfig, packageConfigFile);
|
||||
// Restore local Angular packages dependencies to other Angular packages.
|
||||
this._log(`Restoring original ${PACKAGE_JSON} for local Angular packages.`);
|
||||
Object.keys(packages).forEach(key => {
|
||||
const pkg = packages[key];
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(pkg.config));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,7 +129,7 @@ class NgPackagesInstaller {
|
||||
* Yarn will also delete the local marker file for us.
|
||||
*/
|
||||
restoreNpmDependencies() {
|
||||
this._installDeps('--freeze-lockfile', '--check-files');
|
||||
this._installDeps('--frozen-lockfile', '--check-files');
|
||||
}
|
||||
|
||||
// Protected helpers
|
||||
@ -122,13 +155,14 @@ class NgPackagesInstaller {
|
||||
const sourcePackage = packages[key];
|
||||
if (sourcePackage) {
|
||||
// point the core Angular packages at the distributable folder
|
||||
mergedDependencies[key] = `file:${ANGULAR_DIST_PACKAGES}/${key.replace('@angular/', '')}`;
|
||||
mergedDependencies[key] = `file:${sourcePackage.parentDir}/${key.replace('@angular/', '')}`;
|
||||
this._log(`Overriding dependency with local package: ${key}: ${mergedDependencies[key]}`);
|
||||
// grab peer dependencies
|
||||
Object.keys(sourcePackage.peerDependencies || {})
|
||||
const sourcePackagePeerDeps = sourcePackage.config.peerDependencies || {};
|
||||
Object.keys(sourcePackagePeerDeps)
|
||||
// ignore peerDependencies which are already core Angular packages
|
||||
.filter(key => !packages[key])
|
||||
.forEach(key => peerDependencies[key] = sourcePackage.peerDependencies[key]);
|
||||
.forEach(key => peerDependencies[key] = sourcePackagePeerDeps[key]);
|
||||
}
|
||||
});
|
||||
return [mergedDependencies, peerDependencies];
|
||||
@ -140,20 +174,29 @@ class NgPackagesInstaller {
|
||||
*/
|
||||
_getDistPackages() {
|
||||
const packageConfigs = Object.create(null);
|
||||
this._log(`Angular distributable directory: ${ANGULAR_DIST_PACKAGES}.`);
|
||||
shelljs
|
||||
.find(ANGULAR_DIST_PACKAGES)
|
||||
.map(filePath => filePath.slice(ANGULAR_DIST_PACKAGES.length + 1))
|
||||
.filter(filePath => PACKAGE_JSON_REGEX.test(filePath))
|
||||
.forEach(packagePath => {
|
||||
const packageName = `@angular/${packagePath.slice(0, -PACKAGE_JSON.length -1)}`;
|
||||
if (this.ignorePackages.indexOf(packageName) === -1) {
|
||||
const packageConfig = require(path.resolve(ANGULAR_DIST_PACKAGES, packagePath));
|
||||
packageConfigs[packageName] = packageConfig;
|
||||
} else {
|
||||
this._log('Ignoring package', packageName);
|
||||
}
|
||||
});
|
||||
|
||||
[ANGULAR_DIST_PACKAGES].forEach(distDir => {
|
||||
this._log(`Angular distributable directory: ${distDir}.`);
|
||||
shelljs
|
||||
.find(distDir)
|
||||
.map(filePath => filePath.slice(distDir.length + 1))
|
||||
.filter(filePath => PACKAGE_JSON_REGEX.test(filePath))
|
||||
.forEach(packagePath => {
|
||||
const packageName = `@angular/${packagePath.slice(0, -PACKAGE_JSON.length -1)}`;
|
||||
if (this.ignorePackages.indexOf(packageName) === -1) {
|
||||
const packageConfig = require(path.resolve(distDir, packagePath));
|
||||
packageConfigs[packageName] = {
|
||||
parentDir: distDir,
|
||||
packageJsonPath: path.resolve(distDir, packagePath),
|
||||
config: packageConfig
|
||||
};
|
||||
} else {
|
||||
this._log('Ignoring package', packageName);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
|
||||
return packageConfigs;
|
||||
}
|
||||
@ -244,4 +287,4 @@ function main() {
|
||||
module.exports = NgPackagesInstaller;
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ describe('NgPackagesInstaller', () => {
|
||||
const nodeModulesDir = path.resolve(absoluteRootDir, 'node_modules');
|
||||
const packageJsonPath = path.resolve(absoluteRootDir, 'package.json');
|
||||
const packagesDir = path.resolve(path.resolve(__dirname, '../../../dist/packages-dist'));
|
||||
const toolsDir = path.resolve(path.resolve(__dirname, '../../../dist/tools/@angular'));
|
||||
let installer;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -46,6 +47,7 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
|
||||
describe('installLocalDependencies()', () => {
|
||||
const copyJsonObj = obj => JSON.parse(JSON.stringify(obj));
|
||||
let dummyNgPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -53,12 +55,39 @@ describe('NgPackagesInstaller', () => {
|
||||
|
||||
// These are the packages that are "found" in the dist directory
|
||||
dummyNgPackages = {
|
||||
'@angular/core': { peerDependencies: { rxjs: '5.0.1' } },
|
||||
'@angular/common': { peerDependencies: { '@angular/core': '4.4.1' } },
|
||||
'@angular/compiler': { },
|
||||
'@angular/compiler-cli': { peerDependencies: { typescript: '^2.4.2', '@angular/compiler': '4.3.2' } }
|
||||
'@angular/core': {
|
||||
parentDir: packagesDir,
|
||||
packageJsonPath: `${packagesDir}/core/package.json`,
|
||||
config: { peerDependencies: { rxjs: '5.0.1' } }
|
||||
},
|
||||
'@angular/common': {
|
||||
parentDir: packagesDir,
|
||||
packageJsonPath: `${packagesDir}/common/package.json`,
|
||||
config: { peerDependencies: { '@angular/core': '4.4.4-1ab23cd4' } }
|
||||
},
|
||||
'@angular/compiler': {
|
||||
parentDir: packagesDir,
|
||||
packageJsonPath: `${packagesDir}/compiler/package.json`,
|
||||
config: { peerDependencies: { '@angular/common': '4.4.4-1ab23cd4' } }
|
||||
},
|
||||
'@angular/compiler-cli': {
|
||||
parentDir: toolsDir,
|
||||
packageJsonPath: `${toolsDir}/compiler-cli/package.json`,
|
||||
config: {
|
||||
dependencies: { '@angular/tsc-wrapped': '4.4.4-1ab23cd4' },
|
||||
peerDependencies: { typescript: '^2.4.2', '@angular/compiler': '4.4.4-1ab23cd4' }
|
||||
}
|
||||
},
|
||||
'@angular/tsc-wrapped': {
|
||||
parentDir: toolsDir,
|
||||
packageJsonPath: `${toolsDir}/tsc-wrapped/package.json`,
|
||||
config: {
|
||||
devDependencies: { '@angular/common': '4.4.4-1ab23cd4' },
|
||||
peerDependencies: { tsickle: '^1.4.0' }
|
||||
}
|
||||
}
|
||||
};
|
||||
spyOn(installer, '_getDistPackages').and.returnValue(dummyNgPackages);
|
||||
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyNgPackages));
|
||||
|
||||
// This is the package.json in the "test" folder
|
||||
dummyPackage = {
|
||||
@ -82,7 +111,7 @@ describe('NgPackagesInstaller', () => {
|
||||
'@angular/common': `file:${packagesDir}/common`
|
||||
},
|
||||
devDependencies: {
|
||||
'@angular/compiler-cli': `file:${packagesDir}/compiler-cli`,
|
||||
'@angular/compiler-cli': `file:${toolsDir}/compiler-cli`,
|
||||
rxjs: '5.0.1',
|
||||
typescript: '^2.4.2'
|
||||
},
|
||||
@ -117,6 +146,33 @@ describe('NgPackagesInstaller', () => {
|
||||
expect(installer._getDistPackages).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should temporarily overwrite the package.json files of local Angular packages', () => {
|
||||
const pkgJsonFor = pkgName => dummyNgPackages[`@angular/${pkgName}`].packageJsonPath;
|
||||
const pkgConfigFor = pkgName => copyJsonObj(dummyNgPackages[`@angular/${pkgName}`].config);
|
||||
const overwriteConfigFor = (pkgName, newProps) => Object.assign(pkgConfigFor(pkgName), newProps);
|
||||
|
||||
const allArgs = fs.writeFileSync.calls.allArgs();
|
||||
const firstFiveArgs = allArgs.slice(0, 5);
|
||||
const lastFiveArgs = allArgs.slice(-5);
|
||||
|
||||
expect(firstFiveArgs).toEqual([
|
||||
[pkgJsonFor('core'), JSON.stringify(overwriteConfigFor('core', {private: true}))],
|
||||
[pkgJsonFor('common'), JSON.stringify(overwriteConfigFor('common', {private: true}))],
|
||||
[pkgJsonFor('compiler'), JSON.stringify(overwriteConfigFor('compiler', {private: true}))],
|
||||
[pkgJsonFor('compiler-cli'), JSON.stringify(overwriteConfigFor('compiler-cli', {
|
||||
private: true,
|
||||
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` }
|
||||
}))],
|
||||
[pkgJsonFor('tsc-wrapped'), JSON.stringify(overwriteConfigFor('tsc-wrapped', {
|
||||
private: true,
|
||||
devDependencies: { '@angular/common': `file:${packagesDir}/common` }
|
||||
}))],
|
||||
]);
|
||||
|
||||
expect(lastFiveArgs).toEqual(['core', 'common', 'compiler', 'compiler-cli', 'tsc-wrapped']
|
||||
.map(pkgName => [pkgJsonFor(pkgName), JSON.stringify(pkgConfigFor(pkgName))]));
|
||||
});
|
||||
|
||||
it('should load the package.json', () => {
|
||||
expect(fs.readFileSync).toHaveBeenCalledWith(packageJsonPath);
|
||||
});
|
||||
@ -147,29 +203,42 @@ describe('NgPackagesInstaller', () => {
|
||||
it('should run `yarn install` in the specified directory, with the correct options', () => {
|
||||
spyOn(installer, '_installDeps');
|
||||
installer.restoreNpmDependencies();
|
||||
expect(installer._installDeps).toHaveBeenCalledWith('--freeze-lockfile', '--check-files');
|
||||
expect(installer._installDeps).toHaveBeenCalledWith('--frozen-lockfile', '--check-files');
|
||||
});
|
||||
});
|
||||
|
||||
describe('_getDistPackages', () => {
|
||||
describe('_getDistPackages()', () => {
|
||||
it('should include top level Angular packages', () => {
|
||||
const ngPackages = installer._getDistPackages();
|
||||
const expectedValue = jasmine.objectContaining({
|
||||
parentDir: jasmine.any(String),
|
||||
packageJsonPath: jasmine.any(String),
|
||||
config: jasmine.any(Object),
|
||||
});
|
||||
|
||||
// For example...
|
||||
expect(ngPackages['@angular/common']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/core']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/router']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/upgrade']).toEqual(expectedValue);
|
||||
|
||||
expect(ngPackages['@angular/upgrade/static']).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should store each package\'s parent directory', () => {
|
||||
const ngPackages = installer._getDistPackages();
|
||||
|
||||
// For example...
|
||||
expect(ngPackages['@angular/core'].parentDir).toBe(packagesDir);
|
||||
expect(ngPackages['@angular/router'].parentDir).toBeDefined(toolsDir);
|
||||
});
|
||||
|
||||
it('should not include packages that have been ignored', () => {
|
||||
installer = new NgPackagesInstaller(rootDir, { ignorePackages: ['@angular/router'] });
|
||||
const ngPackages = installer._getDistPackages();
|
||||
|
||||
expect(ngPackages['@angular/common']).toBeDefined();
|
||||
expect(ngPackages['@angular/core']).toBeDefined();
|
||||
expect(ngPackages['@angular/router']).toBeDefined();
|
||||
expect(ngPackages['@angular/upgrade']).toBeDefined();
|
||||
|
||||
expect(ngPackages['@angular/upgrade/static']).not.toBeDefined();
|
||||
|
||||
it('should not include packages that have been ignored', () => {
|
||||
installer = new NgPackagesInstaller(rootDir, { ignorePackages: ['@angular/router'] });
|
||||
const ngPackages = installer._getDistPackages();
|
||||
|
||||
expect(ngPackages['@angular/common']).toBeDefined();
|
||||
expect(ngPackages['@angular/router']).toBeUndefined();
|
||||
});
|
||||
expect(ngPackages['@angular/router']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@ -195,7 +264,7 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('_printWarning', () => {
|
||||
describe('_printWarning()', () => {
|
||||
it('should mention the message passed in the warning', () => {
|
||||
installer._printWarning();
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular build');
|
||||
@ -218,7 +287,7 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('_installDeps', () => {
|
||||
describe('_installDeps()', () => {
|
||||
it('should run yarn install with the given options', () => {
|
||||
installer._installDeps('option-1', 'option-2');
|
||||
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteRootDir });
|
||||
|
@ -63,6 +63,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
|
||||
'router/index.ts',
|
||||
'router/testing/index.ts',
|
||||
'router/upgrade/index.ts',
|
||||
'service-worker/index.ts',
|
||||
'upgrade/index.ts',
|
||||
'upgrade/static/index.ts',
|
||||
];
|
||||
|
@ -21,6 +21,7 @@ const packageMap = {
|
||||
'platform-webworker': ['platform-webworker/index.ts'],
|
||||
'platform-webworker-dynamic': 'platform-webworker-dynamic/index.ts',
|
||||
router: ['router/index.ts', 'router/testing/index.ts', 'router/upgrade/index.ts'],
|
||||
'service-worker': ['service-worker/index.ts'],
|
||||
upgrade: ['upgrade/index.ts', 'upgrade/static/index.ts']
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"noUnusedLocals": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
|
@ -6564,13 +6564,7 @@ rx@2.3.24:
|
||||
version "2.3.24"
|
||||
resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
|
||||
|
||||
rxjs@^5.2.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.0.tgz#a7db14ab157f9d7aac6a56e655e7a3860d39bf26"
|
||||
dependencies:
|
||||
symbol-observable "^1.0.1"
|
||||
|
||||
rxjs@^5.4.2:
|
||||
rxjs@^5.2.0, rxjs@^5.4.2:
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.3.tgz#0758cddee6033d68e0fd53676f0f3596ce3d483f"
|
||||
dependencies:
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "5.0.0-rc.2",
|
||||
"version": "5.0.0-rc.7",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
@ -24,7 +24,7 @@
|
||||
"dependencies": {
|
||||
"core-js": "^2.4.1",
|
||||
"reflect-metadata": "^0.1.3",
|
||||
"rxjs": "5.x",
|
||||
"rxjs": "^5.5.2",
|
||||
"tslib": "^1.7.1",
|
||||
"zone.js": "^0.8.12"
|
||||
},
|
||||
@ -90,6 +90,7 @@
|
||||
"rollup-plugin-sourcemaps": "0.4.2",
|
||||
"selenium-webdriver": "3.5.0",
|
||||
"semver": "5.4.1",
|
||||
"shelljs": "^0.7.8",
|
||||
"source-map": "0.5.7",
|
||||
"source-map-support": "0.4.18",
|
||||
"systemjs": "0.18.10",
|
||||
|
@ -68,7 +68,7 @@ export class StateValue {
|
||||
|
||||
get params(): {[key: string]: any} { return this.options.params as{[key: string]: any}; }
|
||||
|
||||
constructor(input: any) {
|
||||
constructor(input: any, public namespaceId: string = '') {
|
||||
const isObj = input && input.hasOwnProperty('value');
|
||||
const value = isObj ? input['value'] : input;
|
||||
this.value = normalizeTriggerValue(value);
|
||||
@ -141,7 +141,7 @@ export class AnimationTransitionNamespace {
|
||||
if (!triggersWithStates.hasOwnProperty(name)) {
|
||||
addClass(element, NG_TRIGGER_CLASSNAME);
|
||||
addClass(element, NG_TRIGGER_CLASSNAME + '-' + name);
|
||||
triggersWithStates[name] = null;
|
||||
triggersWithStates[name] = DEFAULT_STATE_VALUE;
|
||||
}
|
||||
|
||||
return () => {
|
||||
@ -192,7 +192,7 @@ export class AnimationTransitionNamespace {
|
||||
}
|
||||
|
||||
let fromState = triggersWithStates[triggerName];
|
||||
const toState = new StateValue(value);
|
||||
const toState = new StateValue(value, this.id);
|
||||
|
||||
const isObj = value && value.hasOwnProperty('value');
|
||||
if (!isObj && fromState) {
|
||||
@ -305,38 +305,31 @@ export class AnimationTransitionNamespace {
|
||||
}
|
||||
}
|
||||
|
||||
private _destroyInnerNodes(rootElement: any, context: any, animate: boolean = false) {
|
||||
private _signalRemovalForInnerTriggers(rootElement: any, context: any, animate: boolean = false) {
|
||||
// emulate a leave animation for all inner nodes within this node.
|
||||
// If there are no animations found for any of the nodes then clear the cache
|
||||
// for the element.
|
||||
this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true).forEach(elm => {
|
||||
if (animate && containsClass(elm, this._hostClassName)) {
|
||||
const innerNs = this._engine.namespacesByHostElement.get(elm);
|
||||
|
||||
// special case for a host element with animations on the same element
|
||||
if (innerNs) {
|
||||
innerNs.removeNode(elm, context, true);
|
||||
}
|
||||
|
||||
this.removeNode(elm, context, true);
|
||||
const namespaces = this._engine.fetchNamespacesByElement(elm);
|
||||
if (namespaces.size) {
|
||||
namespaces.forEach(ns => { ns.triggerLeaveAnimation(elm, context, false, true); });
|
||||
} else {
|
||||
this.clearElementCache(elm);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeNode(element: any, context: any, doNotRecurse?: boolean): void {
|
||||
const engine = this._engine;
|
||||
|
||||
if (!doNotRecurse && element.childElementCount) {
|
||||
this._destroyInnerNodes(element, context, true);
|
||||
}
|
||||
|
||||
const triggerStates = engine.statesByElement.get(element);
|
||||
triggerLeaveAnimation(
|
||||
element: any, context: any, destroyAfterComplete?: boolean,
|
||||
defaultToFallback?: boolean): boolean {
|
||||
const triggerStates = this._engine.statesByElement.get(element);
|
||||
if (triggerStates) {
|
||||
const players: TransitionAnimationPlayer[] = [];
|
||||
Object.keys(triggerStates).forEach(triggerName => {
|
||||
// this check is here in the event that an element is removed
|
||||
// twice (both on the host level and the component level)
|
||||
if (this._triggers[triggerName]) {
|
||||
const player = this.trigger(element, triggerName, VOID_VALUE, false);
|
||||
const player = this.trigger(element, triggerName, VOID_VALUE, defaultToFallback);
|
||||
if (player) {
|
||||
players.push(player);
|
||||
}
|
||||
@ -344,11 +337,55 @@ export class AnimationTransitionNamespace {
|
||||
});
|
||||
|
||||
if (players.length) {
|
||||
engine.markElementAsRemoved(this.id, element, true, context);
|
||||
optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element));
|
||||
return;
|
||||
this._engine.markElementAsRemoved(this.id, element, true, context);
|
||||
if (destroyAfterComplete) {
|
||||
optimizeGroupPlayer(players).onDone(() => this._engine.processLeaveNode(element));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareLeaveAnimationListeners(element: any) {
|
||||
const listeners = this._elementListeners.get(element);
|
||||
if (listeners) {
|
||||
const visitedTriggers = new Set<string>();
|
||||
listeners.forEach(listener => {
|
||||
const triggerName = listener.name;
|
||||
if (visitedTriggers.has(triggerName)) return;
|
||||
visitedTriggers.add(triggerName);
|
||||
|
||||
const trigger = this._triggers[triggerName];
|
||||
const transition = trigger.fallbackTransition;
|
||||
const elementStates = this._engine.statesByElement.get(element) !;
|
||||
const fromState = elementStates[triggerName] || DEFAULT_STATE_VALUE;
|
||||
const toState = new StateValue(VOID_VALUE);
|
||||
const player = new TransitionAnimationPlayer(this.id, triggerName, element);
|
||||
|
||||
this._engine.totalQueuedPlayers++;
|
||||
this._queue.push({
|
||||
element,
|
||||
triggerName,
|
||||
transition,
|
||||
fromState,
|
||||
toState,
|
||||
player,
|
||||
isFallbackTransition: true
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeNode(element: any, context: any): void {
|
||||
const engine = this._engine;
|
||||
|
||||
if (element.childElementCount) {
|
||||
this._signalRemovalForInnerTriggers(element, context, true);
|
||||
}
|
||||
|
||||
// this means that a * => VOID animation was detected and kicked off
|
||||
if (this.triggerLeaveAnimation(element, context, true)) return;
|
||||
|
||||
// find the player that is animating and make sure that the
|
||||
// removal is delayed until that player has completed
|
||||
@ -379,33 +416,7 @@ export class AnimationTransitionNamespace {
|
||||
// during flush or will be picked up by a parent query. Either way
|
||||
// we need to fire the listeners for this element when it DOES get
|
||||
// removed (once the query parent animation is done or after flush)
|
||||
const listeners = this._elementListeners.get(element);
|
||||
if (listeners) {
|
||||
const visitedTriggers = new Set<string>();
|
||||
listeners.forEach(listener => {
|
||||
const triggerName = listener.name;
|
||||
if (visitedTriggers.has(triggerName)) return;
|
||||
visitedTriggers.add(triggerName);
|
||||
|
||||
const trigger = this._triggers[triggerName];
|
||||
const transition = trigger.fallbackTransition;
|
||||
const elementStates = engine.statesByElement.get(element) !;
|
||||
const fromState = elementStates[triggerName] || DEFAULT_STATE_VALUE;
|
||||
const toState = new StateValue(VOID_VALUE);
|
||||
const player = new TransitionAnimationPlayer(this.id, triggerName, element);
|
||||
|
||||
this._engine.totalQueuedPlayers++;
|
||||
this._queue.push({
|
||||
element,
|
||||
triggerName,
|
||||
transition,
|
||||
fromState,
|
||||
toState,
|
||||
player,
|
||||
isFallbackTransition: true
|
||||
});
|
||||
});
|
||||
}
|
||||
this.prepareLeaveAnimationListeners(element);
|
||||
|
||||
// whether or not a parent has an animation we need to delay the deferral of the leave
|
||||
// operation until we have more information (which we do after flush() has been called)
|
||||
@ -468,7 +479,7 @@ export class AnimationTransitionNamespace {
|
||||
|
||||
destroy(context: any) {
|
||||
this.players.forEach(p => p.destroy());
|
||||
this._destroyInnerNodes(this.hostElement, context);
|
||||
this._signalRemovalForInnerTriggers(this.hostElement, context);
|
||||
}
|
||||
|
||||
elementContainsData(element: any): boolean {
|
||||
@ -603,6 +614,29 @@ export class TransitionAnimationEngine {
|
||||
|
||||
private _fetchNamespace(id: string) { return this._namespaceLookup[id]; }
|
||||
|
||||
fetchNamespacesByElement(element: any): Set<AnimationTransitionNamespace> {
|
||||
// normally there should only be one namespace per element, however
|
||||
// if @triggers are placed on both the component element and then
|
||||
// its host element (within the component code) then there will be
|
||||
// two namespaces returned. We use a set here to simply the dedupe
|
||||
// of namespaces incase there are multiple triggers both the elm and host
|
||||
const namespaces = new Set<AnimationTransitionNamespace>();
|
||||
const elementStates = this.statesByElement.get(element);
|
||||
if (elementStates) {
|
||||
const keys = Object.keys(elementStates);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const nsId = elementStates[keys[i]].namespaceId;
|
||||
if (nsId) {
|
||||
const ns = this._fetchNamespace(nsId);
|
||||
if (ns) {
|
||||
namespaces.add(ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
trigger(namespaceId: string, element: any, name: string, value: any): boolean {
|
||||
if (isElementNode(element)) {
|
||||
this._fetchNamespace(namespaceId).trigger(element, name, value);
|
||||
@ -648,7 +682,7 @@ export class TransitionAnimationEngine {
|
||||
}
|
||||
}
|
||||
|
||||
removeNode(namespaceId: string, element: any, context: any, doNotRecurse?: boolean): void {
|
||||
removeNode(namespaceId: string, element: any, context: any): void {
|
||||
if (!isElementNode(element)) {
|
||||
this._onRemovalComplete(element, context);
|
||||
return;
|
||||
@ -656,7 +690,7 @@ export class TransitionAnimationEngine {
|
||||
|
||||
const ns = namespaceId ? this._fetchNamespace(namespaceId) : null;
|
||||
if (ns) {
|
||||
ns.removeNode(element, context, doNotRecurse);
|
||||
ns.removeNode(element, context);
|
||||
} else {
|
||||
this.markElementAsRemoved(namespaceId, element, false, context);
|
||||
}
|
||||
@ -688,37 +722,39 @@ export class TransitionAnimationEngine {
|
||||
|
||||
destroyInnerAnimations(containerElement: any) {
|
||||
let elements = this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true);
|
||||
elements.forEach(element => {
|
||||
const players = this.playersByElement.get(element);
|
||||
if (players) {
|
||||
players.forEach(player => {
|
||||
// special case for when an element is set for destruction, but hasn't started.
|
||||
// in this situation we want to delay the destruction until the flush occurs
|
||||
// so that any event listeners attached to the player are triggered.
|
||||
if (player.queued) {
|
||||
player.markedForDestroy = true;
|
||||
} else {
|
||||
player.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
const stateMap = this.statesByElement.get(element);
|
||||
if (stateMap) {
|
||||
Object.keys(stateMap).forEach(triggerName => stateMap[triggerName] = DELETED_STATE_VALUE);
|
||||
}
|
||||
});
|
||||
elements.forEach(element => this.destroyActiveAnimationsForElement(element));
|
||||
|
||||
if (this.playersByQueriedElement.size == 0) return;
|
||||
|
||||
elements = this.driver.query(containerElement, NG_ANIMATING_SELECTOR, true);
|
||||
if (elements.length) {
|
||||
elements.forEach(element => {
|
||||
const players = this.playersByQueriedElement.get(element);
|
||||
if (players) {
|
||||
players.forEach(player => player.finish());
|
||||
elements.forEach(element => this.finishActiveQueriedAnimationOnElement(element));
|
||||
}
|
||||
|
||||
destroyActiveAnimationsForElement(element: any) {
|
||||
const players = this.playersByElement.get(element);
|
||||
if (players) {
|
||||
players.forEach(player => {
|
||||
// special case for when an element is set for destruction, but hasn't started.
|
||||
// in this situation we want to delay the destruction until the flush occurs
|
||||
// so that any event listeners attached to the player are triggered.
|
||||
if (player.queued) {
|
||||
player.markedForDestroy = true;
|
||||
} else {
|
||||
player.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
const stateMap = this.statesByElement.get(element);
|
||||
if (stateMap) {
|
||||
Object.keys(stateMap).forEach(triggerName => stateMap[triggerName] = DELETED_STATE_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
finishActiveQueriedAnimationOnElement(element: any) {
|
||||
const players = this.playersByQueriedElement.get(element);
|
||||
if (players) {
|
||||
players.forEach(player => player.finish());
|
||||
}
|
||||
}
|
||||
|
||||
whenRenderingDone(): Promise<any> {
|
||||
|
@ -853,7 +853,7 @@ export function transition(
|
||||
* var fadeAnimation = animation([
|
||||
* style({ opacity: '{{ start }}' }),
|
||||
* animate('{{ time }}',
|
||||
* style({ opacity: '{{ end }}'))
|
||||
* style({ opacity: '{{ end }}'}))
|
||||
* ], { params: { time: '1000ms', start: 0, end: 1 }});
|
||||
* ```
|
||||
*
|
||||
|
@ -6,7 +6,7 @@
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@angular/compiler-cli": "0.0.0-PLACEHOLDER",
|
||||
"typescript": "^2.4.2"
|
||||
"typescript": ">=2.4.2 <2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bazel/typescript": "0.2.x",
|
||||
|
@ -50,7 +50,7 @@ def _expected_outs(ctx, label):
|
||||
declaration_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in declarations]
|
||||
summary_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in summaries]
|
||||
|
||||
i18n_messages_files = [ctx.new_file(ctx.bin_dir, ctx.label.name + "_ngc_messages.xmb")]
|
||||
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
|
||||
|
||||
return struct(
|
||||
closure_js = closure_js_files,
|
||||
@ -115,7 +115,7 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, config_file_pa
|
||||
else:
|
||||
supports_workers = str(int(ctx.attr._supports_workers))
|
||||
|
||||
arguments = _EXTRA_NODE_OPTIONS_FLAGS
|
||||
arguments = list(_EXTRA_NODE_OPTIONS_FLAGS)
|
||||
# One at-sign makes this a params-file, enabling the worker strategy.
|
||||
# Two at-signs escapes the argument so it's passed through to ngc
|
||||
# rather than the contents getting expanded.
|
||||
@ -144,7 +144,10 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, config_file_pa
|
||||
executable = ctx.executable._ng_xi18n,
|
||||
arguments = (_EXTRA_NODE_OPTIONS_FLAGS +
|
||||
[config_file_path] +
|
||||
[messages_out[0].short_path]),
|
||||
# The base path is bin_dir because of the way the ngc
|
||||
# compiler host is configured. So we need to explictily
|
||||
# point to genfiles/ to redirect the output.
|
||||
["../genfiles/" + messages_out[0].short_path]),
|
||||
progress_message = "Extracting Angular 2 messages (ng_xi18n)",
|
||||
mnemonic = "Angular2MessageExtractor")
|
||||
|
||||
@ -247,4 +250,4 @@ ng_module = rule(
|
||||
),
|
||||
},
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
)
|
||||
|
@ -1,15 +1,13 @@
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
ts_library(
|
||||
name = "ngc_lib",
|
||||
srcs = [
|
||||
"index.ts",
|
||||
"emit_cache.ts",
|
||||
"extract_i18n.ts",
|
||||
],
|
||||
module_name = "@angular/bazel",
|
||||
deps = [
|
||||
# BEGIN-INTERNAL
|
||||
# Only needed when compiling within the Angular repo.
|
||||
@ -19,6 +17,7 @@ ts_library(
|
||||
"@build_bazel_rules_typescript//internal/tsc_wrapped"
|
||||
],
|
||||
tsconfig = ":tsconfig.json",
|
||||
visibility = ["//test/ngc-wrapped:__subpackages__"],
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
|
@ -1,117 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ng from '@angular/compiler-cli';
|
||||
import {CompilerHost, debug, fixUmdModuleDeclarations} from '@bazel/typescript';
|
||||
import * as tsickle from 'tsickle';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
interface EmitCacheEntry {
|
||||
emitResult: tsickle.EmitResult;
|
||||
writtenFiles: Array<{fileName: string, content: string, sourceFiles?: ts.SourceFile[]}>;
|
||||
generatedFile: ng.GeneratedFile;
|
||||
}
|
||||
|
||||
interface SourceFileWithEmitCache extends ts.SourceFile {
|
||||
emitCache: Map<string, EmitCacheEntry>;
|
||||
}
|
||||
|
||||
function getCache(sf: ts.SourceFile, genFileName?: string): EmitCacheEntry|undefined {
|
||||
const emitCache = (sf as SourceFileWithEmitCache).emitCache;
|
||||
return emitCache ? emitCache.get(genFileName || sf.fileName) : undefined;
|
||||
}
|
||||
|
||||
function setCache(sf: ts.SourceFile, entry: EmitCacheEntry) {
|
||||
let emitCache = (sf as SourceFileWithEmitCache).emitCache;
|
||||
if (!emitCache) {
|
||||
emitCache = new Map();
|
||||
(sf as SourceFileWithEmitCache).emitCache = emitCache;
|
||||
}
|
||||
emitCache.set(entry.generatedFile ? entry.generatedFile.genFileName : sf.fileName, entry);
|
||||
}
|
||||
|
||||
export function getCachedGeneratedFile(sf: ts.SourceFile, genFileName: string): ng.GeneratedFile|
|
||||
undefined {
|
||||
const cacheEntry = getCache(sf, genFileName);
|
||||
return cacheEntry ? cacheEntry.generatedFile : undefined;
|
||||
}
|
||||
|
||||
export function emitWithCache(
|
||||
program: ng.Program, inputsChanged: boolean, targetFileNames: string[],
|
||||
compilerOpts: ng.CompilerOptions, host: CompilerHost): tsickle.EmitResult {
|
||||
const emitCallback: ng.EmitCallback = ({
|
||||
targetSourceFiles,
|
||||
writeFile,
|
||||
cancellationToken,
|
||||
emitOnlyDtsFiles,
|
||||
customTransformers = {}
|
||||
}) => {
|
||||
if (!targetSourceFiles) {
|
||||
// Note: we know that we always have targetSourceFiles
|
||||
// as we called `ng.Program.emit` with `targetFileNames`.
|
||||
throw new Error('Unexpected state: no targetSourceFiles!');
|
||||
}
|
||||
let cacheHits = 0;
|
||||
const mergedEmitResult = tsickle.mergeEmitResults(targetSourceFiles.map(targetSourceFile => {
|
||||
const targetGeneratedFile = program.getGeneratedFile(targetSourceFile.fileName);
|
||||
const cacheSf = targetGeneratedFile ?
|
||||
program.getTsProgram().getSourceFile(targetGeneratedFile.srcFileName) :
|
||||
targetSourceFile;
|
||||
const cacheEntry = getCache(cacheSf, targetGeneratedFile && targetGeneratedFile.genFileName);
|
||||
if (cacheEntry) {
|
||||
let useEmitCache = false;
|
||||
if (targetGeneratedFile && !program.hasChanged(targetSourceFile.fileName)) {
|
||||
// we emitted a GeneratedFile with the same content as before -> use the cache
|
||||
useEmitCache = true;
|
||||
} else if (!inputsChanged && !targetGeneratedFile) {
|
||||
// this is an input and no inputs have changed -> use the cache
|
||||
useEmitCache = true;
|
||||
}
|
||||
if (useEmitCache) {
|
||||
cacheHits++;
|
||||
cacheEntry.writtenFiles.forEach(
|
||||
({fileName, content, sourceFiles}) => writeFile(
|
||||
fileName, content, /*writeByteOrderMark*/ false, /*onError*/ undefined,
|
||||
sourceFiles));
|
||||
return cacheEntry.emitResult;
|
||||
}
|
||||
}
|
||||
const writtenFiles:
|
||||
Array<{fileName: string, content: string, sourceFiles?: ts.SourceFile[]}> = [];
|
||||
const recordingWriteFile =
|
||||
(fileName: string, content: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||
writtenFiles.push({fileName, content, sourceFiles});
|
||||
writeFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
|
||||
};
|
||||
const emitResult = tsickle.emitWithTsickle(
|
||||
program.getTsProgram(), host, host, compilerOpts, targetSourceFile, recordingWriteFile,
|
||||
cancellationToken, emitOnlyDtsFiles, {
|
||||
beforeTs: customTransformers.before,
|
||||
afterTs: [
|
||||
...(customTransformers.after || []),
|
||||
fixUmdModuleDeclarations((sf: ts.SourceFile) => host.amdModuleName(sf)),
|
||||
],
|
||||
});
|
||||
setCache(cacheSf, {
|
||||
emitResult,
|
||||
writtenFiles,
|
||||
generatedFile: targetGeneratedFile,
|
||||
});
|
||||
return emitResult;
|
||||
}));
|
||||
debug(`Emitted ${targetSourceFiles.length} files with ${cacheHits} cache hits`);
|
||||
return mergedEmitResult;
|
||||
};
|
||||
return program
|
||||
.emit({
|
||||
targetFileNames,
|
||||
emitCallback,
|
||||
emitFlags: ng.EmitFlags.DTS | ng.EmitFlags.JS | ng.EmitFlags.Codegen
|
||||
}) as tsickle.EmitResult;
|
||||
}
|
@ -11,6 +11,6 @@
|
||||
// Entry point
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
console.error('>>> not yet implemented!');
|
||||
console.error('>>> now yet implemented!');
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
@ -8,14 +8,12 @@
|
||||
// TODO(tbosch): figure out why we need this as it breaks node code within ngc-wrapped
|
||||
/// <reference types="node" />
|
||||
import * as ng from '@angular/compiler-cli';
|
||||
import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, parseTsconfig, runAsWorker, runWorkerLoop} from '@bazel/typescript';
|
||||
import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, fixUmdModuleDeclarations, parseTsconfig, runAsWorker, runWorkerLoop} from '@bazel/typescript';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as tsickle from 'tsickle';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {emitWithCache, getCachedGeneratedFile} from './emit_cache';
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const NGC_GEN_FILES = /^(.*?)\.(ngfactory|ngsummary|ngstyle|shim\.ngstyle)(.*)$/;
|
||||
// FIXME: we should be able to add the assets to the tsconfig so FileLoader
|
||||
@ -42,7 +40,7 @@ export function main(args) {
|
||||
/** The one FileCache instance used in this process. */
|
||||
const fileCache = new FileCache<ts.SourceFile>(debug);
|
||||
|
||||
function runOneBuild(args: string[], inputs?: {[path: string]: string}): boolean {
|
||||
export function runOneBuild(args: string[], inputs?: {[path: string]: string}): boolean {
|
||||
if (args[0] === '-p') args.shift();
|
||||
// Strip leading at-signs, used to indicate a params file
|
||||
const project = args[0].replace(/^@+/, '');
|
||||
@ -62,6 +60,9 @@ function runOneBuild(args: string[], inputs?: {[path: string]: string}): boolean
|
||||
inputs,
|
||||
expectedOuts
|
||||
});
|
||||
if (diagnostics.length) {
|
||||
console.error(ng.formatDiagnostics(diagnostics));
|
||||
}
|
||||
return diagnostics.every(d => d.category !== ts.DiagnosticCategory.Error);
|
||||
}
|
||||
|
||||
@ -76,32 +77,23 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
|
||||
}
|
||||
|
||||
export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, compilerOpts,
|
||||
tsHost, bazelOpts, files, inputs, expectedOuts,
|
||||
gatherDiagnostics = defaultGatherDiagnostics}: {
|
||||
tsHost, bazelOpts, files, inputs, expectedOuts, gatherDiagnostics}: {
|
||||
allowNonHermeticReads: boolean,
|
||||
allDepsCompiledWithBazel?: boolean,
|
||||
compilerOpts: ng.CompilerOptions,
|
||||
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
|
||||
bazelOpts: BazelOptions,
|
||||
files: string[],
|
||||
expectedOuts: string[],
|
||||
gatherDiagnostics?: (program: ng.Program, inputsToCheck: ts.SourceFile[],
|
||||
genFilesToCheck: ng.GeneratedFile[]) => ng.Diagnostics
|
||||
expectedOuts: string[], gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics
|
||||
}): {diagnostics: ng.Diagnostics, program: ng.Program} {
|
||||
let fileLoader: FileLoader;
|
||||
const oldFiles = new Map<string, ts.SourceFile>();
|
||||
|
||||
if (inputs) {
|
||||
fileLoader = new CachedFileLoader(fileCache, allowNonHermeticReads);
|
||||
// Resolve the inputs to absolute paths to match TypeScript internals
|
||||
const resolvedInputs: {[path: string]: string} = {};
|
||||
for (const key of Object.keys(inputs)) {
|
||||
const resolvedKey = path.resolve(key);
|
||||
resolvedInputs[resolvedKey] = inputs[key];
|
||||
const cachedSf = fileCache.getCache(resolvedKey);
|
||||
if (cachedSf) {
|
||||
oldFiles.set(resolvedKey, cachedSf);
|
||||
}
|
||||
resolvedInputs[path.resolve(key)] = inputs[key];
|
||||
}
|
||||
fileCache.updateCache(resolvedInputs);
|
||||
} else {
|
||||
@ -189,56 +181,38 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
path.resolve(bazelBin, fileName) + '.d.ts';
|
||||
}
|
||||
|
||||
const oldProgram = {
|
||||
getSourceFile: (fileName: string) => { return oldFiles.get(fileName); },
|
||||
getGeneratedFile: (srcFileName: string, genFileName: string) => {
|
||||
const sf = oldFiles.get(srcFileName);
|
||||
return sf ? getCachedGeneratedFile(sf, genFileName) : undefined;
|
||||
},
|
||||
};
|
||||
const program =
|
||||
ng.createProgram({rootNames: files, host: ngHost, options: compilerOpts, oldProgram});
|
||||
let inputsChanged = files.some(fileName => program.hasChanged(fileName));
|
||||
const emitCallback: ng.TsEmitCallback = ({
|
||||
program,
|
||||
targetSourceFile,
|
||||
writeFile,
|
||||
cancellationToken,
|
||||
emitOnlyDtsFiles,
|
||||
customTransformers = {},
|
||||
}) =>
|
||||
tsickle.emitWithTsickle(
|
||||
program, bazelHost, bazelHost, compilerOpts, targetSourceFile, writeFile,
|
||||
cancellationToken, emitOnlyDtsFiles, {
|
||||
beforeTs: customTransformers.before,
|
||||
afterTs: [
|
||||
...(customTransformers.after || []),
|
||||
fixUmdModuleDeclarations((sf: ts.SourceFile) => bazelHost.amdModuleName(sf)),
|
||||
],
|
||||
});
|
||||
|
||||
let genFilesToCheck: ng.GeneratedFile[];
|
||||
let inputsToCheck: ts.SourceFile[];
|
||||
if (inputsChanged) {
|
||||
// if an input file changed, we need to type check all
|
||||
// of our compilation sources as well as all generated files.
|
||||
inputsToCheck = bazelOpts.compilationTargetSrc.map(
|
||||
fileName => program.getTsProgram().getSourceFile(fileName));
|
||||
genFilesToCheck = program.getGeneratedFiles().filter(gf => gf.genFileName.endsWith('.ts'));
|
||||
} else {
|
||||
// if no input file changed, only type check the changed generated files
|
||||
// as these don't influence each other nor the type check of the input files.
|
||||
inputsToCheck = [];
|
||||
genFilesToCheck = program.getGeneratedFiles().filter(
|
||||
gf => program.hasChanged(gf.genFileName) && gf.genFileName.endsWith('.ts'));
|
||||
}
|
||||
|
||||
debug(
|
||||
`TypeChecking ${inputsToCheck ? inputsToCheck.length : 'all'} inputs and ${genFilesToCheck ? genFilesToCheck.length : 'all'} generated files`);
|
||||
const diagnostics = [...gatherDiagnostics(program !, inputsToCheck, genFilesToCheck)];
|
||||
let emitResult: tsickle.EmitResult|undefined;
|
||||
if (!diagnostics.length) {
|
||||
const targetFileNames = [...bazelOpts.compilationTargetSrc];
|
||||
for (const genFile of program.getGeneratedFiles()) {
|
||||
if (genFile.genFileName.endsWith('.ts')) {
|
||||
targetFileNames.push(genFile.genFileName);
|
||||
}
|
||||
}
|
||||
emitResult = emitWithCache(program, inputsChanged, targetFileNames, compilerOpts, bazelHost);
|
||||
diagnostics.push(...emitResult.diagnostics);
|
||||
if (!gatherDiagnostics) {
|
||||
gatherDiagnostics = (program) =>
|
||||
gatherDiagnosticsForInputsOnly(compilerOpts, bazelOpts, program);
|
||||
}
|
||||
const {diagnostics, emitResult, program} = ng.performCompilation(
|
||||
{rootNames: files, options: compilerOpts, host: ngHost, emitCallback, gatherDiagnostics});
|
||||
const tsickleEmitResult = emitResult as tsickle.EmitResult;
|
||||
let externs = '/** @externs */\n';
|
||||
if (diagnostics.length) {
|
||||
console.error(ng.formatDiagnostics(compilerOpts, diagnostics));
|
||||
} else if (emitResult) {
|
||||
if (!diagnostics.length) {
|
||||
if (bazelOpts.tsickleGenerateExterns) {
|
||||
externs += tsickle.getGeneratedExterns(emitResult.externs);
|
||||
externs += tsickle.getGeneratedExterns(tsickleEmitResult.externs);
|
||||
}
|
||||
if (bazelOpts.manifest) {
|
||||
const manifest = constructManifest(emitResult.modulesManifest, bazelHost);
|
||||
const manifest = constructManifest(tsickleEmitResult.modulesManifest, bazelHost);
|
||||
fs.writeFileSync(bazelOpts.manifest, manifest);
|
||||
}
|
||||
}
|
||||
@ -257,9 +231,14 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
return {program, diagnostics};
|
||||
}
|
||||
|
||||
function defaultGatherDiagnostics(
|
||||
ngProgram: ng.Program, inputsToCheck: ts.SourceFile[],
|
||||
genFilesToCheck: ng.GeneratedFile[]): (ng.Diagnostic | ts.Diagnostic)[] {
|
||||
function isCompilationTarget(bazelOpts: BazelOptions, sf: ts.SourceFile): boolean {
|
||||
return !NGC_GEN_FILES.test(sf.fileName) &&
|
||||
(bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1);
|
||||
}
|
||||
|
||||
function gatherDiagnosticsForInputsOnly(
|
||||
options: ng.CompilerOptions, bazelOpts: BazelOptions,
|
||||
ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] {
|
||||
const tsProgram = ngProgram.getTsProgram();
|
||||
const diagnostics: (ng.Diagnostic | ts.Diagnostic)[] = [];
|
||||
// These checks mirror ts.getPreEmitDiagnostics, with the important
|
||||
@ -267,7 +246,7 @@ function defaultGatherDiagnostics(
|
||||
// program.getDeclarationDiagnostics() it somehow corrupts the emit.
|
||||
diagnostics.push(...tsProgram.getOptionsDiagnostics());
|
||||
diagnostics.push(...tsProgram.getGlobalDiagnostics());
|
||||
for (const sf of inputsToCheck) {
|
||||
for (const sf of tsProgram.getSourceFiles().filter(f => isCompilationTarget(bazelOpts, f))) {
|
||||
// Note: We only get the diagnostics for individual files
|
||||
// to e.g. not check libraries.
|
||||
diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf));
|
||||
@ -277,9 +256,7 @@ function defaultGatherDiagnostics(
|
||||
// only gather the angular diagnostics if we have no diagnostics
|
||||
// in any other files.
|
||||
diagnostics.push(...ngProgram.getNgStructuralDiagnostics());
|
||||
for (const genFile of genFilesToCheck) {
|
||||
diagnostics.push(...ngProgram.getNgSemanticDiagnostics(genFile));
|
||||
}
|
||||
diagnostics.push(...ngProgram.getNgSemanticDiagnostics());
|
||||
}
|
||||
return diagnostics;
|
||||
}
|
||||
|
39
packages/bazel/test/ngc-wrapped/BUILD.bazel
Normal file
39
packages/bazel/test/ngc-wrapped/BUILD.bazel
Normal file
@ -0,0 +1,39 @@
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "ngc_test_lib",
|
||||
srcs = [
|
||||
"index_test.ts",
|
||||
"test_support.ts",
|
||||
"tsconfig_template.ts",
|
||||
],
|
||||
deps = [
|
||||
# BEGIN-INTERNAL
|
||||
# Only needed when compiling within the Angular repo.
|
||||
# Users will get this dependency from node_modules.
|
||||
"@//packages/compiler-cli",
|
||||
# END-INTERNAL
|
||||
"//src/ngc-wrapped:ngc_lib"
|
||||
],
|
||||
tsconfig = ":tsconfig.json",
|
||||
)
|
||||
|
||||
# We need a filegroup so that we can refer
|
||||
# .d.ts files (by default, jasmine_node_test would get the .js files).
|
||||
filegroup(
|
||||
name = "angular_core",
|
||||
srcs = ["@//packages/core"]
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "ngc_test",
|
||||
srcs = [":ngc_test_lib"],
|
||||
data = [
|
||||
"@build_bazel_rules_typescript//internal:worker_protocol.proto",
|
||||
":angular_core",
|
||||
"//test/ngc-wrapped/empty:empty_tsconfig.json",
|
||||
"//test/ngc-wrapped/empty:tsconfig.json",
|
||||
],
|
||||
size="small",
|
||||
)
|
10
packages/bazel/test/ngc-wrapped/empty/BUILD.bazel
Normal file
10
packages/bazel/test/ngc-wrapped/empty/BUILD.bazel
Normal file
@ -0,0 +1,10 @@
|
||||
load("@angular//:index.bzl", "ng_module")
|
||||
|
||||
package(default_visibility=["//test:__subpackages__"])
|
||||
|
||||
ng_module(
|
||||
name = "empty",
|
||||
srcs = ["empty.ts"],
|
||||
deps = ["@//packages/core"],
|
||||
tsconfig = ":tsconfig.json",
|
||||
)
|
1
packages/bazel/test/ngc-wrapped/empty/README.md
Normal file
1
packages/bazel/test/ngc-wrapped/empty/README.md
Normal file
@ -0,0 +1 @@
|
||||
# Empty ng_module to capture the default tsconfig.json
|
10
packages/bazel/test/ngc-wrapped/empty/empty.ts
Normal file
10
packages/bazel/test/ngc-wrapped/empty/empty.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// Empty file for an empty ng_module
|
||||
// to capture the tsconfig used by ng_module.
|
7
packages/bazel/test/ngc-wrapped/empty/tsconfig.json
Normal file
7
packages/bazel/test/ngc-wrapped/empty/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@angular/core": ["../../../../dist/packages/core"]
|
||||
}
|
||||
}
|
||||
}
|
36
packages/bazel/test/ngc-wrapped/index_test.ts
Normal file
36
packages/bazel/test/ngc-wrapped/index_test.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import {setup} from './test_support';
|
||||
|
||||
describe('ngc_wrapped', () => {
|
||||
|
||||
it('should work', () => {
|
||||
const {read, write, runOneBuild, writeConfig, shouldExist, basePath} = setup();
|
||||
|
||||
write('some_project/index.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
console.log('works: ', Component);
|
||||
`);
|
||||
|
||||
writeConfig({
|
||||
srcTargetPath: 'some_project',
|
||||
});
|
||||
|
||||
// expect no error
|
||||
expect(runOneBuild()).toBe(true);
|
||||
|
||||
shouldExist('bazel-bin/some_project/index.js');
|
||||
|
||||
expect(read('bazel-bin/some_project/index.js'))
|
||||
.toContain(`console.log('works: ', core_1.Component);`);
|
||||
});
|
||||
});
|
161
packages/bazel/test/ngc-wrapped/test_support.ts
Normal file
161
packages/bazel/test/ngc-wrapped/test_support.ts
Normal file
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {runOneBuild} from '@angular/bazel';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {createTsConfig} from './tsconfig_template';
|
||||
|
||||
export interface TestSupport {
|
||||
basePath: string;
|
||||
runfilesPath: string;
|
||||
angularCorePath: string;
|
||||
writeConfig({
|
||||
srcTargetPath, depPaths, pathMapping,
|
||||
}: {
|
||||
srcTargetPath: string,
|
||||
depPaths?: string[],
|
||||
pathMapping?: Array<{moduleName: string; path: string;}>,
|
||||
}): void;
|
||||
read(fileName: string): string;
|
||||
write(fileName: string, content: string): void;
|
||||
writeFiles(...mockDirs: {[fileName: string]: string}[]): void;
|
||||
shouldExist(fileName: string): void;
|
||||
shouldNotExist(fileName: string): void;
|
||||
runOneBuild(): void;
|
||||
}
|
||||
|
||||
export function setup(
|
||||
{
|
||||
bazelBin = 'bazel-bin', tsconfig = 'tsconfig.json',
|
||||
}: {
|
||||
bazelBin?: string,
|
||||
tsconfig?: string,
|
||||
} = {}): TestSupport {
|
||||
const runfilesPath = process.env['RUNFILES'];
|
||||
|
||||
const basePath = makeTempDir(runfilesPath);
|
||||
|
||||
const bazelBinPath = path.resolve(basePath, bazelBin);
|
||||
fs.mkdirSync(bazelBinPath);
|
||||
|
||||
const angularCorePath = path.resolve(runfilesPath, 'angular_src', 'packages', 'core');
|
||||
const ngFiles = listFilesRecursive(angularCorePath);
|
||||
|
||||
const tsConfigJsonPath = path.resolve(basePath, tsconfig);
|
||||
|
||||
return {
|
||||
basePath,
|
||||
runfilesPath,
|
||||
angularCorePath,
|
||||
write,
|
||||
read,
|
||||
writeFiles,
|
||||
writeConfig,
|
||||
shouldExist,
|
||||
shouldNotExist,
|
||||
runOneBuild: runOneBuildImpl
|
||||
};
|
||||
|
||||
// -----------------
|
||||
// helpers
|
||||
|
||||
function write(fileName: string, content: string) {
|
||||
const dir = path.dirname(fileName);
|
||||
if (dir != '.') {
|
||||
const newDir = path.resolve(basePath, dir);
|
||||
if (!fs.existsSync(newDir)) fs.mkdirSync(newDir);
|
||||
}
|
||||
fs.writeFileSync(path.resolve(basePath, fileName), content, {encoding: 'utf-8'});
|
||||
}
|
||||
|
||||
function read(fileName: string) {
|
||||
return fs.readFileSync(path.resolve(basePath, fileName), {encoding: 'utf-8'});
|
||||
}
|
||||
|
||||
function writeFiles(...mockDirs: {[fileName: string]: string}[]) {
|
||||
mockDirs.forEach(
|
||||
(dir) => { Object.keys(dir).forEach((fileName) => { write(fileName, dir[fileName]); }); });
|
||||
}
|
||||
|
||||
function writeConfig({
|
||||
srcTargetPath, depPaths = [], pathMapping = [],
|
||||
}: {
|
||||
srcTargetPath: string,
|
||||
depPaths?: string[],
|
||||
pathMapping?: Array<{moduleName: string; path: string;}>,
|
||||
}) {
|
||||
srcTargetPath = path.resolve(basePath, srcTargetPath);
|
||||
const compilationTargetSrc = listFilesRecursive(srcTargetPath);
|
||||
const target = '//' + path.relative(basePath, srcTargetPath);
|
||||
const files = [...compilationTargetSrc];
|
||||
|
||||
depPaths = depPaths.concat([angularCorePath]);
|
||||
pathMapping = pathMapping.concat([{moduleName: '@angular/core', path: angularCorePath}]);
|
||||
|
||||
for (const depPath of depPaths) {
|
||||
files.push(...listFilesRecursive(depPath).filter(f => f.endsWith('.d.ts')));
|
||||
}
|
||||
|
||||
const pathMappingObj = {};
|
||||
for (const mapping of pathMapping) {
|
||||
pathMappingObj[mapping.moduleName] = [mapping.path];
|
||||
pathMappingObj[path.join(mapping.moduleName, '*')] = [path.join(mapping.path, '*')];
|
||||
}
|
||||
|
||||
const emptyTsConfig = ts.readConfigFile(
|
||||
path.resolve(
|
||||
runfilesPath, 'angular', 'test', 'ngc-wrapped', 'empty', 'empty_tsconfig.json'),
|
||||
read);
|
||||
|
||||
const tsconfig = createTsConfig({
|
||||
defaultTsConfig: emptyTsConfig.config,
|
||||
rootDir: basePath,
|
||||
target: target,
|
||||
outDir: bazelBinPath, compilationTargetSrc,
|
||||
files: files,
|
||||
pathMapping: pathMappingObj,
|
||||
});
|
||||
write(path.resolve(basePath, tsConfigJsonPath), JSON.stringify(tsconfig, null, 2));
|
||||
}
|
||||
|
||||
function shouldExist(fileName: string) {
|
||||
if (!fs.existsSync(path.resolve(basePath, fileName))) {
|
||||
throw new Error(`Expected ${fileName} to be emitted (basePath: ${basePath})`);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldNotExist(fileName: string) {
|
||||
if (fs.existsSync(path.resolve(basePath, fileName))) {
|
||||
throw new Error(`Did not expect ${fileName} to be emitted (basePath: ${basePath})`);
|
||||
}
|
||||
}
|
||||
|
||||
function runOneBuildImpl(): boolean { return runOneBuild(['@' + tsConfigJsonPath]); }
|
||||
}
|
||||
|
||||
function makeTempDir(baseDir): string {
|
||||
const id = (Math.random() * 1000000).toFixed(0);
|
||||
const dir = path.join(baseDir, `tmp.${id}`);
|
||||
fs.mkdirSync(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
export function listFilesRecursive(dir: string, fileList: string[] = []) {
|
||||
fs.readdirSync(dir).map(file => {
|
||||
if (fs.statSync(path.join(dir, file)).isDirectory()) {
|
||||
listFilesRecursive(path.join(dir, file), fileList);
|
||||
} else {
|
||||
fileList.push(path.join(dir, file));
|
||||
}
|
||||
});
|
||||
return fileList;
|
||||
}
|
6
packages/bazel/test/ngc-wrapped/tsconfig.json
Normal file
6
packages/bazel/test/ngc-wrapped/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es5", "es2015.collection", "es2015.core"],
|
||||
"types": ["node", "jasmine"]
|
||||
}
|
||||
}
|
98
packages/bazel/test/ngc-wrapped/tsconfig_template.ts
Normal file
98
packages/bazel/test/ngc-wrapped/tsconfig_template.ts
Normal file
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
|
||||
export interface TsConfigOptions {
|
||||
defaultTsConfig: any;
|
||||
outDir: string;
|
||||
rootDir: string;
|
||||
pathMapping: {[pattern: string]: string[]};
|
||||
// e.g. //packages/core:core
|
||||
target: string;
|
||||
compilationTargetSrc: string[];
|
||||
files: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tsconfig based on the default tsconfig
|
||||
* to adjust paths, ...
|
||||
*
|
||||
* @param options
|
||||
*/
|
||||
export function createTsConfig(options: TsConfigOptions) {
|
||||
const result = options.defaultTsConfig;
|
||||
|
||||
return {
|
||||
'extends': '../angular/test/ngc-wrapped/empty/tsconfig',
|
||||
'compilerOptions': {
|
||||
...result.compilerOptions,
|
||||
'outDir': options.outDir,
|
||||
'rootDir': options.rootDir,
|
||||
'rootDirs': [
|
||||
options.rootDir,
|
||||
],
|
||||
'baseUrl': options.rootDir,
|
||||
'paths': {
|
||||
'*': [
|
||||
'./*',
|
||||
],
|
||||
...options.pathMapping,
|
||||
},
|
||||
// we have to set this as the default tsconfig is made of es6 mode
|
||||
'target': 'es5',
|
||||
// we have to set this as the default tsconfig is made of es6 mode
|
||||
'module': 'commonjs',
|
||||
// if we specify declarationDir, we also have to specify
|
||||
// declaration in the same tsconfig.json, otherwise ts will error.
|
||||
'declaration': true,
|
||||
'declarationDir': options.outDir,
|
||||
},
|
||||
'bazelOptions': {
|
||||
...result.bazelOptions,
|
||||
'workspaceName': 'angular',
|
||||
'target': options.target,
|
||||
// we have to set this as the default tsconfig is made of es6 mode
|
||||
'es5Mode': true,
|
||||
'manifest': createManifestPath(options),
|
||||
'compilationTargetSrc': options.compilationTargetSrc,
|
||||
},
|
||||
'files': options.files,
|
||||
'angularCompilerOptions': {
|
||||
...result.angularCompilerOptions,
|
||||
'expectedOut': [
|
||||
...options.compilationTargetSrc.map(src => srcToExpectedOut(src, 'js', options)),
|
||||
...options.compilationTargetSrc.map(src => srcToExpectedOut(src, 'd.ts', options)),
|
||||
...options.compilationTargetSrc.map(src => srcToExpectedOut(src, 'ngfactory.js', options)),
|
||||
...options.compilationTargetSrc.map(
|
||||
src => srcToExpectedOut(src, 'ngfactory.d.ts', options)),
|
||||
...options.compilationTargetSrc.map(src => srcToExpectedOut(src, 'ngsummary.js', options)),
|
||||
...options.compilationTargetSrc.map(
|
||||
src => srcToExpectedOut(src, 'ngsummary.d.ts', options)),
|
||||
...options.compilationTargetSrc.map(
|
||||
src => srcToExpectedOut(src, 'ngsummary.json', options)),
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function srcToExpectedOut(srcFile: string, suffix: string, options: TsConfigOptions): string {
|
||||
const baseName = path.basename(srcFile).replace(EXT, '');
|
||||
return path.join(
|
||||
path.relative(options.rootDir, options.outDir),
|
||||
path.relative(options.rootDir, path.dirname(srcFile)), baseName) +
|
||||
'.' + suffix;
|
||||
}
|
||||
|
||||
function createManifestPath(options: TsConfigOptions): string {
|
||||
return path.resolve(options.outDir, options.target.replace(/\/\/|@/g, '').replace(/:/g, '/')) +
|
||||
'.es5.MF';
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
"dependencies": {
|
||||
"@angular/core": "^2.0.0-rc.7",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"rxjs": "^5.0.1",
|
||||
"rxjs": "^5.5.0",
|
||||
"jpm": "1.1.4",
|
||||
"firefox-profile": "0.4.0",
|
||||
"selenium-webdriver": "^2.53.3"
|
||||
|
@ -191,6 +191,14 @@ export class HttpXhrBackend implements HttpBackend {
|
||||
// The parse error contains the text of the body that failed to parse.
|
||||
body = { error, text: body } as HttpJsonParseError;
|
||||
}
|
||||
} else if (!ok && req.responseType === 'json' && typeof body === 'string') {
|
||||
try {
|
||||
// Attempt to parse the body as JSON.
|
||||
body = JSON.parse(body);
|
||||
} catch (error) {
|
||||
// Cannot be certain that the body was meant to be parsed as JSON.
|
||||
// Leave the body as a string.
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
|
@ -17,7 +17,7 @@ import {MockXhrFactory} from './xhr_mock';
|
||||
|
||||
function trackEvents(obs: Observable<HttpEvent<any>>): HttpEvent<any>[] {
|
||||
const events: HttpEvent<any>[] = [];
|
||||
obs.subscribe(event => events.push(event));
|
||||
obs.subscribe(event => events.push(event), err => events.push(err));
|
||||
return events;
|
||||
}
|
||||
|
||||
@ -92,6 +92,13 @@ export function main() {
|
||||
const res = events[1] as HttpResponse<{data: string}>;
|
||||
expect(res.body !.data).toBe('some data');
|
||||
});
|
||||
it('handles a json error response', () => {
|
||||
const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'})));
|
||||
factory.mock.mockFlush(500, 'Error', JSON.stringify({data: 'some data'}));
|
||||
expect(events.length).toBe(2);
|
||||
const res = events[1] as any as HttpErrorResponse;
|
||||
expect(res.error !.data).toBe('some data');
|
||||
});
|
||||
it('handles a json string response', () => {
|
||||
const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'})));
|
||||
expect(factory.mock.responseType).toEqual('text');
|
||||
|
@ -12,6 +12,7 @@
|
||||
"tslib": "^1.7.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": "^5.5.0",
|
||||
"@angular/core": "0.0.0-PLACEHOLDER"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -6,13 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
||||
export {CodeGenerator} from './src/codegen';
|
||||
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host';
|
||||
export {DiagnosticTemplateInfo, getExpressionScope, getTemplateExpressionDiagnostics} from './src/diagnostics/expression_diagnostics';
|
||||
export {AstType, ExpressionDiagnosticsContext} from './src/diagnostics/expression_type';
|
||||
export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './src/diagnostics/symbols';
|
||||
export {getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './src/diagnostics/typescript_symbols';
|
||||
export {Extractor} from './src/extractor';
|
||||
export {VERSION} from './src/version';
|
||||
|
||||
export * from './src/metadata';
|
||||
@ -21,8 +18,7 @@ export * from './src/transformers/entry_points';
|
||||
|
||||
export * from './src/perform_compile';
|
||||
|
||||
// TODO(tbosch): remove this once everyone is on transformers
|
||||
// TODO(tbosch): remove this once cli 1.5 is fully released,
|
||||
// and usages in G3 are changed to `CompilerOptions`.
|
||||
export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api';
|
||||
|
||||
// TODO(hansl): moving to Angular 4 need to update this API.
|
||||
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';
|
||||
|
@ -34,10 +34,10 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!ELEMENT ex (#PCDATA)>
|
||||
]>
|
||||
<messagebundle>
|
||||
<msg id="8136548302122759730" desc="desc" meaning="meaning"><source>src/basic.ts:1</source>translate me</msg>
|
||||
<msg id="3492007542396725315"><source>src/basic.ts:5</source><source>src/entry_components.ts:1</source>Welcome</msg>
|
||||
<msg id="126808141597411718"><source>node_modules/third_party/other_comp.d.ts:1,2</source>other-3rdP-component
|
||||
multi-lines</msg>
|
||||
<msg id="8136548302122759730" desc="desc" meaning="meaning"><source>src/basic.ts:1</source>translate me</msg>
|
||||
<msg id="3492007542396725315"><source>src/basic.ts:5</source><source>src/entry_components.ts:1</source>Welcome</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
||||
@ -45,6 +45,14 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="fr" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
<trans-unit id="b0a17f08a4bd742b2acf39780c257c2f519d33ed" datatype="html">
|
||||
<source>other-3rdP-component
|
||||
multi-lines</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">node_modules/third_party/other_comp.d.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
|
||||
<source>translate me</source>
|
||||
<context-group purpose="location">
|
||||
@ -65,14 +73,6 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="b0a17f08a4bd742b2acf39780c257c2f519d33ed" datatype="html">
|
||||
<source>other-3rdP-component
|
||||
multi-lines</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">node_modules/third_party/other_comp.d.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@ -81,6 +81,15 @@ multi-lines</source>
|
||||
const EXPECTED_XLIFF2 = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
|
||||
<file original="ng.template" id="ngi18n">
|
||||
<unit id="126808141597411718">
|
||||
<notes>
|
||||
<note category="location">node_modules/third_party/other_comp.d.ts:1,2</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source>other-3rdP-component
|
||||
multi-lines</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="8136548302122759730">
|
||||
<notes>
|
||||
<note category="description">desc</note>
|
||||
@ -100,15 +109,6 @@ const EXPECTED_XLIFF2 = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<source>Welcome</source>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="126808141597411718">
|
||||
<notes>
|
||||
<note category="location">node_modules/third_party/other_comp.d.ts:1,2</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<source>other-3rdP-component
|
||||
multi-lines</source>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
`;
|
||||
|
@ -15,8 +15,6 @@ import * as ts from 'typescript';
|
||||
import * as assert from 'assert';
|
||||
import {__NGTOOLS_PRIVATE_API_2, readConfiguration} from '@angular/compiler-cli';
|
||||
|
||||
const glob = require('glob');
|
||||
|
||||
/* tslint:disable:no-console */
|
||||
/**
|
||||
* Main method.
|
||||
@ -27,9 +25,6 @@ function main() {
|
||||
console.log(`testing ngtools API...`);
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => codeGenTest())
|
||||
.then(() => codeGenTest(true))
|
||||
.then(() => i18nTest())
|
||||
.then(() => lazyRoutesTest())
|
||||
.then(() => {
|
||||
console.log('All done!');
|
||||
@ -42,152 +37,6 @@ function main() {
|
||||
});
|
||||
}
|
||||
|
||||
function codeGenTest(forceError = false) {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const srcPath = path.join(__dirname, '../src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
const readResources: string[] = [];
|
||||
const wroteFiles: string[] = [];
|
||||
|
||||
const config = readConfiguration(project);
|
||||
const delegateHost = ts.createCompilerHost(config.options, true);
|
||||
const host: ts.CompilerHost = Object.assign(
|
||||
{}, delegateHost,
|
||||
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
|
||||
const program = ts.createProgram(config.rootNames, config.options, host);
|
||||
|
||||
config.options.basePath = basePath;
|
||||
|
||||
console.log(`>>> running codegen for ${project}`);
|
||||
if (forceError) {
|
||||
console.log(`>>> asserting that missingTranslation param with error value throws`);
|
||||
}
|
||||
return __NGTOOLS_PRIVATE_API_2
|
||||
.codeGen({
|
||||
basePath,
|
||||
compilerOptions: config.options, program, host,
|
||||
|
||||
angularCompilerOptions: config.options,
|
||||
|
||||
// i18n options.
|
||||
i18nFormat: 'xlf',
|
||||
i18nFile: path.join(srcPath, 'messages.fi.xlf'),
|
||||
locale: 'fi',
|
||||
missingTranslation: forceError ? 'error' : 'ignore',
|
||||
|
||||
readResource: (fileName: string) => {
|
||||
readResources.push(fileName);
|
||||
if (!host.fileExists(fileName)) {
|
||||
throw new Error(`Compilation failed. Resource file not found: ${fileName}`);
|
||||
}
|
||||
return Promise.resolve(host.readFile(fileName));
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`>>> codegen done, asserting read and wrote files`);
|
||||
|
||||
// Assert for each file that it has been read and each `ts` has a written file associated.
|
||||
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
|
||||
|
||||
allFiles.forEach((fileName: string) => {
|
||||
// Skip tsconfig.
|
||||
if (fileName.match(/tsconfig-build.json$/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert that file was read.
|
||||
if (fileName.match(/\.module\.ts$/)) {
|
||||
const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts');
|
||||
assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`);
|
||||
} else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
|
||||
assert(
|
||||
readResources.indexOf(fileName) != -1,
|
||||
`Expected resource "${fileName}" to be read.`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`done, no errors.`);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (forceError) {
|
||||
assert(
|
||||
e.message.match(`Missing translation for message`),
|
||||
`Expected error message for missing translations`);
|
||||
console.log(`done, error catched`);
|
||||
} else {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function i18nTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
const readResources: string[] = [];
|
||||
const wroteFiles: string[] = [];
|
||||
|
||||
const config = readConfiguration(project);
|
||||
const delegateHost = ts.createCompilerHost(config.options, true);
|
||||
const host: ts.CompilerHost = Object.assign(
|
||||
{}, delegateHost,
|
||||
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
|
||||
const program = ts.createProgram(config.rootNames, config.options, host);
|
||||
|
||||
config.options.basePath = basePath;
|
||||
|
||||
console.log(`>>> running i18n extraction for ${project}`);
|
||||
return __NGTOOLS_PRIVATE_API_2
|
||||
.extractI18n({
|
||||
basePath,
|
||||
compilerOptions: config.options, program, host,
|
||||
angularCompilerOptions: config.options,
|
||||
i18nFormat: 'xlf',
|
||||
locale: undefined,
|
||||
outFile: undefined,
|
||||
readResource: (fileName: string) => {
|
||||
readResources.push(fileName);
|
||||
if (!host.fileExists(fileName)) {
|
||||
throw new Error(`Compilation failed. Resource file not found: ${fileName}`);
|
||||
}
|
||||
return Promise.resolve(host.readFile(fileName));
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`>>> i18n extraction done, asserting read and wrote files`);
|
||||
|
||||
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
|
||||
|
||||
assert(wroteFiles.length == 1, `Expected a single message bundle file.`);
|
||||
|
||||
assert(
|
||||
wroteFiles[0].endsWith('/ngtools_src/messages.xlf'),
|
||||
`Expected the bundle file to be "message.xlf".`);
|
||||
|
||||
allFiles.forEach((fileName: string) => {
|
||||
// Skip tsconfig.
|
||||
if (fileName.match(/tsconfig-build.json$/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert that file was read.
|
||||
if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
|
||||
assert(
|
||||
readResources.indexOf(fileName) != -1,
|
||||
`Expected resource "${fileName}" to be read.`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`done, no errors.`);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error(e.stack);
|
||||
console.error('Extraction failed');
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
function lazyRoutesTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
|
@ -1,126 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// Must be imported first, because Angular decorators throw on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import * as assert from 'assert';
|
||||
import {CompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, readConfiguration} from '@angular/compiler-cli';
|
||||
|
||||
/* tslint:disable:no-console */
|
||||
/**
|
||||
* Main method.
|
||||
* Standalone program that executes the real codegen and tests that
|
||||
* ngsummary.json files are used for libraries.
|
||||
*/
|
||||
function main() {
|
||||
console.log(`testing usage of ngsummary.json files in libraries...`);
|
||||
const basePath = path.resolve(__dirname, '..');
|
||||
const project = path.resolve(basePath, 'tsconfig-build.json');
|
||||
const readFiles: string[] = [];
|
||||
const writtenFiles: {fileName: string, content: string}[] = [];
|
||||
|
||||
class AssertingHostContext extends NodeCompilerHostContext {
|
||||
readFile(fileName: string): string {
|
||||
if (/.*\/node_modules\/.*/.test(fileName) && !/.*ngsummary\.json$/.test(fileName) &&
|
||||
!/package\.json$/.test(fileName)) {
|
||||
// Only allow to read summaries and package.json files from node_modules
|
||||
// TODO (mhevery): Fix this. TypeScript.d.ts does not allow returning null.
|
||||
return null !;
|
||||
}
|
||||
readFiles.push(path.relative(basePath, fileName));
|
||||
return super.readFile(fileName);
|
||||
}
|
||||
readResource(s: string): Promise<string> {
|
||||
readFiles.push(path.relative(basePath, s));
|
||||
return super.readResource(s);
|
||||
}
|
||||
}
|
||||
|
||||
const config = readConfiguration(project);
|
||||
config.options.basePath = basePath;
|
||||
// This flag tells ngc do not recompile libraries.
|
||||
config.options.generateCodeForLibraries = false;
|
||||
|
||||
console.log(`>>> running codegen for ${project}`);
|
||||
codegen(
|
||||
config,
|
||||
(host) => {
|
||||
host.writeFile = (fileName: string, content: string) => {
|
||||
fileName = path.relative(basePath, fileName);
|
||||
writtenFiles.push({fileName, content});
|
||||
};
|
||||
return new AssertingHostContext();
|
||||
})
|
||||
.then((exitCode: any) => {
|
||||
console.log(`>>> codegen done, asserting read files`);
|
||||
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
|
||||
assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/);
|
||||
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
|
||||
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
|
||||
|
||||
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
|
||||
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
|
||||
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
|
||||
|
||||
console.log(`>>> asserting written files`);
|
||||
assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/);
|
||||
|
||||
console.log(`done, no errors.`);
|
||||
process.exit(exitCode);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple adaption of main to just run codegen with a CompilerHostContext
|
||||
*/
|
||||
function codegen(
|
||||
config: {options: CompilerOptions, rootNames: string[]},
|
||||
hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) {
|
||||
const host = ts.createCompilerHost(config.options, true);
|
||||
|
||||
// HACK: patch the realpath to solve symlink issue here:
|
||||
// https://github.com/Microsoft/TypeScript/issues/9552
|
||||
// todo(misko): remove once facade symlinks are removed
|
||||
host.realpath = (path) => path;
|
||||
|
||||
const program = ts.createProgram(config.rootNames, config.options, host);
|
||||
|
||||
return CodeGenerator.create(config.options, {
|
||||
} as any, program, host, hostContextFactory(host)).codegen();
|
||||
}
|
||||
|
||||
function assertSomeFileMatch(fileNames: string[], pattern: RegExp) {
|
||||
assert(
|
||||
fileNames.some(fileName => pattern.test(fileName)),
|
||||
`Expected some read files match ${pattern}`);
|
||||
}
|
||||
|
||||
function assertNoFileMatch(fileNames: string[], pattern: RegExp) {
|
||||
const matches = fileNames.filter(fileName => pattern.test(fileName));
|
||||
assert(
|
||||
matches.length === 0,
|
||||
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
|
||||
}
|
||||
|
||||
function assertWrittenFile(
|
||||
files: {fileName: string, content: string}[], filePattern: RegExp, contentPattern: RegExp) {
|
||||
assert(
|
||||
files.some(file => filePattern.test(file.fileName) && contentPattern.test(file.content)),
|
||||
`Expected some written files for ${filePattern} and content ${contentPattern}`);
|
||||
}
|
||||
|
||||
main();
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user