Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
d138b38bdb | |||
4f409954f8 | |||
00308fcc9f | |||
a7a7bd3ca0 | |||
67630f82b6 | |||
8006be8d8c | |||
48a1f32222 | |||
521f0d4c7d | |||
941cd102b8 | |||
66043e09a2 | |||
12702b20c7 | |||
5de91fe93e | |||
d787b55b31 | |||
3e34fa8651 | |||
8c991756fa | |||
fa0e8ef92c | |||
41abcc34f4 | |||
0aa6341e31 | |||
43377684d9 | |||
dc53248b15 | |||
d1f45002d3 | |||
6353b77f89 | |||
d9c40b7dd5 | |||
a36dfd453b | |||
ced575fd10 | |||
3b63e168e2 | |||
a1d4c2dbf1 | |||
a33182c4ee | |||
66cc2fabf1 | |||
cf8269ee94 | |||
c7241ca0e6 | |||
4a49e19bd7 | |||
4a3a74b7b0 | |||
6aa013cb63 | |||
b9531f90b8 | |||
f239e8496e | |||
57bed3fe73 | |||
c011ffae30 | |||
756dd34519 | |||
267ebf323e | |||
49c45f336e | |||
71236737ab | |||
390837f76e | |||
fbcf7dc889 | |||
5c8c7d8515 | |||
99cc591f63 |
@ -52,10 +52,14 @@ jobs:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
- run: bazel info release
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel build --config=ci packages/...
|
||||
- run: bazel test --config=ci packages/... @angular//...
|
||||
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
||||
# This avoids waiting for a build command to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
- run: bazel query --output=label '//packages/... union @angular//...' | xargs bazel test --config=ci
|
||||
|
||||
- save_cache:
|
||||
key: *cache_key
|
||||
paths:
|
||||
|
@ -15,7 +15,6 @@
|
||||
# hansl - Hans Larsen
|
||||
# IgorMinar - Igor Minar
|
||||
# jasonaden - Jason Aden
|
||||
# juleskremer - Jules Kremer
|
||||
# kara - Kara Erickson
|
||||
# matsko - Matias Niemelä
|
||||
# mhevery - Misko Hevery
|
||||
@ -36,14 +35,32 @@ group_defaults:
|
||||
enabled: true
|
||||
approve_by_comment:
|
||||
enabled: false
|
||||
# see http://docs.pullapprove.com/groups/author_approval/
|
||||
author_approval:
|
||||
# If the author is a reviewer on the PR, they will automatically have an "approved" status.
|
||||
auto: true
|
||||
|
||||
groups:
|
||||
# Require all PRs to have at least one approval from *someone*
|
||||
all:
|
||||
users: all
|
||||
required: 1
|
||||
# In this group, your self-approval does not count
|
||||
author_approval:
|
||||
auto: false
|
||||
ignored: true
|
||||
files:
|
||||
include:
|
||||
- "*"
|
||||
|
||||
root:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "*"
|
||||
exclude:
|
||||
- "WORKSPACE"
|
||||
- "BUILD.bazel"
|
||||
- ".circleci/*"
|
||||
- "aio/*"
|
||||
- "integration/*"
|
||||
@ -71,6 +88,7 @@ groups:
|
||||
- "*.bazel"
|
||||
- "*.bzl"
|
||||
- "packages/bazel/*"
|
||||
- "tools/bazel.rc"
|
||||
users:
|
||||
- alexeagle #primary
|
||||
- chuckjaz
|
||||
@ -86,6 +104,7 @@ groups:
|
||||
- "*.lock"
|
||||
- "tools/*"
|
||||
exclude:
|
||||
- "tools/bazel.rc"
|
||||
- "tools/public_api_guard/*"
|
||||
- "aio/*"
|
||||
users:
|
||||
@ -281,7 +300,7 @@ groups:
|
||||
files:
|
||||
- "packages/benchpress/*"
|
||||
users:
|
||||
# needs primary
|
||||
- alxhub # primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
@ -309,10 +328,8 @@ groups:
|
||||
- "aio/content/navigation.json"
|
||||
- "aio/content/license.md"
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- Foxandxss
|
||||
- stephenfluin
|
||||
- wardbell
|
||||
- Foxandxss
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
@ -326,7 +343,6 @@ groups:
|
||||
- "aio/content/navigation.json"
|
||||
- "aio/content/license.md"
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- stephenfluin
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
|
@ -54,7 +54,6 @@ env:
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=aio_tools_test
|
||||
- CI_MODE=aio
|
||||
- CI_MODE=aio_optional
|
||||
- CI_MODE=aio_e2e AIO_SHARD=0
|
||||
- CI_MODE=aio_e2e AIO_SHARD=1
|
||||
- CI_MODE=bazel
|
||||
@ -64,7 +63,6 @@ matrix:
|
||||
allow_failures:
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
- env: "CI_MODE=aio_optional"
|
||||
|
||||
before_install:
|
||||
# source the env.sh script so that the exported variables are available to other scripts later on
|
||||
|
32
CHANGELOG.md
@ -1,3 +1,33 @@
|
||||
<a name="5.1.3"></a>
|
||||
## [5.1.3](https://github.com/angular/angular/compare/5.1.2...5.1.3) (2018-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** avoid infinite loop with multiple blocked sub triggers ([#21119](https://github.com/angular/angular/issues/21119)) ([3e34fa8](https://github.com/angular/angular/commit/3e34fa8))
|
||||
* **animations:** renaming issue with DOMAnimation. ([#21125](https://github.com/angular/angular/issues/21125)) ([d1f4500](https://github.com/angular/angular/commit/d1f4500))
|
||||
* **common:** handle JS floating point errors in percent pipe ([#20329](https://github.com/angular/angular/issues/20329)) ([fa0e8ef](https://github.com/angular/angular/commit/fa0e8ef)), closes [#20136](https://github.com/angular/angular/issues/20136)
|
||||
* **language-service:** ignore null metadatas ([#20557](https://github.com/angular/angular/issues/20557)) ([48a1f32](https://github.com/angular/angular/commit/48a1f32)), closes [#20260](https://github.com/angular/angular/issues/20260)
|
||||
* **router:** fix wildcard route with lazy loaded module (again) ([#18139](https://github.com/angular/angular/issues/18139)) ([8c99175](https://github.com/angular/angular/commit/8c99175)), closes [#13848](https://github.com/angular/angular/issues/13848)
|
||||
|
||||
|
||||
|
||||
<a name="5.1.2"></a>
|
||||
## [5.1.2](https://github.com/angular/angular/compare/5.1.1...5.1.2) (2017-12-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** fix a Closure compilation issue. ([267ebf3](https://github.com/angular/angular/commit/267ebf3))
|
||||
* **compiler:** make tsx file aot compatible ([756dd34](https://github.com/angular/angular/commit/756dd34)), closes [#20555](https://github.com/angular/angular/issues/20555)
|
||||
* **compiler:** report an error for recursive module references ([ced575f](https://github.com/angular/angular/commit/ced575f))
|
||||
* **compiler-cli:** do not emit invalid .metadata.json files ([a1d4c2d](https://github.com/angular/angular/commit/a1d4c2d))
|
||||
* **compiler-cli:** do not force type checking on .js files ([3b63e16](https://github.com/angular/angular/commit/3b63e16))
|
||||
* **service-worker:** check for updates on navigation ([a33182c](https://github.com/angular/angular/commit/a33182c)), closes [#20877](https://github.com/angular/angular/issues/20877)
|
||||
* **upgrade:** replaces get/setAngularLib with get/setAngularJSGlobal ([66cc2fa](https://github.com/angular/angular/commit/66cc2fa))
|
||||
|
||||
|
||||
|
||||
<a name="5.1.1"></a>
|
||||
## [5.1.1](https://github.com/angular/angular/compare/5.1.0...5.1.1) (2017-12-13)
|
||||
|
||||
@ -13,7 +43,7 @@
|
||||
* **compiler-cli:** disable checkTypes in emit. ([#20828](https://github.com/angular/angular/issues/20828)) ([160a154](https://github.com/angular/angular/commit/160a154))
|
||||
* **compiler-cli:** fix swallowed Error messages ([#20846](https://github.com/angular/angular/issues/20846)) ([6727336](https://github.com/angular/angular/commit/6727336))
|
||||
* **compiler-cli:** merge [@fileoverview](https://github.com/fileoverview) comments. ([#20870](https://github.com/angular/angular/issues/20870)) ([be9a737](https://github.com/angular/angular/commit/be9a737))
|
||||
* **router:** NavigatonError and NavigationCancel should be emitted after resetting the URL ([#20803](https://github.com/angular/angular/issues/20803)) ([baeec4d](https://github.com/angular/angular/commit/baeec4d))
|
||||
* **router:** NavigationError and NavigationCancel should be emitted after resetting the URL ([#20803](https://github.com/angular/angular/issues/20803)) ([baeec4d](https://github.com/angular/angular/commit/baeec4d))
|
||||
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ node_repositories(package_json = ["//:package.json"])
|
||||
git_repository(
|
||||
name = "build_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
tag = "0.6.0",
|
||||
tag = "0.6.2",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_repositories")
|
||||
|
@ -5,7 +5,7 @@
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[0,1,2].*",
|
||||
"**/dummy.module.ts"
|
||||
"!**/dummy.module.ts"
|
||||
],
|
||||
"tags": ["dependency", "di"]
|
||||
}
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.0.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.1.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.1b.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.2.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.3.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,13 +5,17 @@
|
||||
"styles.css",
|
||||
|
||||
"app/app.component.ts",
|
||||
"app/app.component.html",
|
||||
"app/app.component.css",
|
||||
"app/app.module.ts",
|
||||
"app/data-model.ts",
|
||||
"app/hero.service.ts",
|
||||
"app/hero-detail.component.html",
|
||||
"app/hero-detail.component.ts",
|
||||
"app/hero-list.component.html",
|
||||
"app/hero-list.component.ts",
|
||||
"app/hero-detail/hero-detail.component.html",
|
||||
"app/hero-detail/hero-detail.component.ts",
|
||||
"app/hero-detail/hero-detail.component.css",
|
||||
"app/hero-list/hero-list.component.html",
|
||||
"app/hero-list/hero-list.component.ts",
|
||||
"app/hero-list/hero-list.component.css",
|
||||
|
||||
"main-final.ts",
|
||||
"index-final.html"
|
||||
|
@ -6,18 +6,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main-final.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -1,10 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Service Workers';
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
|
||||
describe('sw-example App', () => {
|
||||
let page: AppPage;
|
||||
let logo = element(by.css('img'));
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
@ -15,17 +15,18 @@ describe('sw-example App', () => {
|
||||
});
|
||||
|
||||
it('should display the Angular logo', () => {
|
||||
let logo = element(by.css('img'));
|
||||
page.navigateTo();
|
||||
expect(logo.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should show a header for the list of links', function () {
|
||||
it('should show a header for the list of links', () => {
|
||||
const listHeader = element(by.css('app-root > h2'));
|
||||
expect(listHeader.getText()).toEqual('Here are some links to help you start:');
|
||||
});
|
||||
|
||||
it('should show a list of links', function () {
|
||||
element.all(by.css('ul > li > h2 > a')).then(function(items) {
|
||||
element.all(by.css('ul > li > h2 > a')).then((items) => {
|
||||
expect(items.length).toBe(4);
|
||||
expect(items[0].getText()).toBe('Angular Service Worker Intro');
|
||||
expect(items[1].getText()).toBe('Tour of Heroes');
|
||||
@ -33,5 +34,11 @@ describe('sw-example App', () => {
|
||||
expect(items[3].getText()).toBe('Angular blog');
|
||||
});
|
||||
});
|
||||
|
||||
// Check for a rejected promise as the service worker is not enabled
|
||||
it('SwUpdate.checkForUpdate() should return a rejected promise', () => {
|
||||
const button = element(by.css('button'));
|
||||
const rejectMessage = element(by.css('p'));
|
||||
button.click();
|
||||
expect(rejectMessage.getText()).toContain('rejected: ');
|
||||
});
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "angular.io-example",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^5.0.0",
|
||||
"@angular/common": "^5.0.0",
|
||||
"@angular/compiler": "^5.0.0",
|
||||
"@angular/core": "^5.0.0",
|
||||
"@angular/forms": "^5.0.0",
|
||||
"@angular/http": "^5.0.0",
|
||||
"@angular/service-worker": "^5.0.0",
|
||||
"@angular/platform-browser": "^5.0.0",
|
||||
"@angular/platform-browser-dynamic": "^5.0.0",
|
||||
"@angular/router": "^5.0.0",
|
||||
"core-js": "^2.4.1",
|
||||
"rxjs": "^5.5.2",
|
||||
"zone.js": "^0.8.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "1.5.4",
|
||||
"@angular/compiler-cli": "^5.0.0",
|
||||
"@angular/language-service": "^5.0.0",
|
||||
"@types/jasmine": "~2.5.53",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/node": "~6.0.60",
|
||||
"codelyzer": "^4.0.1",
|
||||
"jasmine-core": "~2.6.2",
|
||||
"jasmine-spec-reporter": "~4.1.0",
|
||||
"karma": "~1.7.0",
|
||||
"karma-chrome-launcher": "~2.1.1",
|
||||
"karma-cli": "~1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.1.2",
|
||||
"ts-node": "~3.2.0",
|
||||
"tslint": "~5.7.0",
|
||||
"typescript": "~2.4.2"
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"description": "Service Worker",
|
||||
"basePath": "src/",
|
||||
"tags": ["service worker"]
|
||||
}
|
@ -5,6 +5,10 @@
|
||||
</h1>
|
||||
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
|
||||
</div>
|
||||
|
||||
<button id="check" (click)="updateCheck()">Check for Update</button>
|
||||
<p id="checkResult">{{updateCheckText}}</p>
|
||||
|
||||
<h2>Here are some links to help you start: </h2>
|
||||
<ul>
|
||||
<li>
|
@ -16,12 +16,12 @@ describe('AppComponent', () => {
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
expect(app.title).toEqual('Service Workers');
|
||||
}));
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to Service Workers!');
|
||||
}));
|
||||
});
|
20
aio/content/examples/service-worker-getting-started/src/app/app.component.ts
Executable file
@ -0,0 +1,20 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Service Workers';
|
||||
updateCheckText = '';
|
||||
|
||||
constructor(private update: SwUpdate) {}
|
||||
|
||||
updateCheck(): void {
|
||||
this.update
|
||||
.checkForUpdate()
|
||||
.then(() => this.updateCheckText = 'resolved')
|
||||
.catch(err => this.updateCheckText = `rejected: ${err.message}`);
|
||||
}
|
||||
}
|
@ -1,19 +1,15 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/interval';
|
||||
|
||||
function promptUser(event): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// #docregion sw-check-update
|
||||
import { interval } from 'rxjs/observable/interval';
|
||||
|
||||
@Injectable()
|
||||
export class CheckForUpdateService {
|
||||
|
||||
constructor(updates: SwUpdate) {
|
||||
Observable.interval(6 * 60 * 60).subscribe(() => updates.checkForUpdate());
|
||||
interval(6 * 60 * 60).subscribe(() => updates.checkForUpdate());
|
||||
}
|
||||
}
|
||||
// #enddocregion sw-check-update
|
@ -5,8 +5,6 @@
|
||||
<title>SwExample</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
@ -547,12 +547,12 @@ In order to prevent collisions in environments where multiple Angular apps share
|
||||
|
||||
### Configuring custom cookie/header names
|
||||
|
||||
If your backend service uses different names for the XSRF token cookie or header, use `HttpClientXsrfModule.withConfig()` to override the defaults.
|
||||
If your backend service uses different names for the XSRF token cookie or header, use `HttpClientXsrfModule.withOptions()` to override the defaults.
|
||||
|
||||
```javascript
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
HttpClientXsrfModule.withConfig({
|
||||
HttpClientXsrfModule.withOptions({
|
||||
cookieName: 'My-Xsrf-Cookie',
|
||||
headerName: 'My-Xsrf-Header',
|
||||
}),
|
||||
|
@ -140,7 +140,7 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</tr>
|
||||
<tr style='vertical-align:top'>
|
||||
<td>
|
||||
<code>ngOnDestroy</code>
|
||||
<code>ngOnDestroy()</code>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
|
@ -1,7 +1,15 @@
|
||||
# Communicating with service workers
|
||||
# Service Worker Communication
|
||||
|
||||
Importing `ServiceWorkerModule` into your `AppModule` doesn't just register the service worker, it also provides a few services you can use to interact with the service worker and control the caching of your app.
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following:
|
||||
* [Getting Started with Service Workers](guide/service-worker-getting-started).
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
## `SwUpdate` service
|
||||
|
||||
The `SwUpdate` service gives you access to events that indicate when the service worker has discovered an available update for your app or when it has activated such an update—meaning it is now serving content from that update to your app.
|
||||
@ -16,7 +24,7 @@ The `SwUpdate` service supports four separate operations:
|
||||
|
||||
The two update events, `available` and `activated`, are `Observable` properties of `SwUpdate`:
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/log-update.service.ts" linenums="false" title="log-update.service.ts" region="sw-update"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/log-update.service.ts" linenums="false" title="log-update.service.ts" region="sw-update"> </code-example>
|
||||
|
||||
|
||||
You can use these events to notify the user of a pending update or to refresh their pages when the code they are running is out of date.
|
||||
@ -27,7 +35,7 @@ It's possible to ask the service worker to check if any updates have been deploy
|
||||
|
||||
Do this with the `checkForUpdate()` method:
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/check-for-update.service.ts" linenums="false" title="check-for-update.service.ts" region="sw-check-update"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/check-for-update.service.ts" linenums="false" title="check-for-update.service.ts" region="sw-check-update"> </code-example>
|
||||
|
||||
|
||||
This method returns a `Promise` which indicates that the update check has completed successfully, though it does not indicate whether an update was discovered as a result of the check. Even if one is found, the service worker must still successfully download the changed files, which can fail. If successful, the `available` event will indicate availability of a new version of the app.
|
||||
@ -36,6 +44,11 @@ This method returns a `Promise` which indicates that the update check has comple
|
||||
|
||||
If the current tab needs to be updated to the latest app version immediately, it can ask to do so with the `activateUpdate()` method:
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/prompt-update.service.ts" linenums="false" title="prompt-update.service.ts" region="sw-activate"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/prompt-update.service.ts" linenums="false" title="prompt-update.service.ts" region="sw-activate"> </code-example>
|
||||
|
||||
Doing this could break lazy-loading into currently running apps, especially if the lazy-loaded chunks use filenames with hashes, which change every version.
|
||||
|
||||
## More on Angular service workers
|
||||
|
||||
You may also be interested in the following:
|
||||
* [Service Worker in Production](guide/service-worker-devops).
|
@ -1,6 +1,13 @@
|
||||
{@a glob}
|
||||
|
||||
# Reference: Configuration file
|
||||
# Service Worker Configuration
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following:
|
||||
* [Service Worker in Production](guide/service-worker-devops).
|
||||
|
||||
<hr />
|
||||
|
||||
The `src/ngsw-config.json` configuration file specifies which files and data URLs the Angular
|
||||
service worker should cache and how it should update the cached files and data. The
|
||||
@ -8,7 +15,7 @@ CLI processes the configuration file during `ng build --prod`. Manually, you can
|
||||
it with the `ngsw-config` tool:
|
||||
|
||||
```sh
|
||||
ngsw-config dist src/ngswn-config.json /base/href
|
||||
ngsw-config dist src/ngsw-config.json /base/href
|
||||
```
|
||||
|
||||
The configuration file uses the JSON format. All file paths must begin with `/`, which is the deployment directory—usually `dist` in CLI projects.
|
||||
@ -159,3 +166,4 @@ The Angular service worker can use either of two caching strategies for data res
|
||||
* `performance`, the default, optimizes for responses that are as fast as possible. If a resource exists in the cache, the cached version is used. This allows for some staleness, depending on the `maxAge`, in exchange for better performance. This is suitable for resources that don't change often; for example, user avatar images.
|
||||
|
||||
* `freshness` optimizes for currency of data, preferentially fetching requested data from the network. Only if the network times out, according to `timeout`, does the request fall back to the cache. This is useful for resources that change frequently; for example, account balances.
|
||||
|
@ -1,7 +1,14 @@
|
||||
# DevOps: Angular service worker in production
|
||||
# Service Worker in Production
|
||||
|
||||
This page is a reference for deploying and supporting production apps that use the Angular service worker. It explains how the Angular service worker fits into the larger production environment, the service worker's behavior under various conditions, and available recourses and fail-safes.
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following:
|
||||
* [Service Worker Communication](guide/service-worker-communications).
|
||||
|
||||
<hr />
|
||||
|
||||
## Service worker and caching of app resources
|
||||
|
||||
Conceptually, you can imagine the Angular service worker as a forward cache or a CDN edge that is installed in the end user's web browser. The service worker's job is to satisfy requests made by the Angular app for resources or data from a local cache, without needing to wait for the network. Like any cache, it has rules for how content is expired and updated.
|
||||
@ -32,12 +39,10 @@ server can ensure that the Angular app always has a consistent set of files.
|
||||
|
||||
#### Update checks
|
||||
|
||||
Every time the Angular service worker starts, it checks for updates to the
|
||||
app by looking for updates to the `ngsw.json` manifest.
|
||||
|
||||
Note that the service worker starts periodically throughout the usage of
|
||||
the app because the web browser terminates the service worker if the page
|
||||
is idle beyond a given timeout.
|
||||
Every time the user opens or refreshes the application, the Angular service worker
|
||||
checks for updates to the app by looking for updates to the `ngsw.json` manifest. If
|
||||
an update is found, it is downloaded and cached automatically, and will be served
|
||||
the next time the application is loaded.
|
||||
|
||||
### Resource integrity
|
||||
|
||||
@ -195,8 +200,8 @@ versions are safe to use, so existing tabs continue to run from
|
||||
cache, but new loads of the app will be served from the network.
|
||||
|
||||
* `SAFE_MODE`: the service worker cannot guarantee the safety of
|
||||
using cached data. Either an unexpected error occurred or all c
|
||||
ached versions are invalid. All traffic will be served from the
|
||||
using cached data. Either an unexpected error occurred or all
|
||||
cached versions are invalid. All traffic will be served from the
|
||||
network, running as little service worker code as possible.
|
||||
|
||||
In both cases, the parenthetical annotation provides the
|
||||
@ -276,8 +281,8 @@ with service workers. Such tools can be powerful when used properly,
|
||||
but there are a few things to keep in mind.
|
||||
|
||||
* When using developer tools, the service worker is kept running
|
||||
in the background and never restarts. For the Angular service
|
||||
worker, this means that update checks to the app will generally not happen.
|
||||
in the background and never restarts. This can cause behavior with Dev
|
||||
Tools open to differ from behavior a user might experience.
|
||||
|
||||
* If you look in the Cache Storage viewer, the cache is frequently
|
||||
out of date. Right click the Cache Storage title and refresh the caches.
|
||||
@ -299,4 +304,8 @@ for `ngsw.json` returns a `404`, then the service worker
|
||||
removes all of its caches and de-registers itself,
|
||||
essentially self-destructing.
|
||||
|
||||
## More on Angular service workers
|
||||
|
||||
You may also be interested in the following:
|
||||
* [Service Worker Configuration](guide/service-worker-config).
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
# Getting started
|
||||
# Getting Started with Service Workers
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following:
|
||||
* [Introduction to Angular service workers](guide/service-worker-intro).
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
Beginning in Angular 5.0.0, you can easily enable Angular service worker support in any CLI project. This document explains how to enable Angular service worker support in new and existing projects. It then uses a simple example to show you a service worker in action, demonstrating loading and basic caching.
|
||||
|
||||
See the <live-example></live-example>.
|
||||
|
||||
|
||||
## Adding a service worker to a new application
|
||||
|
||||
If you're generating a new CLI project, you can use the CLI to set up the Angular service worker as part of creating the project. To do so, add the `--service-worker` flag to the `ng new` command:
|
||||
@ -54,12 +59,12 @@ To import and register the Angular service worker:
|
||||
|
||||
At the top of the root module, `src/app/app.module.ts`, import `ServiceWorkerModule` and `environment`.
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="sw-import"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="sw-import"> </code-example>
|
||||
|
||||
|
||||
Add `ServiceWorkerModule` to the `@NgModule` `imports` array. Use the `register()` helper to take care of registering the service worker, taking care to disable the service worker when not running in production mode.
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="sw-module"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="sw-module"> </code-example>
|
||||
|
||||
The file `ngsw-worker.js` is the name of the prebuilt service worker script, which the CLI copies into `dist/` to deploy along with your server.
|
||||
|
||||
@ -72,7 +77,7 @@ You can begin with the boilerplate version from the CLI, which configures sensib
|
||||
|
||||
Alternately, save the following as `src/ngsw-config.json`:
|
||||
|
||||
<code-example path="service-worker-getstart/src/ngsw-config.json" linenums="false" title="src/ngsw-config.json"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/ngsw-config.json" linenums="false" title="src/ngsw-config.json"> </code-example>
|
||||
|
||||
### Step 5: Build the project
|
||||
|
||||
@ -92,7 +97,9 @@ using an example application.
|
||||
|
||||
### Serving with `http-server`
|
||||
|
||||
As `ng serve` does not work with service workers, you must use a real HTTP server to test your project locally. It's a good idea to test on a dedicated port.
|
||||
Because `ng serve` does not work with service workers, you must use a seperate HTTP server to test your project locally. You can use any HTTP server. The example below uses the [http-server](https://www.npmjs.com/package/http-server) package from npm. To reduce the possibility of conflicts, test on a dedicated port.
|
||||
|
||||
To serve with `http-server`, change to the directory containing your web files and start the web server:
|
||||
|
||||
```sh
|
||||
cd dist
|
||||
@ -181,7 +188,6 @@ What went wrong? Nothing, actually. The Angular service worker is doing its job
|
||||
If you look at the `http-server` logs, you can see the service worker requesting `/ngsw.json`. This is how the service worker checks for updates.
|
||||
|
||||
2. Refresh the page.
|
||||

|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/service-worker/welcome-msg-fr.png" alt="The text has changed to say Bienvenue à app!">
|
||||
@ -189,3 +195,9 @@ If you look at the `http-server` logs, you can see the service worker requesting
|
||||
|
||||
The service worker installed the updated version of your app *in the background*, and the next time the page is loaded or reloaded, the service worker switches to the latest version.
|
||||
|
||||
<hr />
|
||||
|
||||
## More on Angular service workers
|
||||
|
||||
You may also be interested in the following:
|
||||
* [Communicating with service workers](guide/service-worker-communications).
|
@ -1,4 +1,4 @@
|
||||
# Introduction to Angular service workers
|
||||
# Angular Service Worker Introduction
|
||||
|
||||
Service workers augment the traditional web deployment model and empower applications to deliver a user experience with the reliability and performance on par with natively-installed code.
|
||||
|
||||
@ -46,3 +46,8 @@ For more information about browser support, see the [browser support](https://de
|
||||
[Can I Use](http://caniuse.com/#feat=serviceworkers).
|
||||
|
||||
The remainder of this Angular documentation specifically addresses the Angular implementation of service workers.
|
||||
|
||||
## More on Angular service workers
|
||||
|
||||
You may also be interested in the following:
|
||||
* [Getting Started with service workers](guide/service-worker-getting-started).
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 28 KiB |
@ -276,38 +276,6 @@
|
||||
"title": "Routing & Navigation",
|
||||
"tooltip": "Discover the basics of screen navigation with the Angular Router."
|
||||
},
|
||||
{
|
||||
"title": "Service Workers",
|
||||
"tooltip": "Angular service workers: Controlling caching of application resources.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/service-worker-intro",
|
||||
"title": "Introduction",
|
||||
"tooltip": "Angular's implementation of service workers improves user experience with slow or unreliable network connectivity."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-getstart",
|
||||
"title": "Getting Started",
|
||||
"tooltip": "Enabling the service worker in a CLI project and observing behavior in the browser."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-comm",
|
||||
"title": "Communication",
|
||||
"tooltip": "Services that enable you to interact with an Angular service worker."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-devops",
|
||||
"title": "Service Workers in Production",
|
||||
"tooltip": "Information about running applications with service workers, including application update management, debugging, and killing applications."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-configref",
|
||||
"title": "Reference: Configuration File",
|
||||
"tooltip": "The ngsw-config.json configuration file controls service worker caching behavior."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"url": "guide/testing",
|
||||
"title": "Testing",
|
||||
@ -382,6 +350,37 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Service Workers",
|
||||
"tooltip": "Angular service workers: Controlling caching of application resources.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/service-worker-intro",
|
||||
"title": "Introduction",
|
||||
"tooltip": "Angular's implementation of service workers improves user experience with slow or unreliable network connectivity."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-getting-started",
|
||||
"title": "Getting Started",
|
||||
"tooltip": "Enabling the service worker in a CLI project and observing behavior in the browser."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-communications",
|
||||
"title": "Service Worker Communication",
|
||||
"tooltip": "Services that enable you to interact with an Angular service worker."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-devops",
|
||||
"title": "Service Worker in Production",
|
||||
"tooltip": "Running applications with service workers, managing application update, debugging, and killing applications."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-config",
|
||||
"title": "Service Worker Configuration",
|
||||
"tooltip": "Configuring service worker caching behavior."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Upgrading",
|
||||
|
@ -7,15 +7,16 @@ describe('site App', function() {
|
||||
beforeEach(() => {
|
||||
SitePage.setWindowWidth(1050); // Make the window wide enough to show the SideNav side-by-side.
|
||||
page = new SitePage();
|
||||
page.navigateTo();
|
||||
});
|
||||
|
||||
it('should show features text after clicking "Features"', () => {
|
||||
page.navigateTo('');
|
||||
page.getTopMenuLink('features').click();
|
||||
expect(page.getDocViewerText()).toMatch(/Progressive web apps/i);
|
||||
});
|
||||
|
||||
it('should set appropriate window titles', () => {
|
||||
page.navigateTo('');
|
||||
expect(browser.getTitle()).toBe('Angular');
|
||||
|
||||
page.getTopMenuLink('features').click();
|
||||
@ -25,9 +26,9 @@ describe('site App', function() {
|
||||
expect(browser.getTitle()).toBe('Angular');
|
||||
});
|
||||
|
||||
it('should show the tutorial index page at `/tutorial/` after jitterbugging through features', () => {
|
||||
it('should show the tutorial index page at `/tutorial` after jitterbugging through features', () => {
|
||||
// check that we can navigate directly to the tutorial page
|
||||
page.navigateTo('tutorial/');
|
||||
page.navigateTo('tutorial');
|
||||
expect(page.getDocViewerText()).toMatch(/Tutorial: Tour of Heroes/i);
|
||||
|
||||
// navigate to a different page
|
||||
@ -52,24 +53,24 @@ describe('site App', function() {
|
||||
describe('scrolling to the top', () => {
|
||||
it('should scroll to the top when navigating to another page', () => {
|
||||
page.navigateTo('guide/security');
|
||||
browser.sleep(1000); // Wait for initial async scroll-to-top after `onDocRendered`.
|
||||
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
expect(page.getScrollTop()).toBeGreaterThan(0);
|
||||
|
||||
page.navigateTo('api');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
page.getNavItem(/api/i).click();
|
||||
expect(page.locationPath()).toBe('/api');
|
||||
expect(page.getScrollTop()).toBe(0);
|
||||
});
|
||||
|
||||
it('should scroll to the top when navigating to the same page', () => {
|
||||
page.navigateTo('guide/security');
|
||||
browser.sleep(1000); // Wait for initial async scroll-to-top after `onDocRendered`.
|
||||
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
expect(page.getScrollTop()).toBeGreaterThan(0);
|
||||
|
||||
page.navigateTo('guide/security');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
page.getNavItem(/security/i).click();
|
||||
expect(page.locationPath()).toBe('/guide/security');
|
||||
expect(page.getScrollTop()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -87,42 +88,50 @@ describe('site App', function() {
|
||||
page.navigateTo('api');
|
||||
page.locationPath()
|
||||
.then(p => path = p)
|
||||
.then(() => page.ga().then(calls => {
|
||||
.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();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('should call ga with new URL on navigation', done => {
|
||||
let path: string;
|
||||
page.navigateTo('');
|
||||
page.getTopMenuLink('features').click();
|
||||
page.locationPath()
|
||||
.then(p => path = p)
|
||||
.then(() => page.ga().then(calls => {
|
||||
.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', () => {
|
||||
it('should find pages when searching by a partial word in the title', () => {
|
||||
page.navigateTo('');
|
||||
|
||||
page.enterSearch('ngCont');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('NgControl');
|
||||
expect(page.getSearchResults()).toContain('NgControl');
|
||||
|
||||
page.enterSearch('accessor');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('ControlValueAccessor');
|
||||
expect(page.getSearchResults()).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');
|
||||
const results = page.getSearchResults();
|
||||
|
||||
expect(results).toContain('Http');
|
||||
expect(results).toContain('Router');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -28,8 +28,11 @@ export class SitePage {
|
||||
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);
|
||||
navigateTo(pageUrl) {
|
||||
// Navigate to the page, disable animations, and wait for Angular.
|
||||
return browser.get('/' + pageUrl)
|
||||
.then(() => browser.executeScript('document.body.classList.add(\'no-animations\')'))
|
||||
.then(() => browser.waitForAngular());
|
||||
}
|
||||
|
||||
getDocViewerText() {
|
||||
@ -59,6 +62,6 @@ export class SitePage {
|
||||
getSearchResults() {
|
||||
const results = element.all(by.css('.search-results li'));
|
||||
browser.wait(ExpectedConditions.presenceOf(results.first()), 8000);
|
||||
return results;
|
||||
return results.map(link => link.getText());
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,12 @@
|
||||
{"type": 301, "source": "/docs/ts/latest/:any*", "destination": "/:any*"},
|
||||
|
||||
// aot-compiler.md and metadata.md combined into aot-compiler.md - issue #19510
|
||||
{"type": 301, "source": "/guide/metadata", "destination": "/guide/aot-compiler"}
|
||||
{"type": 301, "source": "/guide/metadata", "destination": "/guide/aot-compiler"},
|
||||
|
||||
// service-worker-getstart.md, service-worker-comm.md, service-worker-configref.md
|
||||
{"type": 301, "source": "/guide/service-worker-getstart", "destination": "/guide/service-worker-getting-started"},
|
||||
{"type": 301, "source": "/guide/service-worker-comm", "destination": "/guide/service-worker-communications"},
|
||||
{"type": 301, "source": "/guide/service-worker-configref", "destination": "/guide/service-worker-config"}
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
|
@ -4,12 +4,14 @@
|
||||
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-toolbar color="primary" class="app-toolbar">
|
||||
<button class="hamburger" [class.starting]="isStarting" mat-button
|
||||
(click)="sidenav.toggle()" title="Docs menu">
|
||||
<mat-icon [ngClass]="{'sidenav-open': !isSideBySide }" svgIcon="menu"></mat-icon>
|
||||
<mat-toolbar color="primary" class="app-toolbar" [class.transitioning]="isTransitioning">
|
||||
<button mat-button class="hamburger" (click)="sidenav.toggle()" title="Docs menu">
|
||||
<mat-icon svgIcon="menu"></mat-icon>
|
||||
</button>
|
||||
<a class="nav-link home" href="/"><img src="{{ homeImageUrl }}" title="Home" alt="Home"></a>
|
||||
<a class="nav-link home" href="/" [ngSwitch]="isSideBySide">
|
||||
<img *ngSwitchCase="true" src="assets/images/logos/angular/logo-nav@2x.png" width="150" height="40" title="Home" alt="Home">
|
||||
<img *ngSwitchDefault src="assets/images/logos/angular/shield-large.svg" width="37" height="40" title="Home" alt="Home">
|
||||
</a>
|
||||
<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>
|
||||
</mat-toolbar>
|
||||
@ -17,7 +19,7 @@
|
||||
|
||||
<mat-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
|
||||
|
||||
<mat-sidenav [ngClass]="{'collapsed': !isSideBySide }" #sidenav class="sidenav" [opened]="isOpened" [mode]="mode" (open)="updateHostClasses()" (close)="updateHostClasses()">
|
||||
<mat-sidenav [ngClass]="{'collapsed': !isSideBySide}" #sidenav class="sidenav" [opened]="isOpened" [mode]="mode" (open)="updateHostClasses()" (close)="updateHostClasses()">
|
||||
<aio-nav-menu *ngIf="!isSideBySide" [nodes]="topMenuNarrowNodes" [currentNode]="currentNodes?.TopBarNarrow" [isWide]="false"></aio-nav-menu>
|
||||
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNodes?.SideNav" [isWide]="isSideBySide"></aio-nav-menu>
|
||||
|
||||
@ -28,7 +30,13 @@
|
||||
|
||||
<section class="sidenav-content" [id]="pageId" role="content">
|
||||
<aio-mode-banner [mode]="deployment.mode" [version]="versionInfo"></aio-mode-banner>
|
||||
<aio-doc-viewer [doc]="currentDocument" (docReady)="onDocReady()" (docRemoved)="onDocRemoved()" (docInserted)="onDocInserted()"></aio-doc-viewer>
|
||||
<aio-doc-viewer [class.no-animations]="isStarting"
|
||||
[doc]="currentDocument"
|
||||
(docReady)="onDocReady()"
|
||||
(docRemoved)="onDocRemoved()"
|
||||
(docInserted)="onDocInserted()"
|
||||
(docRendered)="onDocRendered()">
|
||||
</aio-doc-viewer>
|
||||
<aio-dt [on]="dtOn" [(doc)]="currentDocument"></aio-dt>
|
||||
</section>
|
||||
|
||||
|
@ -154,34 +154,35 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('SideNav when side-by-side (wide)', () => {
|
||||
const navigateTo = (path: string) => {
|
||||
locationService.go(path);
|
||||
component.updateSideNav();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
component.onResize(sideBySideBreakPoint + 1); // side-by-side
|
||||
});
|
||||
|
||||
it('should open when nav to a guide page (guide/pipes)', () => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/pipes');
|
||||
expect(sidenav.opened).toBe(true);
|
||||
});
|
||||
|
||||
it('should open when nav to an api page', () => {
|
||||
locationService.go('api/a/b/c/d');
|
||||
fixture.detectChanges();
|
||||
navigateTo('api/a/b/c/d');
|
||||
expect(sidenav.opened).toBe(true);
|
||||
});
|
||||
|
||||
it('should be closed when nav to a marketing page (features)', () => {
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
navigateTo('features');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
describe('when manually closed', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/pipes');
|
||||
hamburger.click();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@ -191,56 +192,53 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
it('should stay closed when nav from one guide page to another', () => {
|
||||
locationService.go('guide/bags');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/bags');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should stay closed when nav from a guide page to api page', () => {
|
||||
locationService.go('api');
|
||||
fixture.detectChanges();
|
||||
navigateTo('api');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should reopen when nav to market page and back to guide page', () => {
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
locationService.go('guide/bags');
|
||||
fixture.detectChanges();
|
||||
navigateTo('features');
|
||||
navigateTo('guide/bags');
|
||||
expect(sidenav.opened).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SideNav when NOT side-by-side (narrow)', () => {
|
||||
const navigateTo = (path: string) => {
|
||||
locationService.go(path);
|
||||
component.updateSideNav();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
component.onResize(sideBySideBreakPoint - 1); // NOT side-by-side
|
||||
});
|
||||
|
||||
it('should be closed when nav to a guide page (guide/pipes)', () => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/pipes');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should be closed when nav to an api page', () => {
|
||||
locationService.go('api/a/b/c/d');
|
||||
fixture.detectChanges();
|
||||
navigateTo('api/a/b/c/d');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should be closed when nav to a marketing page (features)', () => {
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
navigateTo('features');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
describe('when manually opened', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/pipes');
|
||||
hamburger.click();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@ -257,20 +255,17 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
it('should close when nav to another guide page', () => {
|
||||
locationService.go('guide/bags');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/bags');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should close when nav to api page', () => {
|
||||
locationService.go('api');
|
||||
fixture.detectChanges();
|
||||
navigateTo('api');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should close again when nav to market page', () => {
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
navigateTo('features');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
@ -325,101 +320,6 @@ describe('AppComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('pageId', () => {
|
||||
|
||||
it('should set the id of the doc viewer container based on the current doc', () => {
|
||||
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
|
||||
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
expect(component.pageId).toEqual('guide-pipes');
|
||||
expect(container.properties['id']).toEqual('guide-pipes');
|
||||
|
||||
locationService.go('news');
|
||||
fixture.detectChanges();
|
||||
expect(component.pageId).toEqual('news');
|
||||
expect(container.properties['id']).toEqual('news');
|
||||
|
||||
locationService.go('');
|
||||
fixture.detectChanges();
|
||||
expect(component.pageId).toEqual('home');
|
||||
expect(container.properties['id']).toEqual('home');
|
||||
});
|
||||
|
||||
it('should not be affected by changes to the query', () => {
|
||||
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
|
||||
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
|
||||
locationService.go('guide/other?search=http');
|
||||
fixture.detectChanges();
|
||||
expect(component.pageId).toEqual('guide-other');
|
||||
expect(container.properties['id']).toEqual('guide-other');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hostClasses', () => {
|
||||
|
||||
it('should set the css classes of the host container based on the current doc and navigation view', () => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
|
||||
checkHostClass('page', 'guide-pipes');
|
||||
checkHostClass('folder', 'guide');
|
||||
checkHostClass('view', 'SideNav');
|
||||
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
checkHostClass('page', 'features');
|
||||
checkHostClass('folder', 'features');
|
||||
checkHostClass('view', 'TopBar');
|
||||
|
||||
locationService.go('');
|
||||
fixture.detectChanges();
|
||||
checkHostClass('page', 'home');
|
||||
checkHostClass('folder', 'home');
|
||||
checkHostClass('view', '');
|
||||
});
|
||||
|
||||
it('should set the css class of the host container based on the open/closed state of the side nav', async () => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'open');
|
||||
|
||||
sidenav.close();
|
||||
await waitForEmit(sidenav.onClose);
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'closed');
|
||||
|
||||
sidenav.open();
|
||||
await waitForEmit(sidenav.onOpen);
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'open');
|
||||
|
||||
function waitForEmit(emitter: Observable<void>): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
emitter.subscribe(resolve);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should set the css class of the host container based on the initial deployment mode', () => {
|
||||
createTestingModule('a/b', 'archive');
|
||||
initializeTest();
|
||||
checkHostClass('mode', 'archive');
|
||||
});
|
||||
|
||||
function checkHostClass(type, value) {
|
||||
const host = fixture.debugElement;
|
||||
const classes = host.properties['className'];
|
||||
const classArray = classes.split(' ').filter(c => c.indexOf(`${type}-`) === 0);
|
||||
expect(classArray.length).toBeLessThanOrEqual(1, `"${classes}" should have only one class matching ${type}-*`);
|
||||
expect(classArray).toEqual([`${type}-${value}`], `"${classes}" should contain ${type}-${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
describe('currentDocument', () => {
|
||||
it('should display a guide page (guide/pipes)', () => {
|
||||
locationService.go('guide/pipes');
|
||||
@ -895,7 +795,9 @@ describe('AppComponent', () => {
|
||||
|
||||
describe('with mocked DocViewer', () => {
|
||||
const getDocViewer = () => fixture.debugElement.query(By.css('aio-doc-viewer'));
|
||||
const triggerDocReady = () => getDocViewer().triggerEventHandler('docReady', undefined);
|
||||
const triggerDocViewerEvent =
|
||||
(evt: 'docReady' | 'docRemoved' | 'docInserted' | 'docRendered') =>
|
||||
getDocViewer().triggerEventHandler(evt, undefined);
|
||||
|
||||
beforeEach(() => {
|
||||
createTestingModule('a/b');
|
||||
@ -907,7 +809,7 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('initial rendering', () => {
|
||||
it('should initially add the starting class until the first document is ready', fakeAsync(() => {
|
||||
it('should initially add the starting class until a document is rendered', () => {
|
||||
const getSidenavContainer = () => fixture.debugElement.query(By.css('mat-sidenav-container'));
|
||||
|
||||
initializeTest();
|
||||
@ -915,21 +817,181 @@ describe('AppComponent', () => {
|
||||
expect(component.isStarting).toBe(true);
|
||||
expect(getSidenavContainer().classes['starting']).toBe(true);
|
||||
|
||||
triggerDocReady();
|
||||
fixture.detectChanges();
|
||||
expect(component.isStarting).toBe(true);
|
||||
expect(getSidenavContainer().classes['starting']).toBe(true);
|
||||
|
||||
tick(499);
|
||||
fixture.detectChanges();
|
||||
expect(component.isStarting).toBe(true);
|
||||
expect(getSidenavContainer().classes['starting']).toBe(true);
|
||||
|
||||
tick(2);
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isStarting).toBe(false);
|
||||
expect(getSidenavContainer().classes['starting']).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should initially disable animations on the DocViewer for the first rendering', () => {
|
||||
initializeTest();
|
||||
|
||||
expect(component.isStarting).toBe(true);
|
||||
expect(docViewer.classList.contains('no-animations')).toBe(true);
|
||||
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isStarting).toBe(false);
|
||||
expect(docViewer.classList.contains('no-animations')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subsequent rendering', () => {
|
||||
beforeEach(jasmine.clock().install);
|
||||
afterEach(jasmine.clock().uninstall);
|
||||
|
||||
it('should set the transitioning class on `.app-toolbar` while a document is being rendered', () => {
|
||||
const getToolbar = () => fixture.debugElement.query(By.css('.app-toolbar'));
|
||||
|
||||
initializeTest();
|
||||
|
||||
// Initially, `isTransitoning` is true.
|
||||
expect(component.isTransitioning).toBe(true);
|
||||
expect(getToolbar().classes['transitioning']).toBe(true);
|
||||
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(false);
|
||||
expect(getToolbar().classes['transitioning']).toBe(false);
|
||||
|
||||
// While a document is being rendered, `isTransitoning` is set to true.
|
||||
triggerDocViewerEvent('docReady');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(true);
|
||||
expect(getToolbar().classes['transitioning']).toBe(true);
|
||||
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(false);
|
||||
expect(getToolbar().classes['transitioning']).toBe(false);
|
||||
});
|
||||
|
||||
it('should update the sidenav state as soon as a new document is inserted', () => {
|
||||
initializeTest();
|
||||
const updateSideNavSpy = spyOn(component, 'updateSideNav');
|
||||
|
||||
triggerDocViewerEvent('docInserted');
|
||||
jasmine.clock().tick(0);
|
||||
expect(updateSideNavSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
triggerDocViewerEvent('docInserted');
|
||||
jasmine.clock().tick(0);
|
||||
expect(updateSideNavSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pageId', () => {
|
||||
const navigateTo = (path: string) => {
|
||||
locationService.go(path);
|
||||
triggerDocViewerEvent('docInserted');
|
||||
jasmine.clock().tick(0);
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(jasmine.clock().install);
|
||||
afterEach(jasmine.clock().uninstall);
|
||||
|
||||
it('should set the id of the doc viewer container based on the current doc', () => {
|
||||
initializeTest();
|
||||
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
expect(component.pageId).toEqual('guide-pipes');
|
||||
expect(container.properties['id']).toEqual('guide-pipes');
|
||||
|
||||
navigateTo('news');
|
||||
expect(component.pageId).toEqual('news');
|
||||
expect(container.properties['id']).toEqual('news');
|
||||
|
||||
navigateTo('');
|
||||
expect(component.pageId).toEqual('home');
|
||||
expect(container.properties['id']).toEqual('home');
|
||||
});
|
||||
|
||||
it('should not be affected by changes to the query', () => {
|
||||
initializeTest();
|
||||
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
navigateTo('guide/other?search=http');
|
||||
|
||||
expect(component.pageId).toEqual('guide-other');
|
||||
expect(container.properties['id']).toEqual('guide-other');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hostClasses', () => {
|
||||
const triggerUpdateHostClasses = () => {
|
||||
triggerDocViewerEvent('docInserted');
|
||||
jasmine.clock().tick(0);
|
||||
fixture.detectChanges();
|
||||
};
|
||||
const navigateTo = (path: string) => {
|
||||
locationService.go(path);
|
||||
triggerUpdateHostClasses();
|
||||
};
|
||||
|
||||
beforeEach(jasmine.clock().install);
|
||||
afterEach(jasmine.clock().uninstall);
|
||||
|
||||
it('should set the css classes of the host container based on the current doc and navigation view', () => {
|
||||
initializeTest();
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
checkHostClass('page', 'guide-pipes');
|
||||
checkHostClass('folder', 'guide');
|
||||
checkHostClass('view', 'SideNav');
|
||||
|
||||
navigateTo('features');
|
||||
checkHostClass('page', 'features');
|
||||
checkHostClass('folder', 'features');
|
||||
checkHostClass('view', 'TopBar');
|
||||
|
||||
navigateTo('');
|
||||
checkHostClass('page', 'home');
|
||||
checkHostClass('folder', 'home');
|
||||
checkHostClass('view', '');
|
||||
});
|
||||
|
||||
it('should set the css class of the host container based on the open/closed state of the side nav', async () => {
|
||||
initializeTest();
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
checkHostClass('sidenav', 'open');
|
||||
|
||||
sidenav.close();
|
||||
await waitForEmit(sidenav.onClose);
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'closed');
|
||||
|
||||
sidenav.open();
|
||||
await waitForEmit(sidenav.onOpen);
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'open');
|
||||
|
||||
function waitForEmit(emitter: Observable<void>): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
emitter.subscribe(resolve);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should set the css class of the host container based on the initial deployment mode', () => {
|
||||
createTestingModule('a/b', 'archive');
|
||||
initializeTest();
|
||||
|
||||
triggerUpdateHostClasses();
|
||||
checkHostClass('mode', 'archive');
|
||||
});
|
||||
|
||||
function checkHostClass(type, value) {
|
||||
const host = fixture.debugElement;
|
||||
const classes = host.properties['className'];
|
||||
const classArray = classes.split(' ').filter(c => c.indexOf(`${type}-`) === 0);
|
||||
expect(classArray.length).toBeLessThanOrEqual(1, `"${classes}" should have only one class matching ${type}-*`);
|
||||
expect(classArray).toEqual([`${type}-${value}`], `"${classes}" should contain ${type}-${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
describe('progress bar', () => {
|
||||
@ -938,7 +1000,7 @@ describe('AppComponent', () => {
|
||||
const getProgressBar = () => fixture.debugElement.query(By.directive(MatProgressBar));
|
||||
const initializeAndCompleteNavigation = () => {
|
||||
initializeTest();
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
tick(HIDE_DELAY);
|
||||
};
|
||||
|
||||
@ -975,7 +1037,7 @@ describe('AppComponent', () => {
|
||||
it('should not be shown when re-navigating to the empty path', fakeAsync(() => {
|
||||
initializeAndCompleteNavigation();
|
||||
locationService.urlSubject.next('');
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
|
||||
locationService.urlSubject.next('');
|
||||
|
||||
@ -991,7 +1053,7 @@ describe('AppComponent', () => {
|
||||
locationService.urlSubject.next('c/d');
|
||||
|
||||
tick(SHOW_DELAY - 1);
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
|
||||
tick(1);
|
||||
fixture.detectChanges();
|
||||
@ -1005,7 +1067,7 @@ describe('AppComponent', () => {
|
||||
locationService.urlSubject.next('c/d');
|
||||
|
||||
tick(SHOW_DELAY);
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(getProgressBar()).toBeTruthy();
|
||||
@ -1018,7 +1080,7 @@ describe('AppComponent', () => {
|
||||
locationService.urlSubject.next('c/d');
|
||||
|
||||
tick(SHOW_DELAY);
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(getProgressBar()).toBeTruthy();
|
||||
@ -1037,8 +1099,8 @@ describe('AppComponent', () => {
|
||||
locationService.urlSubject.next('c/d'); // The URL changes.
|
||||
locationService.urlSubject.next('e/f'); // The URL changes again before `onDocReady()`.
|
||||
|
||||
tick(SHOW_DELAY - 1); // `onDocReady()` is triggered (for the last doc),
|
||||
triggerDocReady(); // before the progress bar is shown.
|
||||
tick(SHOW_DELAY - 1); // `onDocReady()` is triggered (for the last doc),
|
||||
triggerDocViewerEvent('docReady'); // before the progress bar is shown.
|
||||
|
||||
tick(1);
|
||||
fixture.detectChanges();
|
||||
|
@ -4,7 +4,6 @@ import { MatSidenav } from '@angular/material/sidenav';
|
||||
|
||||
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 { ScrollService } from 'app/shared/scroll.service';
|
||||
@ -16,6 +15,7 @@ import { TocService } from 'app/shared/toc.service';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
import 'rxjs/add/operator/first';
|
||||
|
||||
const sideNavView = 'SideNav';
|
||||
|
||||
@ -58,6 +58,7 @@ export class AppComponent implements OnInit {
|
||||
|
||||
isFetching = false;
|
||||
isStarting = true;
|
||||
isTransitioning = true;
|
||||
isSideBySide = false;
|
||||
private isFetchingTimeout: any;
|
||||
private isSideNavDoc = false;
|
||||
@ -75,18 +76,9 @@ export class AppComponent implements OnInit {
|
||||
|
||||
versionInfo: VersionInfo;
|
||||
|
||||
get homeImageUrl() {
|
||||
return this.isSideBySide ?
|
||||
'assets/images/logos/angular/logo-nav@2x.png' :
|
||||
'assets/images/logos/angular/shield-large.svg';
|
||||
}
|
||||
get isOpened() { return this.isSideBySide && this.isSideNavDoc; }
|
||||
get mode() { return this.isSideBySide ? 'side' : 'over'; }
|
||||
|
||||
// Need the doc-viewer element for scrolling the contents
|
||||
@ViewChild(DocViewerComponent, { read: ElementRef })
|
||||
docViewer: ElementRef;
|
||||
|
||||
// Search related properties
|
||||
showSearchResults = false;
|
||||
searchResults: Observable<SearchResults>;
|
||||
@ -120,12 +112,13 @@ export class AppComponent implements OnInit {
|
||||
|
||||
/* No need to unsubscribe because this root component never dies */
|
||||
|
||||
this.documentService.currentDocument.subscribe(doc => {
|
||||
this.currentDocument = doc;
|
||||
this.setPageId(doc.id);
|
||||
this.setFolderId(doc.id);
|
||||
this.updateHostClasses();
|
||||
});
|
||||
this.documentService.currentDocument.subscribe(doc => this.currentDocument = doc);
|
||||
// Generally, we want to delay updating the host classes for the new document, until after the
|
||||
// leaving document has been removed (to avoid having the styles for the new document applied
|
||||
// prematurely).
|
||||
// On the first document, though, (when we know there is no previous document), we want to
|
||||
// ensure the styles are applied as soon as possible to avoid flicker.
|
||||
this.documentService.currentDocument.first().subscribe(doc => this.updateHostClassesForDoc(doc));
|
||||
|
||||
this.locationService.currentPath.subscribe(path => {
|
||||
// Redirect to docs if we are in not in stable mode and are not hitting a docs page
|
||||
@ -146,21 +139,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
|
||||
this.navigationService.currentNodes.subscribe(currentNodes => {
|
||||
this.currentNodes = currentNodes;
|
||||
|
||||
// Preserve current sidenav open state by default
|
||||
let openSideNav = this.sidenav.opened;
|
||||
const isSideNavDoc = !!currentNodes[sideNavView];
|
||||
|
||||
if (this.isSideNavDoc !== isSideNavDoc) {
|
||||
// View type changed. Is it now a sidenav view (e.g, guide or tutorial)?
|
||||
// Open if changed to a sidenav doc; close if changed to a marketing doc.
|
||||
openSideNav = this.isSideNavDoc = isSideNavDoc;
|
||||
}
|
||||
// May be open or closed when wide; always closed when narrow
|
||||
this.sideNavToggle(this.isSideBySide ? openSideNav : false);
|
||||
});
|
||||
this.navigationService.currentNodes.subscribe(currentNodes => this.currentNodes = currentNodes);
|
||||
|
||||
// Compute the version picker list from the current version and the versions in the navigation map
|
||||
combineLatest(
|
||||
@ -204,14 +183,14 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
onDocReady() {
|
||||
// About to transition to new view.
|
||||
this.isTransitioning = true;
|
||||
|
||||
// Stop fetching timeout (which, when render is fast, means progress bar never shown)
|
||||
clearTimeout(this.isFetchingTimeout);
|
||||
|
||||
// If progress bar has been shown, keep it for at least 500ms (to avoid flashing).
|
||||
setTimeout(() => {
|
||||
this.isStarting = false;
|
||||
this.isFetching = false;
|
||||
}, 500);
|
||||
setTimeout(() => this.isFetching = false, 500);
|
||||
}
|
||||
|
||||
onDocRemoved() {
|
||||
@ -221,11 +200,25 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
onDocInserted() {
|
||||
// TODO: Find a better way to avoid `ExpressionChangedAfterItHasBeenChecked` error.
|
||||
setTimeout(() => {
|
||||
// Update the SideNav state (if necessary).
|
||||
this.updateSideNav();
|
||||
|
||||
// Update the host classes to match the new document.
|
||||
this.updateHostClassesForDoc(this.currentDocument);
|
||||
});
|
||||
|
||||
// Scroll 500ms after the new document has been inserted into the doc-viewer.
|
||||
// The delay is to allow time for async layout to complete.
|
||||
setTimeout(() => this.autoScroll(), 500);
|
||||
}
|
||||
|
||||
onDocRendered() {
|
||||
this.isStarting = false;
|
||||
this.isTransitioning = false;
|
||||
}
|
||||
|
||||
onDocVersionChange(versionIndex: number) {
|
||||
const version = this.docVersions[versionIndex];
|
||||
if (version.url) {
|
||||
@ -290,6 +283,27 @@ export class AppComponent implements OnInit {
|
||||
this.hostClasses = `${mode} ${sideNavOpen} ${pageClass} ${folderClass} ${viewClasses}`;
|
||||
}
|
||||
|
||||
updateHostClassesForDoc(doc: DocumentContents) {
|
||||
this.setPageId(doc.id);
|
||||
this.setFolderId(doc.id);
|
||||
this.updateHostClasses();
|
||||
}
|
||||
|
||||
updateSideNav() {
|
||||
// Preserve current sidenav open state by default.
|
||||
let openSideNav = this.sidenav.opened;
|
||||
const isSideNavDoc = !!this.currentNodes[sideNavView];
|
||||
|
||||
if (this.isSideNavDoc !== isSideNavDoc) {
|
||||
// View type changed. Is it now a sidenav view (e.g, guide or tutorial)?
|
||||
// Open if changed to a sidenav doc; close if changed to a marketing doc.
|
||||
openSideNav = this.isSideNavDoc = isSideNavDoc;
|
||||
}
|
||||
|
||||
// May be open or closed when wide; always closed when narrow.
|
||||
this.sideNavToggle(this.isSideBySide && openSideNav);
|
||||
}
|
||||
|
||||
// Dynamically change height of table of contents container
|
||||
@HostListener('window:scroll')
|
||||
onScroll() {
|
||||
|
@ -43,15 +43,15 @@ export class ApiListComponent implements OnInit {
|
||||
// API types
|
||||
types: Option[] = [
|
||||
{ value: 'all', title: 'All' },
|
||||
{ value: 'directive', title: 'Directive' },
|
||||
{ value: 'pipe', title: 'Pipe'},
|
||||
{ value: 'decorator', title: 'Decorator' },
|
||||
{ value: 'class', title: 'Class' },
|
||||
{ value: 'interface', title: 'Interface' },
|
||||
{ value: 'function', title: 'Function' },
|
||||
{ value: 'const', title: 'Const' },
|
||||
{ value: 'decorator', title: 'Decorator' },
|
||||
{ value: 'directive', title: 'Directive' },
|
||||
{ value: 'enum', title: 'Enum' },
|
||||
{ value: 'type-alias', title: 'Type Alias' },
|
||||
{ value: 'const', title: 'Const'}
|
||||
{ value: 'function', title: 'Function' },
|
||||
{ value: 'interface', title: 'Interface' },
|
||||
{ value: 'pipe', title: 'Pipe' },
|
||||
{ value: 'type-alias', title: 'Type Alias' }
|
||||
];
|
||||
|
||||
statuses: Option[] = [
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
TestDocViewerComponent, TestModule, TestParentComponent
|
||||
} from 'testing/doc-viewer-utils';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { DocViewerComponent } from './doc-viewer.component';
|
||||
import { DocViewerComponent, NO_ANIMATIONS } from './doc-viewer.component';
|
||||
|
||||
|
||||
describe('DocViewerComponent', () => {
|
||||
@ -368,7 +368,7 @@ describe('DocViewerComponent', () => {
|
||||
});
|
||||
|
||||
it('should display nothing if the document has no contents', async () => {
|
||||
docViewer.currViewContainer.innerHTML = 'Test';
|
||||
await doRender('Test');
|
||||
expect(docViewerEl.textContent).toBe('Test');
|
||||
|
||||
await doRender('');
|
||||
@ -647,6 +647,8 @@ describe('DocViewerComponent', () => {
|
||||
oldCurrViewContainer.innerHTML = 'Current view';
|
||||
oldNextViewContainer.innerHTML = 'Next view';
|
||||
|
||||
docViewerEl.appendChild(oldCurrViewContainer);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
});
|
||||
@ -656,122 +658,142 @@ describe('DocViewerComponent', () => {
|
||||
beforeEach(() => DocViewerComponent.animationsEnabled = animationsEnabled);
|
||||
afterEach(() => DocViewerComponent.animationsEnabled = true);
|
||||
|
||||
it('should return an observable', done => {
|
||||
docViewer.swapViews().subscribe(done, done.fail);
|
||||
});
|
||||
[true, false].forEach(noAnimations => {
|
||||
describe(`(.${NO_ANIMATIONS}: ${noAnimations})`, () => {
|
||||
beforeEach(() => docViewerEl.classList[noAnimations ? 'add' : 'remove'](NO_ANIMATIONS));
|
||||
|
||||
it('should swap the views', async () => {
|
||||
await doSwapViews();
|
||||
it('should return an observable', done => {
|
||||
docViewer.swapViews().subscribe(done, done.fail);
|
||||
});
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
it('should swap the views', async () => {
|
||||
await doSwapViews();
|
||||
|
||||
await doSwapViews();
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
expect(docViewer.currViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldNextViewContainer);
|
||||
});
|
||||
await doSwapViews();
|
||||
|
||||
it('should emit `docRemoved` after removing the leaving view', async () => {
|
||||
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
expect(docViewer.currViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldNextViewContainer);
|
||||
});
|
||||
|
||||
it('should emit `docRemoved` after removing the leaving view', async () => {
|
||||
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
});
|
||||
|
||||
docViewer.docRemoved.subscribe(onDocRemovedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocRemovedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not emit `docRemoved` if the leaving view is already removed', async () => {
|
||||
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved');
|
||||
|
||||
docViewer.docRemoved.subscribe(onDocRemovedSpy);
|
||||
docViewerEl.removeChild(oldCurrViewContainer);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocRemovedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit `docInserted` after inserting the entering view', async () => {
|
||||
const onDocInsertedSpy = jasmine.createSpy('onDocInserted').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
docViewer.docInserted.subscribe(onDocInsertedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocInsertedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should call the callback after inserting the entering view', async () => {
|
||||
const onInsertedCb = jasmine.createSpy('onInsertedCb').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
const onDocInsertedSpy = jasmine.createSpy('onDocInserted');
|
||||
|
||||
docViewer.docInserted.subscribe(onDocInsertedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews(onInsertedCb);
|
||||
|
||||
expect(onInsertedCb).toHaveBeenCalledTimes(1);
|
||||
expect(onInsertedCb).toHaveBeenCalledBefore(onDocInsertedSpy);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should empty the previous view', async () => {
|
||||
await doSwapViews();
|
||||
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
|
||||
docViewer.nextViewContainer.innerHTML = 'Next view 2';
|
||||
await doSwapViews();
|
||||
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view 2');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
|
||||
if (animationsEnabled && !noAnimations) {
|
||||
// Only test this when there are animations. Without animations, the views are swapped
|
||||
// synchronously, so there is no need (or way) to abort.
|
||||
it('should abort swapping if the returned observable is unsubscribed from', async () => {
|
||||
docViewer.swapViews().subscribe().unsubscribe();
|
||||
await doSwapViews();
|
||||
|
||||
// Since the first call was cancelled, only one swapping should have taken place.
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
} else {
|
||||
it('should swap views synchronously when animations are disabled', () => {
|
||||
const cbSpy = jasmine.createSpy('cb');
|
||||
|
||||
docViewer.swapViews(cbSpy).subscribe();
|
||||
|
||||
expect(cbSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
docViewer.docRemoved.subscribe(onDocRemovedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocRemovedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not emit `docRemoved` if the leaving view is already removed', async () => {
|
||||
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved');
|
||||
|
||||
docViewer.docRemoved.subscribe(onDocRemovedSpy);
|
||||
docViewerEl.removeChild(oldCurrViewContainer);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocRemovedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit `docInserted` after inserting the entering view', async () => {
|
||||
const onDocInsertedSpy = jasmine.createSpy('onDocInserted').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
docViewer.docInserted.subscribe(onDocInsertedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocInsertedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should call the callback after inserting the entering view', async () => {
|
||||
const onInsertedCb = jasmine.createSpy('onInsertedCb').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
const onDocInsertedSpy = jasmine.createSpy('onDocInserted');
|
||||
|
||||
docViewer.docInserted.subscribe(onDocInsertedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews(onInsertedCb);
|
||||
|
||||
expect(onInsertedCb).toHaveBeenCalledTimes(1);
|
||||
expect(onInsertedCb).toHaveBeenCalledBefore(onDocInsertedSpy);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should empty the previous view', async () => {
|
||||
await doSwapViews();
|
||||
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
|
||||
docViewer.nextViewContainer.innerHTML = 'Next view 2';
|
||||
await doSwapViews();
|
||||
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view 2');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
|
||||
if (animationsEnabled) {
|
||||
// Without animations, the views are swapped synchronously,
|
||||
// so there is no need (or way) to abort.
|
||||
it('should abort swapping if the returned observable is unsubscribed from', async () => {
|
||||
docViewer.swapViews().subscribe().unsubscribe();
|
||||
await doSwapViews();
|
||||
|
||||
// Since the first call was cancelled, only one swapping should have taken place.
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -15,6 +15,9 @@ import { Logger } from 'app/shared/logger.service';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
|
||||
// Constants
|
||||
export const NO_ANIMATIONS = 'no-animations';
|
||||
|
||||
// Initialization prevents flicker once pre-rendering is on
|
||||
const initialDocViewerElement = document.querySelector('aio-doc-viewer');
|
||||
const initialDocViewerContent = initialDocViewerElement ? initialDocViewerElement.innerHTML : '';
|
||||
@ -77,8 +80,6 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
|
||||
|
||||
if (this.hostElement.firstElementChild) {
|
||||
this.currViewContainer = this.hostElement.firstElementChild as HTMLElement;
|
||||
} else {
|
||||
this.hostElement.appendChild(this.currViewContainer);
|
||||
}
|
||||
|
||||
this.onDestroy$.subscribe(() => this.destroyEmbeddedComponents());
|
||||
@ -176,10 +177,21 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
|
||||
return () => cancelAnimationFrame(rafId);
|
||||
});
|
||||
|
||||
// Get the actual transition duration (taking global styles into account).
|
||||
// According to the [CSSOM spec](https://drafts.csswg.org/cssom/#serializing-css-values),
|
||||
// `time` values should be returned in seconds.
|
||||
const getActualDuration = (elem: HTMLElement) => {
|
||||
const cssValue = getComputedStyle(elem).transitionDuration;
|
||||
const seconds = Number(cssValue.replace(/s$/, ''));
|
||||
return 1000 * seconds;
|
||||
};
|
||||
const animateProp =
|
||||
(elem: HTMLElement, prop: string, from: string, to: string, duration = 333) => {
|
||||
(elem: HTMLElement, prop: string, from: string, to: string, duration = 200) => {
|
||||
const animationsDisabled = !DocViewerComponent.animationsEnabled
|
||||
|| this.hostElement.classList.contains(NO_ANIMATIONS);
|
||||
|
||||
elem.style.transition = '';
|
||||
return !DocViewerComponent.animationsEnabled
|
||||
return animationsDisabled
|
||||
? this.void$.do(() => elem.style[prop] = to)
|
||||
: this.void$
|
||||
// In order to ensure that the `from` value will be applied immediately (i.e.
|
||||
@ -189,11 +201,11 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
|
||||
.switchMap(() => raf$).do(() => elem.style[prop] = from)
|
||||
.switchMap(() => raf$).do(() => elem.style.transition = `all ${duration}ms ease-in-out`)
|
||||
.switchMap(() => raf$).do(() => elem.style[prop] = to)
|
||||
.switchMap(() => timer(duration)).switchMap(() => this.void$);
|
||||
.switchMap(() => timer(getActualDuration(elem))).switchMap(() => this.void$);
|
||||
};
|
||||
|
||||
const animateLeave = (elem: HTMLElement) => animateProp(elem, 'opacity', '1', '0.25');
|
||||
const animateEnter = (elem: HTMLElement) => animateProp(elem, 'opacity', '0.25', '1');
|
||||
const animateLeave = (elem: HTMLElement) => animateProp(elem, 'opacity', '1', '0.1');
|
||||
const animateEnter = (elem: HTMLElement) => animateProp(elem, 'opacity', '0.1', '1');
|
||||
|
||||
let done$ = this.void$;
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe('SearchBoxComponent', () => {
|
||||
|
||||
describe('initialisation', () => {
|
||||
it('should get the current search query from the location service',
|
||||
inject([LocationService], (location: MockLocationService) => fakeAsync(() => {
|
||||
fakeAsync(inject([LocationService], (location: MockLocationService) => {
|
||||
location.search.and.returnValue({ search: 'initial search' });
|
||||
component.ngOnInit();
|
||||
expect(location.search).toHaveBeenCalled();
|
||||
|
4
aio/src/styles/1-layouts/_doc-viewer.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.no-animations aio-doc-viewer > * {
|
||||
// Disable view transition animations.
|
||||
transition: none !important;
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
@import 'api-page';
|
||||
@import 'content-layout';
|
||||
@import 'doc-viewer';
|
||||
@import 'footer';
|
||||
@import 'layout-global';
|
||||
@import 'marketing-layout';
|
||||
|
@ -1,3 +1,92 @@
|
||||
// VARIABLES
|
||||
$hamburgerShownMargin: 0;
|
||||
$hamburgerHiddenMargin: 0 24px 0 -88px;
|
||||
|
||||
|
||||
// DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED
|
||||
mat-toolbar.mat-toolbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
padding: 0 16px 0 0;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);
|
||||
|
||||
mat-icon {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar {
|
||||
background-color: $blue;
|
||||
|
||||
@media (min-width: 481px) {
|
||||
&:not(.transitioning) {
|
||||
background-color: transparent;
|
||||
transition: background-color .2s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-features mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-events mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
box-shadow: none;
|
||||
|
||||
// FIXED TOPNAV TOOLBAR FOR SMALL MOBILE
|
||||
@media (min-width: 481px) {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// DOCS PAGES OVERRIDE: HAMBURGER
|
||||
aio-shell.folder-api mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-docs mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-guide mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
@media (min-width: 992px) {
|
||||
.hamburger.mat-button {
|
||||
// Hamburger shown on non-marketing pages on large screens.
|
||||
margin: $hamburgerShownMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HAMBURGER BUTTON
|
||||
.hamburger.mat-button {
|
||||
height: 100%;
|
||||
margin: $hamburgerShownMargin;
|
||||
padding: 0;
|
||||
transition-duration: .4s;
|
||||
transition-property: color, margin;
|
||||
transition-timing-function: cubic-bezier(.25, .8, .25, 1);
|
||||
|
||||
@media (min-width: 992px) {
|
||||
// Hamburger hidden by default on large screens.
|
||||
// (Will be shown per doc.)
|
||||
margin: $hamburgerHiddenMargin;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $offwhite;
|
||||
}
|
||||
|
||||
& .mat-icon {
|
||||
color: white;
|
||||
position: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HOME NAV-LINK
|
||||
.nav-link.home img {
|
||||
position: relative;
|
||||
margin-top: -21px;
|
||||
@ -12,6 +101,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TOP MENU
|
||||
aio-top-menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -56,55 +147,6 @@ aio-top-menu {
|
||||
}
|
||||
}
|
||||
|
||||
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR HAMBURGER MENU
|
||||
aio-shell.page-home mat-toolbar.app-toolbar.mat-toolbar {
|
||||
background-color: transparent;
|
||||
transition: background-color .2s linear .3s;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
background-color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
// DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED
|
||||
mat-toolbar.mat-toolbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
padding: 0 16px 0 0;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);
|
||||
|
||||
mat-icon {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-features mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-events mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
// FIXED TOPNAV TOOLBAR FOR SMALL MOBILE
|
||||
@media (min-width: 481px) {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
button.hamburger {
|
||||
margin: 0 24px 0 -88px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// REMOVE BOX SHADOW ON CERTAIN MARKETING PAGES
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-events mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
// SEARCH BOX
|
||||
aio-search-box.search-container {
|
||||
|
@ -1,29 +0,0 @@
|
||||
.hamburger {
|
||||
transition-duration: 150ms;
|
||||
transition-property: background-color, color;
|
||||
transition-timing-function: ease-in-out;
|
||||
&:hover {
|
||||
color: $lightgray;
|
||||
}
|
||||
}
|
||||
|
||||
.hamburger.mat-button {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&:not(.starting) {
|
||||
transition-duration: .4s;
|
||||
transition-property: color, margin;
|
||||
transition-timing-function: cubic-bezier(.25, .8, .25, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.hamburger.mat-button:hover {
|
||||
color: $offwhite;
|
||||
}
|
||||
|
||||
.hamburger .mat-icon {
|
||||
position: inherit;
|
||||
color: white;
|
||||
}
|
@ -16,7 +16,6 @@
|
||||
@import 'edit-page-cta';
|
||||
@import 'features';
|
||||
@import 'filetree';
|
||||
@import 'hamburger';
|
||||
@import 'heading-anchors';
|
||||
@import 'hr';
|
||||
@import 'images';
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"scripts": [
|
||||
{ "name": "ng", "command": "ng" },
|
||||
{ "name": "build", "command": "ng build" },
|
||||
{ "name": "start", "command": "ng serve" },
|
||||
{ "name": "test", "command": "ng test" },
|
||||
{ "name": "lint", "command": "ng lint" },
|
||||
|
@ -54,6 +54,19 @@ class ExampleZipper {
|
||||
}
|
||||
}
|
||||
|
||||
// rename a custom main.ts or index.html file
|
||||
_renameFile(file) {
|
||||
if (/src\/main[-.]\w+\.ts$/.test(file)) {
|
||||
return 'src/main.ts';
|
||||
}
|
||||
|
||||
if (/src\/index[-.]\w+\.html$/.test(file)) {
|
||||
return 'src/index.html';
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
_zipExample(configFileName, sourceDirName, outputDirName) {
|
||||
let json = require(configFileName, 'utf-8');
|
||||
const basePath = json.basePath || '';
|
||||
@ -79,12 +92,16 @@ class ExampleZipper {
|
||||
'tslint.json',
|
||||
'karma-test-shim.js',
|
||||
'karma.conf.js',
|
||||
'tsconfig.json',
|
||||
'src/testing/**/*',
|
||||
'src/.babelrc',
|
||||
'src/favicon.ico',
|
||||
'src/typings.d.ts'
|
||||
'src/polyfills.ts',
|
||||
'src/typings.d.ts',
|
||||
'src/environments/**/*',
|
||||
'src/tsconfig.*'
|
||||
];
|
||||
var defaultExcludes = [
|
||||
var alwaysExcludes = [
|
||||
'!**/bs-config.e2e.json',
|
||||
'!**/*plnkr.*',
|
||||
'!**/*zipper.*',
|
||||
@ -132,13 +149,14 @@ class ExampleZipper {
|
||||
}
|
||||
});
|
||||
|
||||
Array.prototype.push.apply(gpaths, defaultExcludes);
|
||||
Array.prototype.push.apply(gpaths, alwaysExcludes);
|
||||
|
||||
let fileNames = globby.sync(gpaths, { ignore: ['**/node_modules/**']});
|
||||
|
||||
let zip = this._createZipArchive(outputFileName);
|
||||
fileNames.forEach((fileName) => {
|
||||
let relativePath = path.relative(exampleDirName, fileName);
|
||||
relativePath = this._renameFile(relativePath);
|
||||
let content = fs.readFileSync(fileName, 'utf8');
|
||||
let extn = path.extname(fileName).substr(1);
|
||||
// if we don't need to clean up the file then we can do the following.
|
||||
|
@ -38,6 +38,8 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
|
||||
readTypeScriptModules.basePath = API_SOURCE_PATH;
|
||||
readTypeScriptModules.ignoreExportsMatching = [/^[_ɵ]|^VERSION$/];
|
||||
readTypeScriptModules.hidePrivateMembers = true;
|
||||
|
||||
// NOTE: This list shold be in sync with tools/gulp-tasks/public-api.js
|
||||
readTypeScriptModules.sourceFiles = [
|
||||
'animations/index.ts',
|
||||
'animations/browser/index.ts',
|
||||
|
4
build.sh
@ -206,7 +206,7 @@ minify() {
|
||||
base_file=$( basename "${file}" )
|
||||
if [[ "${base_file}" =~ $regex && "${base_file##*.}" != "map" ]]; then
|
||||
local out_file=$(dirname "${file}")/${BASH_REMATCH[1]}.min.js
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${out_file} --source-map ${out_file}.map --source-map-include-sources ${file}
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${out_file} --source-map ${out_file}.map --prefix relative --source-map-include-sources ${file}
|
||||
fi
|
||||
done
|
||||
}
|
||||
@ -476,7 +476,7 @@ do
|
||||
|
||||
if [[ ${PACKAGE} == "common" ]]; then
|
||||
echo "====== Copy i18n locale data"
|
||||
rsync -a --exclude=*.d.ts --exclude=*.metadata.json ${OUT_DIR}/locales/ ${NPM_DIR}/locales
|
||||
rsync -a ${OUT_DIR}/locales/ ${NPM_DIR}/locales
|
||||
fi
|
||||
else
|
||||
echo "====== Copy ${PACKAGE} node tool"
|
||||
|
@ -7,30 +7,11 @@ Caretaker is responsible for merging PRs into the individual branches and intern
|
||||
- Draining the queue of PRs ready to be merged. (PRs with [`PR action: merge`](https://github.com/angular/angular/pulls?q=is%3Aopen+is%3Apr+label%3A%22PR+action%3A+merge%22) label)
|
||||
- Assigining [new issues](https://github.com/angular/angular/issues?q=is%3Aopen+is%3Aissue+no%3Alabel) to individual component authors.
|
||||
|
||||
## Setup
|
||||
|
||||
### Set `upstream` to fetch PRs into your local repo
|
||||
|
||||
Use this conmmands to configure your `git` to fetch PRs into your local repo.
|
||||
|
||||
```
|
||||
git remote add upstream git@github.com:angular/angular.git
|
||||
git config --add remote.upstream.fetch +refs/pull/*/head:refs/remotes/upstream/pr/*
|
||||
```
|
||||
|
||||
|
||||
## Merging the PR
|
||||
|
||||
A PR needs to have `PR action: merge` and `PR target: *` labels to be considered
|
||||
ready to merge. Merging is performed by running `merge-pr` with a PR number to merge.
|
||||
|
||||
NOTE: before running `merge-pr` ensure that you have synced all of the PRs
|
||||
locally by running:
|
||||
|
||||
```
|
||||
$ git fetch upstream
|
||||
```
|
||||
|
||||
To merge a PR run:
|
||||
|
||||
```
|
||||
@ -40,6 +21,7 @@ $ ./scripts/github/merge-pr 1234
|
||||
The `merge-pr` script will:
|
||||
- Ensure that all approriate labels are on the PR.
|
||||
- That the current branch (`master` or `?.?.x` patch) mathches the `PR target: *` label.
|
||||
- Fetches the latest PR code from the `angular/angular` repo.
|
||||
- It will `cherry-pick` all of the SHAs from the PR into the current branch.
|
||||
- It will rewrite commit history by automatically adding `Close #1234` and `(#1234)` into the commit message.
|
||||
|
||||
@ -53,8 +35,8 @@ $ ./scripts/github/merge-pr 1234
|
||||
======================
|
||||
GitHub Merge PR Steps
|
||||
======================
|
||||
git cherry-pick upstream/pr/1234~1..upstream/pr/1234
|
||||
git filter-branch -f --msg-filter "/usr/local/google/home/misko/angular-pr/scripts/github/utils/github.closes 1234" HEAD~1..HEAD
|
||||
git cherry-pick angular/pr/1234~1..angular/pr/1234
|
||||
git filter-branch -f --msg-filter "/home/misko/angular/scripts/github/utils/github.closes 1234" HEAD~1..HEAD
|
||||
```
|
||||
|
||||
If the `cherry-pick` command fails than resolve conflicts and use `git cherry-pick --continue` once ready. After the `cherry-pick` is done cut&paste and run the `filter-branch` command to properly rewrite the messages
|
||||
|
@ -14,7 +14,7 @@ node_repositories(package_json = ["//:package.json"])
|
||||
git_repository(
|
||||
name = "build_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
tag = "0.6.0",
|
||||
tag = "0.6.2",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_repositories")
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "5.1.1",
|
||||
"version": "5.1.3",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
|
@ -6,7 +6,15 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export interface DOMAnimation {
|
||||
/**
|
||||
* DOMAnimation represents the Animation Web API.
|
||||
*
|
||||
* It is an external API by the browser, and must thus use "declare interface",
|
||||
* to prevent renaming by Closure Compiler.
|
||||
*
|
||||
* @see https://developer.mozilla.org/de/docs/Web/API/Animation
|
||||
*/
|
||||
export declare interface DOMAnimation {
|
||||
cancel(): void;
|
||||
play(): void;
|
||||
pause(): void;
|
||||
|
@ -33,17 +33,17 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
} else {
|
||||
this.players.forEach(player => {
|
||||
player.onDone(() => {
|
||||
if (++doneCount >= total) {
|
||||
if (++doneCount == total) {
|
||||
this._onFinish();
|
||||
}
|
||||
});
|
||||
player.onDestroy(() => {
|
||||
if (++destroyCount >= total) {
|
||||
if (++destroyCount == total) {
|
||||
this._onDestroy();
|
||||
}
|
||||
});
|
||||
player.onStart(() => {
|
||||
if (++startCount >= total) {
|
||||
if (++startCount == total) {
|
||||
this._onStart();
|
||||
}
|
||||
});
|
||||
@ -67,9 +67,9 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
|
||||
private _onStart() {
|
||||
if (!this.hasStarted()) {
|
||||
this._started = true;
|
||||
this._onStartFns.forEach(fn => fn());
|
||||
this._onStartFns = [];
|
||||
this._started = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
"./closure-locale.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true
|
||||
"skipTemplateCodegen": true,
|
||||
"skipMetadataEmit": true
|
||||
}
|
||||
}
|
||||
|
@ -270,6 +270,13 @@ function getDateTranslation(
|
||||
return getLocaleDayPeriods(locale, form, <TranslationWidth>width)[currentHours < 12 ? 0 : 1];
|
||||
case TranslationType.Eras:
|
||||
return getLocaleEraNames(locale, <TranslationWidth>width)[date.getFullYear() <= 0 ? 0 : 1];
|
||||
default:
|
||||
// This default case is not needed by TypeScript compiler, as the switch is exhaustive.
|
||||
// However Closure Compiler does not understand that and reports an error in typed mode.
|
||||
// The `throw new Error` below works around the problem, and the unexpected: never variable
|
||||
// makes sure tsc still checks this code is unreachable.
|
||||
const unexpected: never = name;
|
||||
throw new Error(`unexpected translation type ${unexpected}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,11 +46,6 @@ export function formatNumber(
|
||||
num = value;
|
||||
}
|
||||
|
||||
if (style === NumberFormatStyle.Percent) {
|
||||
num = num * 100;
|
||||
}
|
||||
|
||||
const numStr = Math.abs(num) + '';
|
||||
const pattern = parseNumberFormat(format, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign));
|
||||
let formattedText = '';
|
||||
let isZero = false;
|
||||
@ -58,7 +53,11 @@ export function formatNumber(
|
||||
if (!isFinite(num)) {
|
||||
formattedText = getLocaleNumberSymbol(locale, NumberSymbol.Infinity);
|
||||
} else {
|
||||
const parsedNumber = parseNumber(numStr);
|
||||
let parsedNumber = parseNumber(num);
|
||||
|
||||
if (style === NumberFormatStyle.Percent) {
|
||||
parsedNumber = toPercent(parsedNumber);
|
||||
}
|
||||
|
||||
let minInt = pattern.minInt;
|
||||
let minFraction = pattern.minFrac;
|
||||
@ -249,11 +248,35 @@ interface ParsedNumber {
|
||||
integerLen: number;
|
||||
}
|
||||
|
||||
// Transforms a parsed number into a percentage by multiplying it by 100
|
||||
function toPercent(parsedNumber: ParsedNumber): ParsedNumber {
|
||||
// if the number is 0, don't do anything
|
||||
if (parsedNumber.digits[0] === 0) {
|
||||
return parsedNumber;
|
||||
}
|
||||
|
||||
// Getting the current number of decimals
|
||||
const fractionLen = parsedNumber.digits.length - parsedNumber.integerLen;
|
||||
if (parsedNumber.exponent) {
|
||||
parsedNumber.exponent += 2;
|
||||
} else {
|
||||
if (fractionLen === 0) {
|
||||
parsedNumber.digits.push(0, 0);
|
||||
} else if (fractionLen === 1) {
|
||||
parsedNumber.digits.push(0);
|
||||
}
|
||||
parsedNumber.integerLen += 2;
|
||||
}
|
||||
|
||||
return parsedNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number (as a string)
|
||||
* Parses a number.
|
||||
* Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/
|
||||
*/
|
||||
function parseNumber(numStr: string): ParsedNumber {
|
||||
function parseNumber(num: number): ParsedNumber {
|
||||
let numStr = Math.abs(num) + '';
|
||||
let exponent = 0, digits, integerLen;
|
||||
let i, j, zeros;
|
||||
|
||||
@ -356,12 +379,23 @@ function roundNumber(parsedNumber: ParsedNumber, minFrac: number, maxFrac: numbe
|
||||
// Pad out with zeros to get the required fraction length
|
||||
for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0);
|
||||
|
||||
|
||||
let dropTrailingZeros = fractionSize !== 0;
|
||||
// Minimal length = nb of decimals required + current nb of integers
|
||||
// Any number besides that is optional and can be removed if it's a trailing 0
|
||||
const minLen = minFrac + parsedNumber.integerLen;
|
||||
// Do any carrying, e.g. a digit was rounded up to 10
|
||||
const carry = digits.reduceRight(function(carry, d, i, digits) {
|
||||
d = d + carry;
|
||||
digits[i] = d % 10;
|
||||
return Math.floor(d / 10);
|
||||
digits[i] = d < 10 ? d : d - 10; // d % 10
|
||||
if (dropTrailingZeros) {
|
||||
// Do not keep meaningless fractional trailing zeros (e.g. 15.52000 --> 15.52)
|
||||
if (digits[i] === 0 && i >= minLen) {
|
||||
digits.pop();
|
||||
} else {
|
||||
dropTrailingZeros = false;
|
||||
}
|
||||
}
|
||||
return d >= 10 ? 1 : 0; // Math.floor(d / 10);
|
||||
}, 0);
|
||||
if (carry) {
|
||||
digits.unshift(carry);
|
||||
|
@ -79,6 +79,22 @@ export function main() {
|
||||
expect(pipe.transform(1.2, '.2')).toEqual('120.00%');
|
||||
expect(pipe.transform(1.2, '4.2')).toEqual('0,120.00%');
|
||||
expect(pipe.transform(1.2, '4.2', 'fr')).toEqual('0 120,00 %');
|
||||
// see issue #20136
|
||||
expect(pipe.transform(0.12345674, '0.0-10')).toEqual('12.345674%');
|
||||
expect(pipe.transform(0, '0.0-10')).toEqual('0%');
|
||||
expect(pipe.transform(0.00, '0.0-10')).toEqual('0%');
|
||||
expect(pipe.transform(1, '0.0-10')).toEqual('100%');
|
||||
expect(pipe.transform(0.1, '0.0-10')).toEqual('10%');
|
||||
expect(pipe.transform(0.12, '0.0-10')).toEqual('12%');
|
||||
expect(pipe.transform(0.123, '0.0-10')).toEqual('12.3%');
|
||||
expect(pipe.transform(12.3456, '0.0-10')).toEqual('1,234.56%');
|
||||
expect(pipe.transform(12.345600, '0.0-10')).toEqual('1,234.56%');
|
||||
expect(pipe.transform(12.345699999, '0.0-6')).toEqual('1,234.57%');
|
||||
expect(pipe.transform(12.345699999, '0.4-6')).toEqual('1,234.5700%');
|
||||
expect(pipe.transform(100, '0.4-6')).toEqual('10,000.0000%');
|
||||
expect(pipe.transform(100, '0.0-10')).toEqual('10,000%');
|
||||
expect(pipe.transform(1.5e2)).toEqual('15,000%');
|
||||
expect(pipe.transform(1e100)).toEqual('1E+102%');
|
||||
});
|
||||
|
||||
it('should not support other objects', () => {
|
||||
|
@ -130,7 +130,7 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter implements ts.CompilerHos
|
||||
moduleName, containingFile.replace(/\\/g, '/'), this.options, this,
|
||||
this.moduleResolutionCache)
|
||||
.resolvedModule;
|
||||
if (rm && this.isSourceFile(rm.resolvedFileName)) {
|
||||
if (rm && this.isSourceFile(rm.resolvedFileName) && DTS.test(rm.resolvedFileName)) {
|
||||
// Case: generateCodeForLibraries = true and moduleName is
|
||||
// a .d.ts file in a node_modules folder.
|
||||
// Need to set isExternalLibraryImport to false so that generated files for that file
|
||||
@ -326,7 +326,7 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter implements ts.CompilerHos
|
||||
return {generate: false};
|
||||
}
|
||||
const [, base, genSuffix, suffix] = genMatch;
|
||||
if (suffix !== 'ts') {
|
||||
if (suffix !== 'ts' && suffix !== 'tsx') {
|
||||
return {generate: false};
|
||||
}
|
||||
let baseFileName: string|undefined;
|
||||
@ -337,9 +337,9 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter implements ts.CompilerHos
|
||||
}
|
||||
} else {
|
||||
// Note: on-the-fly generated files always have a `.ts` suffix,
|
||||
// but the file from which we generated it can be a `.ts`/ `.d.ts`
|
||||
// but the file from which we generated it can be a `.ts`/ `.tsx`/ `.d.ts`
|
||||
// (see options.generateCodeForLibraries).
|
||||
baseFileName = [`${base}.ts`, `${base}.d.ts`].find(
|
||||
baseFileName = [`${base}.ts`, `${base}.tsx`, `${base}.d.ts`].find(
|
||||
baseFileName => this.isSourceFile(baseFileName) && this.originalFileExists(baseFileName));
|
||||
if (!baseFileName) {
|
||||
return {generate: false};
|
||||
|
@ -336,9 +336,11 @@ class AngularCompilerProgram implements Program {
|
||||
if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) {
|
||||
metadataJsonCount++;
|
||||
const metadata = this.metadataCache.getMetadata(sf);
|
||||
const metadataText = JSON.stringify([metadata]);
|
||||
const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json'));
|
||||
this.writeFile(outFileName, metadataText, false, undefined, undefined, [sf]);
|
||||
if (metadata) {
|
||||
const metadataText = JSON.stringify([metadata]);
|
||||
const outFileName = srcToOutPath(sf.fileName.replace(/\.tsx?$/, '.metadata.json'));
|
||||
this.writeFile(outFileName, metadataText, false, undefined, undefined, [sf]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1474,6 +1474,26 @@ describe('ngc transformer command-line', () => {
|
||||
});
|
||||
|
||||
describe('regressions', () => {
|
||||
//#20479
|
||||
it('should not generate an invalid metadata file', () => {
|
||||
write('src/tsconfig.json', `{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"files": ["lib.ts"],
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true
|
||||
}
|
||||
}`);
|
||||
write('src/lib.ts', `
|
||||
export namespace A{
|
||||
export class C1 {
|
||||
}
|
||||
export interface I1{
|
||||
}
|
||||
}`);
|
||||
expect(main(['-p', path.join(basePath, 'src/tsconfig.json')])).toBe(0);
|
||||
shouldNotExist('src/lib.metadata.json');
|
||||
});
|
||||
|
||||
//#19544
|
||||
it('should recognize @NgModule() directive with a redundant @Injectable()', () => {
|
||||
write('src/tsconfig.json', `{
|
||||
@ -1616,6 +1636,68 @@ describe('ngc transformer command-line', () => {
|
||||
expect(messages[0]).toContain('Parser Error: Unexpected token');
|
||||
});
|
||||
|
||||
// Regression test for #19979
|
||||
it('should not stack overflow on a recursive module export', () => {
|
||||
write('src/tsconfig.json', `{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"files": ["test-module.ts"]
|
||||
}`);
|
||||
|
||||
write('src/test-module.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: 'Hello'
|
||||
})
|
||||
export class MyFaultyComponent {}
|
||||
|
||||
@NgModule({
|
||||
exports: [MyFaultyModule],
|
||||
declarations: [MyFaultyComponent],
|
||||
providers: [],
|
||||
})
|
||||
export class MyFaultyModule { }
|
||||
`);
|
||||
const messages: string[] = [];
|
||||
expect(
|
||||
main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message)))
|
||||
.toBe(1, 'Compile was expected to fail');
|
||||
expect(messages[0]).toContain(`module 'MyFaultyModule' is exported recursively`);
|
||||
});
|
||||
|
||||
// Regression test for #19979
|
||||
it('should not stack overflow on a recursive module import', () => {
|
||||
write('src/tsconfig.json', `{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"files": ["test-module.ts"]
|
||||
}`);
|
||||
|
||||
write('src/test-module.ts', `
|
||||
import {Component, NgModule, forwardRef} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: 'Hello'
|
||||
})
|
||||
export class MyFaultyComponent {}
|
||||
|
||||
@NgModule({
|
||||
imports: [forwardRef(() => MyFaultyModule)]
|
||||
})
|
||||
export class MyFaultyImport {}
|
||||
|
||||
@NgModule({
|
||||
imports: [MyFaultyImport],
|
||||
declarations: [MyFaultyComponent]
|
||||
})
|
||||
export class MyFaultyModule { }
|
||||
`);
|
||||
const messages: string[] = [];
|
||||
expect(
|
||||
main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message)))
|
||||
.toBe(1, 'Compile was expected to fail');
|
||||
expect(messages[0]).toContain(`is imported recursively by the module 'MyFaultyImport`);
|
||||
});
|
||||
|
||||
it('should allow using 2 classes with the same name in declarations with noEmitOnError=true',
|
||||
() => {
|
||||
write('src/tsconfig.json', `{
|
||||
@ -1647,6 +1729,38 @@ describe('ngc transformer command-line', () => {
|
||||
`);
|
||||
expect(main(['-p', path.join(basePath, 'src/tsconfig.json')])).toBe(0);
|
||||
});
|
||||
|
||||
it('should not type check a .js files from node_modules with allowJs', () => {
|
||||
write('src/tsconfig.json', `{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"noEmitOnError": true,
|
||||
"allowJs": true,
|
||||
"declaration": false
|
||||
},
|
||||
"files": ["test-module.ts"]
|
||||
}`);
|
||||
write('src/test-module.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import 'my-library';
|
||||
|
||||
@Component({
|
||||
template: 'hello'
|
||||
})
|
||||
export class HelloCmp {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [HelloCmp],
|
||||
})
|
||||
export class MyModule {}
|
||||
`);
|
||||
write('src/node_modules/t.txt', ``);
|
||||
write('src/node_modules/my-library/index.js', `
|
||||
export someVar = 1;
|
||||
export someOtherVar = undefined + 1;
|
||||
`);
|
||||
expect(main(['-p', path.join(basePath, 'src/tsconfig.json')])).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatted messages', () => {
|
||||
|
@ -285,6 +285,22 @@ describe('NgCompilerHost', () => {
|
||||
expect(sf.referencedFiles.length).toBe(1);
|
||||
expect(sf.referencedFiles[0].fileName).toBe('main.ts');
|
||||
});
|
||||
|
||||
it('should generate for tsx files', () => {
|
||||
codeGenerator.findGeneratedFileNames.and.returnValue(['/tmp/src/index.ngfactory.ts']);
|
||||
codeGenerator.generateFile.and.returnValue(aGeneratedFile);
|
||||
const host = createHost({files: {'tmp': {'src': {'index.tsx': ``}}}});
|
||||
|
||||
const genSf = host.getSourceFile('/tmp/src/index.ngfactory.ts', ts.ScriptTarget.Latest);
|
||||
expect(genSf.text).toBe(aGeneratedFileText);
|
||||
|
||||
const sf = host.getSourceFile('/tmp/src/index.tsx', ts.ScriptTarget.Latest);
|
||||
expect(sf.referencedFiles[0].fileName).toBe('/tmp/src/index.ngfactory.ts');
|
||||
|
||||
// the codegen should have been cached
|
||||
expect(codeGenerator.generateFile).toHaveBeenCalledTimes(1);
|
||||
expect(codeGenerator.findGeneratedFileNames).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSourceFile', () => {
|
||||
|
@ -391,6 +391,22 @@ describe('ng program', () => {
|
||||
testSupport.shouldExist('built/src/main.ngfactory.d.ts');
|
||||
});
|
||||
|
||||
it('should work with tsx files', () => {
|
||||
// create a temporary ts program to get the list of all files from angular...
|
||||
testSupport.writeFiles({
|
||||
'src/main.tsx': createModuleAndCompSource('main'),
|
||||
});
|
||||
const allRootNames = resolveFiles([path.resolve(testSupport.basePath, 'src/main.tsx')]);
|
||||
|
||||
const program = compile(undefined, {jsx: ts.JsxEmit.React}, allRootNames);
|
||||
|
||||
testSupport.shouldExist('built/src/main.js');
|
||||
testSupport.shouldExist('built/src/main.d.ts');
|
||||
testSupport.shouldExist('built/src/main.ngfactory.js');
|
||||
testSupport.shouldExist('built/src/main.ngfactory.d.ts');
|
||||
testSupport.shouldExist('built/src/main.ngsummary.json');
|
||||
});
|
||||
|
||||
it('should emit also empty generated files depending on the options', () => {
|
||||
testSupport.writeFiles({
|
||||
'src/main.ts': `
|
||||
|
@ -34,7 +34,7 @@ import {StaticReflector} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {createForJitStub, serializeSummaries} from './summary_serializer';
|
||||
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
||||
import {ngfactoryFilePath, normalizeGenFileSuffix, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
||||
|
||||
enum StubEmitFlags {
|
||||
Basic = 1 << 0,
|
||||
@ -103,7 +103,7 @@ export class AotCompiler {
|
||||
genFileNames.push(summaryForJitFileName(file.fileName, true));
|
||||
}
|
||||
}
|
||||
const fileSuffix = splitTypescriptSuffix(file.fileName, true)[1];
|
||||
const fileSuffix = normalizeGenFileSuffix(splitTypescriptSuffix(file.fileName, true)[1]);
|
||||
file.directives.forEach((dirSymbol) => {
|
||||
const compMeta =
|
||||
this._metadataResolver.getNonNormalizedDirectiveMetadata(dirSymbol) !.metadata;
|
||||
@ -318,7 +318,7 @@ export class AotCompiler {
|
||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
||||
injectables: StaticSymbol[]): GeneratedFile[] {
|
||||
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
|
||||
const fileSuffix = normalizeGenFileSuffix(splitTypescriptSuffix(srcFileUrl, true)[1]);
|
||||
const generatedFiles: GeneratedFile[] = [];
|
||||
|
||||
const outputCtx = this._createOutputContext(ngfactoryFilePath(srcFileUrl, true));
|
||||
|
@ -481,7 +481,7 @@ export class StaticSymbolResolver {
|
||||
if (moduleMetadatas) {
|
||||
let maxVersion = -1;
|
||||
moduleMetadatas.forEach((md) => {
|
||||
if (md['version'] > maxVersion) {
|
||||
if (md && md['version'] > maxVersion) {
|
||||
maxVersion = md['version'];
|
||||
moduleMetadata = md;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ const JIT_SUMMARY_NAME = /NgSummary$/;
|
||||
|
||||
export function ngfactoryFilePath(filePath: string, forceSourceFile = false): string {
|
||||
const urlWithSuffix = splitTypescriptSuffix(filePath, forceSourceFile);
|
||||
return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`;
|
||||
return `${urlWithSuffix[0]}.ngfactory${normalizeGenFileSuffix(urlWithSuffix[1])}`;
|
||||
}
|
||||
|
||||
export function stripGeneratedFileSuffix(filePath: string): string {
|
||||
@ -38,6 +38,10 @@ export function splitTypescriptSuffix(path: string, forceSourceFile = false): st
|
||||
return [path, ''];
|
||||
}
|
||||
|
||||
export function normalizeGenFileSuffix(srcFileSuffix: string): string {
|
||||
return srcFileSuffix === '.tsx' ? '.ts' : srcFileSuffix;
|
||||
}
|
||||
|
||||
export function summaryFileName(fileName: string): string {
|
||||
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
|
||||
return `${fileNameWithoutSuffix}.ngsummary.json`;
|
||||
|
@ -441,11 +441,12 @@ export class CompileMetadataResolver {
|
||||
this._ngModuleResolver.isNgModule(type);
|
||||
}
|
||||
|
||||
getNgModuleSummary(moduleType: any): cpl.CompileNgModuleSummary|null {
|
||||
getNgModuleSummary(moduleType: any, alreadyCollecting: Set<any>|null = null):
|
||||
cpl.CompileNgModuleSummary|null {
|
||||
let moduleSummary: cpl.CompileNgModuleSummary|null =
|
||||
<cpl.CompileNgModuleSummary>this._loadSummary(moduleType, cpl.CompileSummaryKind.NgModule);
|
||||
if (!moduleSummary) {
|
||||
const moduleMeta = this.getNgModuleMetadata(moduleType, false);
|
||||
const moduleMeta = this.getNgModuleMetadata(moduleType, false, alreadyCollecting);
|
||||
moduleSummary = moduleMeta ? moduleMeta.toSummary() : null;
|
||||
if (moduleSummary) {
|
||||
this._summaryCache.set(moduleType, moduleSummary);
|
||||
@ -473,7 +474,9 @@ export class CompileMetadataResolver {
|
||||
return Promise.all(loading);
|
||||
}
|
||||
|
||||
getNgModuleMetadata(moduleType: any, throwIfNotFound = true): cpl.CompileNgModuleMetadata|null {
|
||||
getNgModuleMetadata(
|
||||
moduleType: any, throwIfNotFound = true,
|
||||
alreadyCollecting: Set<any>|null = null): cpl.CompileNgModuleMetadata|null {
|
||||
moduleType = resolveForwardRef(moduleType);
|
||||
let compileMeta = this._ngModuleCache.get(moduleType);
|
||||
if (compileMeta) {
|
||||
@ -511,7 +514,18 @@ export class CompileMetadataResolver {
|
||||
|
||||
if (importedModuleType) {
|
||||
if (this._checkSelfImport(moduleType, importedModuleType)) return;
|
||||
const importedModuleSummary = this.getNgModuleSummary(importedModuleType);
|
||||
if (!alreadyCollecting) alreadyCollecting = new Set();
|
||||
if (alreadyCollecting.has(importedModuleType)) {
|
||||
this._reportError(
|
||||
syntaxError(
|
||||
`${this._getTypeDescriptor(importedModuleType)} '${stringifyType(importedType)}' is imported recursively by the module '${stringifyType(moduleType)}'.`),
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
alreadyCollecting.add(importedModuleType);
|
||||
const importedModuleSummary =
|
||||
this.getNgModuleSummary(importedModuleType, alreadyCollecting);
|
||||
alreadyCollecting.delete(importedModuleType);
|
||||
if (!importedModuleSummary) {
|
||||
this._reportError(
|
||||
syntaxError(
|
||||
@ -539,7 +553,17 @@ export class CompileMetadataResolver {
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
const exportedModuleSummary = this.getNgModuleSummary(exportedType);
|
||||
if (!alreadyCollecting) alreadyCollecting = new Set();
|
||||
if (alreadyCollecting.has(exportedType)) {
|
||||
this._reportError(
|
||||
syntaxError(
|
||||
`${this._getTypeDescriptor(exportedType)} '${stringify(exportedType)}' is exported recursively by the module '${stringifyType(moduleType)}'`),
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
alreadyCollecting.add(exportedType);
|
||||
const exportedModuleSummary = this.getNgModuleSummary(exportedType, alreadyCollecting);
|
||||
alreadyCollecting.delete(exportedType);
|
||||
if (exportedModuleSummary) {
|
||||
exportedModules.push(exportedModuleSummary);
|
||||
} else {
|
||||
|
@ -905,6 +905,14 @@ describe('compiler (bundled Angular)', () => {
|
||||
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||
});
|
||||
|
||||
it('should support tsx', () => {
|
||||
const tsOptions = {jsx: ts.JsxEmit.React};
|
||||
const {genFiles} =
|
||||
compile([QUICKSTART_TSX, angularFiles], /* options */ undefined, tsOptions);
|
||||
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bundled library', () => {
|
||||
@ -978,6 +986,34 @@ const QUICKSTART: MockDirectory = {
|
||||
}
|
||||
};
|
||||
|
||||
const QUICKSTART_TSX: MockDirectory = {
|
||||
quickstart: {
|
||||
app: {
|
||||
// #20555
|
||||
'app.component.tsx': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '<h1>Hello {{name}}</h1>'
|
||||
})
|
||||
export class AppComponent {
|
||||
name = 'Angular';
|
||||
}
|
||||
`,
|
||||
'app.module.ts': `
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ AppComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
`
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const LIBRARY: MockDirectory = {
|
||||
bolder: {
|
||||
'public-api.ts': `
|
||||
|
@ -2823,6 +2823,96 @@ export function main() {
|
||||
expect(child.log).toEqual(['child-start', 'child-done']);
|
||||
}));
|
||||
|
||||
it('should fire and synchronize the start/done callbacks on multiple blocked sub triggers',
|
||||
fakeAsync(() => {
|
||||
@Component({
|
||||
selector: 'cmp',
|
||||
animations: [
|
||||
trigger(
|
||||
'parent1',
|
||||
[
|
||||
transition(
|
||||
'* => go, * => go-again',
|
||||
[
|
||||
style({opacity: 0}),
|
||||
animate('1s', style({opacity: 1})),
|
||||
]),
|
||||
]),
|
||||
trigger(
|
||||
'parent2',
|
||||
[
|
||||
transition(
|
||||
'* => go, * => go-again',
|
||||
[
|
||||
style({lineHeight: '0px'}),
|
||||
animate('1s', style({lineHeight: '10px'})),
|
||||
]),
|
||||
]),
|
||||
trigger(
|
||||
'child1',
|
||||
[
|
||||
transition(
|
||||
'* => go, * => go-again',
|
||||
[
|
||||
style({width: '0px'}),
|
||||
animate('1s', style({width: '100px'})),
|
||||
]),
|
||||
]),
|
||||
trigger(
|
||||
'child2',
|
||||
[
|
||||
transition(
|
||||
'* => go, * => go-again',
|
||||
[
|
||||
style({height: '0px'}),
|
||||
animate('1s', style({height: '100px'})),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
template: `
|
||||
<div [@parent1]="parent1Exp" (@parent1.start)="track($event)"
|
||||
[@parent2]="parent2Exp" (@parent2.start)="track($event)">
|
||||
<div [@child1]="child1Exp" (@child1.start)="track($event)"
|
||||
[@child2]="child2Exp" (@child2.start)="track($event)"></div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
public parent1Exp = '';
|
||||
public parent2Exp = '';
|
||||
public child1Exp = '';
|
||||
public child2Exp = '';
|
||||
public log: string[] = [];
|
||||
|
||||
track(event: any) { this.log.push(`${event.triggerName}-${event.phaseName}`); }
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.log = [];
|
||||
cmp.parent1Exp = 'go';
|
||||
cmp.parent2Exp = 'go';
|
||||
cmp.child1Exp = 'go';
|
||||
cmp.child2Exp = 'go';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(cmp.log).toEqual(
|
||||
['parent1-start', 'parent2-start', 'child1-start', 'child2-start']);
|
||||
|
||||
cmp.parent1Exp = 'go-again';
|
||||
cmp.parent2Exp = 'go-again';
|
||||
cmp.child1Exp = 'go-again';
|
||||
cmp.child2Exp = 'go-again';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
}));
|
||||
|
||||
it('should stretch the starting keyframe of a child animation queries are issued by the parent',
|
||||
() => {
|
||||
@Component({
|
||||
|
@ -40,7 +40,7 @@ let _inFakeAsyncCall = false;
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* {@example testing/ts/fake_async.ts region='basic'}
|
||||
* {@example core/testing/ts/fake_async.ts region='basic'}
|
||||
*
|
||||
* @param fn
|
||||
* @returns The function wrapped to be executed in the fakeAsync zone
|
||||
@ -107,7 +107,7 @@ function _getFakeAsyncZoneSpec(): any {
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* {@example testing/ts/fake_async.ts region='basic'}
|
||||
* {@example core/testing/ts/fake_async.ts region='basic'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
|
@ -75,6 +75,8 @@ const EMAIL_REGEXP =
|
||||
export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a value greater than a number.
|
||||
*`min()` exists only as a function, not as a directive. For example,
|
||||
* `control = new FormControl('', Validators.min(3));`.
|
||||
*/
|
||||
static min(min: number): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
@ -90,6 +92,8 @@ export class Validators {
|
||||
|
||||
/**
|
||||
* Validator that requires controls to have a value less than a number.
|
||||
* `max()` exists only as a function, not as a directive. For example,
|
||||
* `control = new FormControl('', Validators.max(15));`.
|
||||
*/
|
||||
static max(max: number): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
|
@ -109,29 +109,33 @@ class Recognizer {
|
||||
|
||||
if ((route.outlet || PRIMARY_OUTLET) !== outlet) throw new NoMatch();
|
||||
|
||||
let snapshot: ActivatedRouteSnapshot;
|
||||
let consumedSegments: UrlSegment[] = [];
|
||||
let rawSlicedSegments: UrlSegment[] = [];
|
||||
|
||||
if (route.path === '**') {
|
||||
const params = segments.length > 0 ? last(segments) !.parameters : {};
|
||||
const snapshot = new ActivatedRouteSnapshot(
|
||||
snapshot = new ActivatedRouteSnapshot(
|
||||
segments, params, Object.freeze(this.urlTree.queryParams), this.urlTree.fragment !,
|
||||
getData(route), outlet, route.component !, route, getSourceSegmentGroup(rawSegment),
|
||||
getPathIndexShift(rawSegment) + segments.length, getResolve(route));
|
||||
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, [])];
|
||||
} else {
|
||||
const result: MatchResult = match(rawSegment, route, segments);
|
||||
consumedSegments = result.consumedSegments;
|
||||
rawSlicedSegments = segments.slice(result.lastChild);
|
||||
|
||||
snapshot = new ActivatedRouteSnapshot(
|
||||
consumedSegments, result.parameters, Object.freeze(this.urlTree.queryParams),
|
||||
this.urlTree.fragment !, getData(route), outlet, route.component !, route,
|
||||
getSourceSegmentGroup(rawSegment),
|
||||
getPathIndexShift(rawSegment) + consumedSegments.length, getResolve(route));
|
||||
}
|
||||
|
||||
const {consumedSegments, parameters, lastChild} = match(rawSegment, route, segments);
|
||||
const rawSlicedSegments = segments.slice(lastChild);
|
||||
const childConfig = getChildConfig(route);
|
||||
const childConfig: Route[] = getChildConfig(route);
|
||||
|
||||
const {segmentGroup, slicedSegments} =
|
||||
split(rawSegment, consumedSegments, rawSlicedSegments, childConfig);
|
||||
|
||||
const snapshot = new ActivatedRouteSnapshot(
|
||||
consumedSegments, parameters, Object.freeze(this.urlTree.queryParams),
|
||||
this.urlTree.fragment !, getData(route), outlet, route.component !, route,
|
||||
getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + consumedSegments.length,
|
||||
getResolve(route));
|
||||
|
||||
|
||||
if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
|
||||
const children = this.processChildren(childConfig, segmentGroup);
|
||||
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, children)];
|
||||
@ -166,7 +170,13 @@ function getChildConfig(route: Route): Route[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[]) {
|
||||
interface MatchResult {
|
||||
consumedSegments: UrlSegment[];
|
||||
lastChild: number;
|
||||
parameters: any;
|
||||
}
|
||||
|
||||
function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[]): MatchResult {
|
||||
if (route.path === '') {
|
||||
if (route.pathMatch === 'full' && (segmentGroup.hasChildren() || segments.length > 0)) {
|
||||
throw new NoMatch();
|
||||
|
@ -3470,10 +3470,10 @@ describe('Integration', () => {
|
||||
declarations: [LazyLoadedComponent],
|
||||
imports: [RouterModule.forChild([{path: '', component: LazyLoadedComponent}])],
|
||||
})
|
||||
class LoadedModule {
|
||||
class LazyLoadedModule {
|
||||
}
|
||||
|
||||
loader.stubbedModules = {lazy: LoadedModule};
|
||||
loader.stubbedModules = {lazy: LazyLoadedModule};
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{path: '**', loadChildren: 'lazy'}]);
|
||||
@ -3482,6 +3482,7 @@ describe('Integration', () => {
|
||||
advance(fixture);
|
||||
|
||||
expect(location.path()).toEqual('/lazy');
|
||||
expect(fixture.nativeElement).toHaveText('lazy-loaded');
|
||||
})));
|
||||
|
||||
describe('preloading', () => {
|
||||
|
@ -88,6 +88,11 @@ export class Driver implements Debuggable, UpdateSource {
|
||||
|
||||
private lastUpdateCheck: number|null = null;
|
||||
|
||||
/**
|
||||
* Whether there is a check for updates currently scheduled due to navigation.
|
||||
*/
|
||||
private scheduledNavUpdateCheck: boolean = false;
|
||||
|
||||
/**
|
||||
* A scheduler which manages a queue of tasks that need to be executed when the SW is
|
||||
* not doing any other work (not processing any other requests).
|
||||
@ -327,6 +332,15 @@ export class Driver implements Debuggable, UpdateSource {
|
||||
return this.safeFetch(event.request);
|
||||
}
|
||||
|
||||
// On navigation requests, check for new updates.
|
||||
if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {
|
||||
this.scheduledNavUpdateCheck = true;
|
||||
this.idle.schedule('check-updates-on-navigation', async() => {
|
||||
this.scheduledNavUpdateCheck = false;
|
||||
await this.checkForUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
// Decide which version of the app to use to serve this request. This is asynchronous as in
|
||||
// some cases, a record will need to be written to disk about the assignment that is made.
|
||||
const appVersion = await this.assignVersion(event);
|
||||
|
@ -337,6 +337,41 @@ export function main() {
|
||||
serverUpdate.assertNoOtherRequests();
|
||||
});
|
||||
|
||||
async_it('checks for updates on navigation', async() => {
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
server.clearRequests();
|
||||
|
||||
expect(await makeRequest(scope, '/foo.txt', 'default', {
|
||||
mode: 'navigate',
|
||||
})).toEqual('this is foo');
|
||||
|
||||
scope.advance(12000);
|
||||
await driver.idle.empty;
|
||||
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
});
|
||||
|
||||
async_it('does not make concurrent checks for updates on navigation', async() => {
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
server.clearRequests();
|
||||
|
||||
expect(await makeRequest(scope, '/foo.txt', 'default', {
|
||||
mode: 'navigate',
|
||||
})).toEqual('this is foo');
|
||||
|
||||
expect(await makeRequest(scope, '/foo.txt', 'default', {
|
||||
mode: 'navigate',
|
||||
})).toEqual('this is foo');
|
||||
|
||||
scope.advance(12000);
|
||||
await driver.idle.empty;
|
||||
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertNoOtherRequests();
|
||||
});
|
||||
|
||||
async_it('preserves multiple client assignments across restarts', async() => {
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
|
@ -243,22 +243,36 @@ try {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the AngularJS library.
|
||||
* @deprecated Use {@link setAngularJSGlobal} instead.
|
||||
*/
|
||||
export function setAngularLib(ng: any): void {
|
||||
setAngularJSGlobal(ng);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link getAngularJSGlobal} instead.
|
||||
*/
|
||||
export function getAngularLib(): any {
|
||||
return getAngularJSGlobal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the AngularJS global.
|
||||
*
|
||||
* Used when angularjs is loaded lazily, and not available on `window`.
|
||||
* Used when AngularJS is loaded lazily, and not available on `window`.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export function setAngularLib(ng: any): void {
|
||||
export function setAngularJSGlobal(ng: any): void {
|
||||
angular = ng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version of the AngularJS library.
|
||||
* Returns the current AngularJS global.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export function getAngularLib(): any {
|
||||
export function getAngularJSGlobal(): any {
|
||||
return angular;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
* Entry point for all public APIs of this package. allowing
|
||||
* Angular 1 and Angular 2+ to run side by side in the same application.
|
||||
*/
|
||||
export {getAngularLib, setAngularLib} from './src/common/angular1';
|
||||
export {getAngularJSGlobal, getAngularLib, setAngularJSGlobal, setAngularLib} from './src/common/angular1';
|
||||
export {downgradeComponent} from './src/common/downgrade_component';
|
||||
export {downgradeInjectable} from './src/common/downgrade_injectable';
|
||||
export {VERSION} from './src/common/version';
|
||||
@ -20,4 +20,5 @@ export {downgradeModule} from './src/static/downgrade_module';
|
||||
export {UpgradeComponent} from './src/static/upgrade_component';
|
||||
export {UpgradeModule} from './src/static/upgrade_module';
|
||||
|
||||
|
||||
// This file only re-exports content of the `src` folder. Keep it that way.
|
||||
|
@ -12,7 +12,7 @@ import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {$INJECTOR, INJECTOR_KEY} from '@angular/upgrade/src/common/constants';
|
||||
import {UpgradeModule, downgradeInjectable, getAngularLib, setAngularLib} from '@angular/upgrade/static';
|
||||
import {UpgradeModule, downgradeInjectable, getAngularJSGlobal, setAngularJSGlobal} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html} from '../test_helpers';
|
||||
|
||||
@ -103,9 +103,9 @@ export function main() {
|
||||
it('should allow resetting angular at runtime', async(() => {
|
||||
let wrappedBootstrapepedCalled = false;
|
||||
|
||||
const n: any = getAngularLib();
|
||||
const n: any = getAngularJSGlobal();
|
||||
|
||||
setAngularLib({
|
||||
setAngularJSGlobal({
|
||||
bootstrap: (...args: any[]) => {
|
||||
wrappedBootstrapepedCalled = true;
|
||||
n.bootstrap(...args);
|
||||
|
@ -36,7 +36,9 @@ fi
|
||||
|
||||
setEnvVar NODE_VERSION 8.9.1
|
||||
setEnvVar YARN_VERSION 1.0.2
|
||||
setEnvVar CHROMIUM_VERSION 499098 # Chrome 62 linux stable, see https://www.chromium.org/developers/calendar
|
||||
# Pin to a Chromium version that does not cause the aio e2e tests to flake. (See https://github.com/angular/angular/pull/20403.)
|
||||
# Revision 494239 (which was part of Chrome 62.0.3186.0) is the last version that does not cause flakes. (Latest revision checked: 508578)
|
||||
setEnvVar CHROMIUM_VERSION 494239 # Chrome 62 linux stable, see https://www.chromium.org/developers/calendar
|
||||
setEnvVar CHROMEDRIVER_VERSION_ARG "--versions.chrome 2.33"
|
||||
setEnvVar BAZEL_VERSION 0.8.1
|
||||
setEnvVar SAUCE_CONNECT_VERSION 4.4.9
|
||||
|
@ -50,8 +50,7 @@ travisFoldEnd "bower-install"
|
||||
if [[ ${TRAVIS} &&
|
||||
${CI_MODE} == "aio" ||
|
||||
${CI_MODE} == "aio_e2e" ||
|
||||
${CI_MODE} == "aio_tools_test" ||
|
||||
${CI_MODE} == "aio_optional"
|
||||
${CI_MODE} == "aio_tools_test"
|
||||
]]; then
|
||||
# angular.io: Install all yarn dependencies according to angular.io/yarn.lock
|
||||
travisFoldStart "yarn-install.aio"
|
||||
@ -84,8 +83,7 @@ if [[ ${TRAVIS} &&
|
||||
${CI_MODE} == "e2e" ||
|
||||
${CI_MODE} == "e2e_2" ||
|
||||
${CI_MODE} == "aio" ||
|
||||
${CI_MODE} == "aio_e2e" ||
|
||||
${CI_MODE} == "aio_optional"
|
||||
${CI_MODE} == "aio_e2e"
|
||||
]]; then
|
||||
travisFoldStart "install-chromium"
|
||||
(
|
||||
|
@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -u -e -o pipefail
|
||||
|
||||
# Setup environment
|
||||
readonly thisDir=$(cd $(dirname $0); pwd)
|
||||
source ${thisDir}/_travis-fold.sh
|
||||
|
||||
# run in subshell to avoid polluting cwd
|
||||
(
|
||||
cd ${PROJECT_ROOT}/aio
|
||||
# Run e2e tests
|
||||
travisFoldStart "test.aio.e2e"
|
||||
yarn setup
|
||||
yarn e2e
|
||||
travisFoldEnd "test.aio.e2e"
|
||||
|
||||
)
|
@ -31,6 +31,12 @@ source ${thisDir}/_travis-fold.sh
|
||||
travisFoldEnd "test.aio.unit"
|
||||
|
||||
|
||||
# Run e2e tests
|
||||
travisFoldStart "test.aio.e2e"
|
||||
yarn e2e
|
||||
travisFoldEnd "test.aio.e2e"
|
||||
|
||||
|
||||
# Run unit tests for aio/aio-builds-setup
|
||||
travisFoldStart "test.aio.aio-builds-setup"
|
||||
./aio-builds-setup/scripts/test.sh
|
||||
|
@ -46,9 +46,6 @@ case ${CI_MODE} in
|
||||
aio_e2e)
|
||||
${thisDir}/test-aio-e2e.sh
|
||||
;;
|
||||
aio_optional)
|
||||
${thisDir}/test-aio-optional.sh
|
||||
;;
|
||||
bazel)
|
||||
${thisDir}/test-bazel.sh
|
||||
;;
|
||||
|
@ -67,17 +67,20 @@ else
|
||||
fi
|
||||
|
||||
|
||||
CHERRY_PICK_PR="git cherry-pick upstream/pr/$PR_NUMBER~$PR_SHA_COUNT..upstream/pr/$PR_NUMBER"
|
||||
FETCH_PR="git fetch https://github.com/angular/angular.git pull/$PR_NUMBER/head:angular/pr/$PR_NUMBER"
|
||||
CHERRY_PICK_PR="git cherry-pick angular/pr/$PR_NUMBER~$PR_SHA_COUNT..angular/pr/$PR_NUMBER"
|
||||
REWRITE_MESSAGE="git filter-branch -f --msg-filter \"$BASEDIR/utils/github_closes.js $PR_NUMBER\" HEAD~$PR_SHA_COUNT..HEAD"
|
||||
|
||||
echo "======================"
|
||||
echo "GitHub Merge PR Steps"
|
||||
echo "======================"
|
||||
echo " $FETCH_PR"
|
||||
echo " $CHERRY_PICK_PR"
|
||||
echo " $REWRITE_MESSAGE"
|
||||
echo "----------------------"
|
||||
|
||||
echo ">>> Cherry Pick: $CHERRY_PICK_PR"
|
||||
$FETCH_PR
|
||||
$CHERRY_PICK_PR
|
||||
|
||||
echo
|
||||
|
@ -47,3 +47,6 @@ test --experimental_ui
|
||||
|
||||
# Don't be spammy in the continuous integration logs
|
||||
build:ci --noshow_progress
|
||||
|
||||
# Don't run manual tests on CI
|
||||
test:ci --test_tag_filters=-manual
|
||||
|
@ -6,29 +6,40 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// NOTE: This list shold be in sync with aio/tools/transforms/angular-api-package/index.js
|
||||
const entrypoints = [
|
||||
'dist/packages-dist/core/core.d.ts', 'dist/packages-dist/core/testing.d.ts',
|
||||
'dist/packages-dist/common/common.d.ts', 'dist/packages-dist/common/testing.d.ts',
|
||||
'dist/packages-dist/common/http.d.ts', 'dist/packages-dist/common/http/testing.d.ts',
|
||||
'dist/packages-dist/animations/animations.d.ts',
|
||||
'dist/packages-dist/animations/browser.d.ts',
|
||||
'dist/packages-dist/animations/browser/testing.d.ts',
|
||||
'dist/packages-dist/common/common.d.ts',
|
||||
'dist/packages-dist/common/testing.d.ts',
|
||||
'dist/packages-dist/common/http.d.ts',
|
||||
'dist/packages-dist/common/http/testing.d.ts',
|
||||
// The API surface of the compiler is currently unstable - all of the important APIs are exposed
|
||||
// via @angular/core, @angular/platform-browser or @angular/platform-browser-dynamic instead.
|
||||
//'dist/packages-dist/compiler/index.d.ts',
|
||||
//'dist/packages-dist/compiler/testing.d.ts',
|
||||
'dist/packages-dist/upgrade/upgrade.d.ts', 'dist/packages-dist/upgrade/static.d.ts',
|
||||
'dist/packages-dist/core/core.d.ts',
|
||||
'dist/packages-dist/core/testing.d.ts',
|
||||
'dist/packages-dist/forms/forms.d.ts',
|
||||
'dist/packages-dist/http/http.d.ts',
|
||||
'dist/packages-dist/http/testing.d.ts',
|
||||
'dist/packages-dist/platform-browser/platform-browser.d.ts',
|
||||
'dist/packages-dist/platform-browser/animations.d.ts',
|
||||
'dist/packages-dist/platform-browser/testing.d.ts',
|
||||
'dist/packages-dist/platform-browser-dynamic/platform-browser-dynamic.d.ts',
|
||||
'dist/packages-dist/platform-browser-dynamic/testing.d.ts',
|
||||
'dist/packages-dist/platform-webworker/platform-webworker.d.ts',
|
||||
'dist/packages-dist/platform-webworker-dynamic/platform-webworker-dynamic.d.ts',
|
||||
'dist/packages-dist/platform-server/platform-server.d.ts',
|
||||
'dist/packages-dist/platform-server/testing.d.ts', 'dist/packages-dist/http/http.d.ts',
|
||||
'dist/packages-dist/http/testing.d.ts', 'dist/packages-dist/forms/forms.d.ts',
|
||||
'dist/packages-dist/router/router.d.ts', 'dist/packages-dist/animations/animations.d.ts',
|
||||
'dist/packages-dist/platform-server/testing.d.ts',
|
||||
'dist/packages-dist/router/router.d.ts',
|
||||
'dist/packages-dist/router/testing.d.ts',
|
||||
'dist/packages-dist/router/upgrade.d.ts',
|
||||
'dist/packages-dist/service-worker/service-worker.d.ts',
|
||||
'dist/packages-dist/service-worker/config.d.ts', 'dist/packages-dist/animations/browser.d.ts',
|
||||
'dist/packages-dist/animations/browser/testing.d.ts',
|
||||
'dist/packages-dist/platform-browser/animations.d.ts'
|
||||
'dist/packages-dist/service-worker/config.d.ts',
|
||||
'dist/packages-dist/upgrade/upgrade.d.ts',
|
||||
'dist/packages-dist/upgrade/static.d.ts',
|
||||
];
|
||||
|
||||
const publicApiDir = 'tools/public_api_guard';
|
||||
|
16
tools/public_api_guard/router/testing.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/** @stable */
|
||||
export declare class RouterTestingModule {
|
||||
static withRoutes(routes: Routes): ModuleWithProviders;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare function setupTestingRouter(urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], urlHandlingStrategy?: UrlHandlingStrategy): Router;
|
||||
|
||||
/** @stable */
|
||||
export declare class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||
stubbedModules: {
|
||||
[path: string]: any;
|
||||
};
|
||||
constructor(compiler: Compiler);
|
||||
load(path: string): Promise<NgModuleFactory<any>>;
|
||||
}
|
10
tools/public_api_guard/router/upgrade.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/** @experimental */
|
||||
export declare const RouterUpgradeInitializer: {
|
||||
provide: InjectionToken<((compRef: ComponentRef<any>) => void)[]>;
|
||||
multi: boolean;
|
||||
useFactory: (ngUpgrade: UpgradeModule) => () => void;
|
||||
deps: (typeof UpgradeModule)[];
|
||||
};
|
||||
|
||||
/** @experimental */
|
||||
export declare function setUpLocationSync(ngUpgrade: UpgradeModule): void;
|