From 3ec58ecea0342b24c141d602823f4abaa0fe9420 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Tue, 11 Apr 2017 10:18:09 -0700 Subject: [PATCH 0001/1039] feat(aio): hr styling --- aio/src/styles/2-modules/_hr.scss | 9 +++++++++ aio/src/styles/2-modules/_modules-dir.scss | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 aio/src/styles/2-modules/_hr.scss diff --git a/aio/src/styles/2-modules/_hr.scss b/aio/src/styles/2-modules/_hr.scss new file mode 100644 index 0000000000..c45daad44f --- /dev/null +++ b/aio/src/styles/2-modules/_hr.scss @@ -0,0 +1,9 @@ +.hr-margin { + display: block; + height: 1px; + border: 0; + border-top: 1px solid $lightgray; + margin-top: 16px; + margin-bottom: 16px; + padding: 0; +} \ No newline at end of file diff --git a/aio/src/styles/2-modules/_modules-dir.scss b/aio/src/styles/2-modules/_modules-dir.scss index 502ce46fb8..15cf17b1d3 100644 --- a/aio/src/styles/2-modules/_modules-dir.scss +++ b/aio/src/styles/2-modules/_modules-dir.scss @@ -17,4 +17,5 @@ @import 'hero'; @import 'announcement-bar'; @import 'banner'; - @import 'api-list'; \ No newline at end of file + @import 'api-list'; + @import 'hr'; \ No newline at end of file From 2da0f1639f14ea7e0d5bbe7f42dc476acc10e792 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Tue, 11 Apr 2017 10:35:36 -0700 Subject: [PATCH 0002/1039] feat(aio): filtree updates --- aio/src/styles/2-modules/_filetree.scss | 51 +++++++++++++------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/aio/src/styles/2-modules/_filetree.scss b/aio/src/styles/2-modules/_filetree.scss index 1208c59ff1..f619a284ff 100644 --- a/aio/src/styles/2-modules/_filetree.scss +++ b/aio/src/styles/2-modules/_filetree.scss @@ -1,37 +1,38 @@ -.filetree { +aio-filetree { background: $offwhite; box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, .2); border: 4px solid $lightgray; margin: 0 0 5px 0; padding: 10px; + display: flex; + flex-direction: column; + height: 100%; +} - .file { - display: block; - font-family: $main-font; - line-height: 32px; - color: $darkgray; - } - - .children { - padding-left: 5px; +aio-folder { + padding-left: 24px; position: relative; overflow: hidden; + display: flex; + flex-direction: column; +} - .file { - position: relative; +aio-file { + display: block; + position: relative; + line-height: 32px; + padding-left: 5px; - &:before { - content: ''; - left: -18px; - bottom: 16px; - width: 5px; - height: 9999px; - position: absolute; - border-width: 0 0 1px 1px; - border-style: solid; - border-color: $lightgray; - border-radius: 0 0 0 3px; - } + &:before { + content: ''; + left: -18px; + bottom: 16px; + width: 5px; + height: 9999px; + position: absolute; + border-width: 0 0 1px 1px; + border-style: solid; + border-color: $lightgray; + border-radius: 0 0 0 3px; } - } } \ No newline at end of file From ad9a3a2d3bbb1e99cc7becf2f79469c614a9884e Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Tue, 11 Apr 2017 13:24:42 -0700 Subject: [PATCH 0003/1039] features page and code/table fixes --- aio/content/marketing/features.html | 111 ++++++++++++++---------- aio/src/styles/2-modules/_features.scss | 13 +-- aio/src/styles/2-modules/_table.scss | 12 ++- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/aio/content/marketing/features.html b/aio/content/marketing/features.html index c38935c7a4..ecc9235918 100755 --- a/aio/content/marketing/features.html +++ b/aio/content/marketing/features.html @@ -1,64 +1,87 @@ -

Features & Benefits

-
-
+

Features & Benefits

Cross Platform

-

Progressive web apps

+ +
+
Progressive Web Apps

Use modern web platform capabilities to deliver app-like experiences. - High performance, offline, and zero-step installation.

-

Native

-

Build native mobile apps with strategies from Ionic Framework, - NativeScript, and React Native.

-

Desktop

-

Create desktop-installed apps across Mac, Windows, and Linux using - the same Angular methods you've learned for the web plus the ability to access native OS - APIs.

+ High performance, offline, and zero-step installation.

+
+ +
+
Native
+

Build native mobile apps with strategies from Ionic Framework, NativeScript, and React Native.

+
+ +
+
Desktop
+

Create desktop-installed apps across Mac, Windows, and Linux using the same Angular methods you've learned for the web plus the ability to access native OS APIs.

+
+

Speed and Performance

-

Code generation

-

Angular turns your templates into code that's highly optimized for - today's JavaScript virtual machines, giving you all the benefits of hand-written code - with the productivity of a framework.

-

Universal

-

Serve the first view of your application on node.js, .NET, PHP, and - other servers for near-instant rendering in just HTML and CSS. Also paves the way for - sites that optimize for SEO.

-

Code splitting

-

Angular apps load quickly with the new Component Router, which - delivers automatic code-splitting so users only load code required to render the view - they request.

+ +
+
Code Generation
+

Angular turns your templates into code that's highly optimized for today's JavaScript virtual machines, giving you all the benefits of hand-written code with the productivity of a framework.

+
+ +
+
Universal
+

Serve the first view of your application on node.js, .NET, PHP, and other servers for near-instant rendering in just HTML and CSS. Also paves the way for sites that optimize for SEO.

+
+ +
+
Code Splitting
+

Angular apps load quickly with the new Component Router, which delivers automatic code-splitting so users only load code required to render the view they request.

+
+
+

Productivity

-

Templates

+ +
+
Templates

Quickly create UI views with simple and powerful template syntax.

-

Angular CLI

-

Command line tools: start building fast, add components and tests, - then instantly deploy.

-

IDEs

-

Get intelligent code completion, instant errors, and other feedback - in popular editors and IDEs.

+ +
+
Angular CLI
+

Command line tools: start building fast, add components and tests, then instantly deploy.

+
+ +
+
IDEs
+

Get intelligent code completion, instant errors, and other feedback in popular editors and IDEs.

+
+

Full Development Story

-

Testing

-

With Karma for unit tests, you can know if you've broken things every - time you save. And Protractor makes your scenario tests run faster and in a stable - manner.

-

Animation

-

Create high-performance, complex choreographies and animation - timelines with very little code through Angular's intuitive API.

-

Accessibility

-

Create accessible applications with ARIA-enabled components, - developer guides, and built-in a11y test infrastructure.

+ +
+
Testing
+

With Karma for unit tests, you can know if you've broken things every time you save. And Protractor makes your scenario tests run faster and in a stable manner.

+
+ +
+
Animation
+

Create high-performance, complex choreographies and animation timelines with very little code through Angular's intuitive API.

+
+ +
+
Accessibility
+

Create accessible applications with ARIA-enabled components, developer guides, and built-in a11y test infrastructure.

+
+
- + + +
diff --git a/aio/src/styles/2-modules/_features.scss b/aio/src/styles/2-modules/_features.scss index a9b27a2a9f..15398725c6 100644 --- a/aio/src/styles/2-modules/_features.scss +++ b/aio/src/styles/2-modules/_features.scss @@ -1,17 +1,12 @@ .feature-row { display: flex; - justify-content: space-between; + flex-direction: row; flex-wrap: wrap; + margin: 24px 0 0; @media (max-width: 600px) { flex-direction: column; } - - h2.text-headline { - font-size: 28px; - margin-top: 10px; - color: #37474F; - } } .features-desc { @@ -19,6 +14,6 @@ } .feature { - max-width: 350px; - margin-bottom: $unit * 4; + max-width: 300px; + margin: 0 24px 32px 0; } diff --git a/aio/src/styles/2-modules/_table.scss b/aio/src/styles/2-modules/_table.scss index 2ab3fe42c2..b68c2698f3 100644 --- a/aio/src/styles/2-modules/_table.scss +++ b/aio/src/styles/2-modules/_table.scss @@ -9,13 +9,23 @@ table { } thead { + display: table-header-group; + vertical-align: middle; + border-color: inherit; + + tr { + display: table-row; + vertical-align: inherit; + border-color: inherit; + } + th { background: $offwhite; border-bottom: 1px solid $lightgray; color: $darkgray; font-size: 12px; font-weight: 500; - padding: 0 24px; + padding: 8px 32px; text-align: left; text-transform: uppercase; } From 498bd64d9c64918651f106ebcd4254ed79c82c8b Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Tue, 11 Apr 2017 18:50:44 -0700 Subject: [PATCH 0004/1039] code style updates --- aio/src/styles/2-modules/_code.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aio/src/styles/2-modules/_code.scss b/aio/src/styles/2-modules/_code.scss index f561d08c7a..bef542fe44 100644 --- a/aio/src/styles/2-modules/_code.scss +++ b/aio/src/styles/2-modules/_code.scss @@ -54,7 +54,9 @@ code-tabs mat-tab-body-content .fadeIn { aio-code pre { display: flex; padding: 0 48px 0 0; + min-height: 32px; white-space: pre-wrap; + align-items: center; code span, code ol li { line-height: 24px; From a6545ddd4d4670e34c1542f244497c116f92f227 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Tue, 11 Apr 2017 19:02:29 -0700 Subject: [PATCH 0005/1039] filetree and subsection edits --- aio/src/styles/2-modules/_filetree.scss | 57 +++++++++++------------ aio/src/styles/2-modules/_subsection.scss | 8 +++- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/aio/src/styles/2-modules/_filetree.scss b/aio/src/styles/2-modules/_filetree.scss index f619a284ff..94dc4a4fee 100644 --- a/aio/src/styles/2-modules/_filetree.scss +++ b/aio/src/styles/2-modules/_filetree.scss @@ -1,38 +1,37 @@ -aio-filetree { +.filetree { background: $offwhite; - box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, .2); - border: 4px solid $lightgray; - margin: 0 0 5px 0; - padding: 10px; - display: flex; - flex-direction: column; - height: 100%; -} + box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12); + border: 2px solid $lightgray; + margin: 0 0 24px 0; + padding: 24px; -aio-folder { + .file { + display: block; + font-family: $main-font; + line-height: 32px; + color: $lightgray; + } + + .children { padding-left: 24px; position: relative; overflow: hidden; - display: flex; - flex-direction: column; -} -aio-file { - display: block; - position: relative; - line-height: 32px; - padding-left: 5px; + .file { + position: relative; - &:before { - content: ''; - left: -18px; - bottom: 16px; - width: 5px; - height: 9999px; - position: absolute; - border-width: 0 0 1px 1px; - border-style: solid; - border-color: $lightgray; - border-radius: 0 0 0 3px; + &:before { + content: ''; + left: -18px; + bottom: 16px; + width: 16px; + height: 9999px; + position: absolute; + border-width: 0 0 1px 1px; + border-style: solid; + border-color: $lightgray; + border-radius: 0 0 0 3px; + } } + } } \ No newline at end of file diff --git a/aio/src/styles/2-modules/_subsection.scss b/aio/src/styles/2-modules/_subsection.scss index f3ee7e6965..eb81602d8a 100644 --- a/aio/src/styles/2-modules/_subsection.scss +++ b/aio/src/styles/2-modules/_subsection.scss @@ -1,7 +1,11 @@ .l-sub-section { color: $darkgray; background-color: $lightgray; - border-left: 10px solid $mediumgray; + border-left: 8px solid $mediumgray; padding: 16px; - margin-bottom: 10px; + margin-bottom: 8px; + + h3 { + margin: 8px 0 0; + } } \ No newline at end of file From 7d69a91bfeb24404713282f05294462defd2e7c4 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Tue, 11 Apr 2017 19:21:56 -0700 Subject: [PATCH 0006/1039] table updates, live example add, shield logo res --- aio/src/app/app.component.ts | 2 +- aio/src/styles/2-modules/_live-example.scss | 3 +++ aio/src/styles/2-modules/_modules-dir.scss | 3 ++- aio/src/styles/2-modules/_table.scss | 7 ++++--- 4 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 aio/src/styles/2-modules/_live-example.scss diff --git a/aio/src/app/app.component.ts b/aio/src/app/app.component.ts index a0a7d0ac75..3aae674c89 100644 --- a/aio/src/app/app.component.ts +++ b/aio/src/app/app.component.ts @@ -38,7 +38,7 @@ export class AppComponent implements OnInit { get homeImageUrl() { return this.isSideBySide ? 'assets/images/logos/standard/logo-nav@2x.png' : - 'assets/images/logos/standard/logo-nav-small.png'; + 'assets/images/logos/standard/shield-large@2x.png'; } get isOpened() { return this.isSideBySide && this.isSideNavDoc; } get mode() { return this.isSideBySide ? 'side' : 'over'; } diff --git a/aio/src/styles/2-modules/_live-example.scss b/aio/src/styles/2-modules/_live-example.scss new file mode 100644 index 0000000000..e66948eaa1 --- /dev/null +++ b/aio/src/styles/2-modules/_live-example.scss @@ -0,0 +1,3 @@ +live-example > div:first-child { + max-width: 600px; +} \ No newline at end of file diff --git a/aio/src/styles/2-modules/_modules-dir.scss b/aio/src/styles/2-modules/_modules-dir.scss index 15cf17b1d3..a8370d2a6b 100644 --- a/aio/src/styles/2-modules/_modules-dir.scss +++ b/aio/src/styles/2-modules/_modules-dir.scss @@ -18,4 +18,5 @@ @import 'announcement-bar'; @import 'banner'; @import 'api-list'; - @import 'hr'; \ No newline at end of file + @import 'hr'; + @import 'live-example'; \ No newline at end of file diff --git a/aio/src/styles/2-modules/_table.scss b/aio/src/styles/2-modules/_table.scss index b68c2698f3..78ea0e6617 100644 --- a/aio/src/styles/2-modules/_table.scss +++ b/aio/src/styles/2-modules/_table.scss @@ -20,7 +20,7 @@ table { } th { - background: $offwhite; + background: rgba($lightgray, 0.2); border-bottom: 1px solid $lightgray; color: $darkgray; font-size: 12px; @@ -28,6 +28,7 @@ table { padding: 8px 32px; text-align: left; text-transform: uppercase; + line-height: 28px; } } @@ -35,7 +36,7 @@ table { th, td { border-bottom: 1px solid $lightgray; - padding: 16px 24px; + padding: 16px 32px; text-align: left; line-height: 24px; @@ -64,7 +65,7 @@ table { } th { - background: $offwhite; + background: rgba($lightgray, 0.2); border-right: 1px solid $lightgray; font-weight: 600; max-width: 100px; From cc1ed77dd85afb03a381932cfaa0baf5feb22eeb Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Tue, 11 Apr 2017 19:37:03 -0700 Subject: [PATCH 0007/1039] consolidated and moved api-list scss --- .../app/embedded/api/api-list.component.scss | 477 ------------------ .../app/embedded/api/api-list.component.ts | 3 +- aio/src/styles/2-modules/_api-list.scss | 431 ++++++++++++++++ 3 files changed, 432 insertions(+), 479 deletions(-) delete mode 100644 aio/src/app/embedded/api/api-list.component.scss diff --git a/aio/src/app/embedded/api/api-list.component.scss b/aio/src/app/embedded/api/api-list.component.scss deleted file mode 100644 index 8ab4b96292..0000000000 --- a/aio/src/app/embedded/api/api-list.component.scss +++ /dev/null @@ -1,477 +0,0 @@ -/* HACKED FROM aio v.1 */ - -// TODO:refactor out those parts that should be shared SASS files and import them here - -/* -* Metrics -* -* Metrics based on material design 8pt unit -*/ - -$unit: 8px; -$phone-breakpoint: 480px; -$tablet-breakpoint: 800px; - -/* -* Layer Stacking -* -* The approved range that can be used for layering (z-indexes) -*/ - -$layer-1: 1; -$layer-2: 2; -$layer-3: 3; -$layer-4: 4; -$layer-5: 5; - -/* - * Colors from original _colors.scss - */ -$white: #FFFFFF; -$black: #000000; - -$amber-700: #FFA000; -$blue-400: #42A5F5; -$blue-500: #2196F3; -$blue-600: #1E88E5; -$blue-800: #1565C0; -$blue-grey-50: #ECEFF1; -$blue-grey-100: #CFD8DC; -$blue-grey-500: #607D8B; -$blue-grey-600: #546E7A; -$green-500: #4CAF50; -$green-800: #2E7D32; -$light-green-600: #7CB342; -$pink-600: #D81B60; -$purple-600: #8E24AA; -$teal-500: #009688; - -$lightgrey: #F5F6F7; - - -/* -* Layout -*/ -.docs-content { - position: relative; -} - -.l-content-small { - padding: 16px; - max-width: 1100px; - margin: 0; - - @media handheld and (max-width: $phone-breakpoint), - screen and (max-device-width: $phone-breakpoint), - screen and (max-width: $tablet-breakpoint) { - padding: 0; - padding-top: ($unit * 3); - } -} - -/* -* SEARCH BAR -*/ - -.form-search { - position: relative; - - input { - box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12); - box-sizing: border-box; - border: 1px solid $white; - color: $blue-600; - font-size: 16px; - height: $unit * 4; - line-height: $unit * 4; - outline: none; - padding: 0 ($unit *2) 0 ($unit * 4); - transition: all .2s; - - // PLACEHOLDER TEXT - &::-webkit-input-placeholder { /* Chrome/Opera/Safari */ - color: $blue-grey-100; - font-size: 14px; - } - &::-moz-placeholder { /* Firefox 19+ */ - color: $blue-grey-100; - font-size: 14px; - } - &:-ms-input-placeholder { /* IE 10+ */ - color: $blue-grey-100; - font-size: 14px; - } - &:-moz-placeholder { /* Firefox 18- */ - color: $blue-grey-100; - font-size: 14px; - } - - &:focus { - border: 1px solid $blue-400; - box-shadow: 0 2px 2px rgba($blue-400, 0.24), 0 0 2px rgba($blue-400, 0.12); - } - } - - .material-icons { - color: $blue-grey-100; - font-size: 20px; - left: $unit; - position: absolute; - top: 6px; - z-index: $layer-1; - } -} - -/* -* Select Menu -*/ - -$form-select-width: 200px; - -.form-select-menu { - position: relative; -} - -.form-select-button { - background: $white; - box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12); - box-sizing: border-box; - border: 1px solid $white; - color: $blue-grey-600; - font-size: 12px; - font-weight: 400; - height: $unit * 4; - line-height: $unit * 4; - outline: none; - padding: 0 ($unit * 2); - text-align: left; - width: $form-select-width - ($unit * 2); - - strong { - font-weight: 600; - margin-right: $unit; - text-transform: uppercase; - } - - &.has-symbol { - .symbol { - margin-right: $unit; - } - } -} - -.form-select-dropdown { - background: $white; - box-shadow: 0 16px 16px rgba($black, 0.24), 0 0 16px rgba($black, 0.12); - border-radius: 4px; - left: -$unit; - list-style-type: none; - margin: 0; - padding: $unit 0; - position: absolute; - top: -$unit; - width: 200px; - z-index: $layer-2; - - li { - cursor: pointer; - font-size: 14px; - line-height: $unit * 4; - margin: 0; - padding: 0 ($unit * 2) 0 ($unit * 5); - position: relative; - transition: all .2s; - - &:hover { - background: $blue-grey-50; - color: $blue-500; - } - - &.selected { - background-color: $blue-grey-100; - } - - .symbol { - left: $unit * 2; - position: absolute; - top: $unit; - z-index: $layer-5; - } - } -} - -/* -* API Symbols -* -*/ - - -/* -* Variables -*/ - - -$api-symbols: ( - all: ( - content: ' ', - background: $white - ), - decorator: ( - content: '@', - background: $blue-800 - ), - directive: ( - content: 'D', - background: $pink-600 - ), - pipe: ( - content: 'P', - background: $blue-grey-600 - ), - class: ( - content: 'C', - background: $blue-500 - ), - interface: ( - content: 'I', - background: $teal-500 - ), - function: ( - content: 'F', - background: $green-500 - ), - enum: ( - content: 'E', - background: $amber-700 - ), - const: ( - content: 'K', - background: $purple-600 - ), - type-alias: ( - content: 'T', - background: $light-green-600 - ) -); - - -/* -* Symbol Class -*/ - -.symbol { - border-radius: 2px; - box-shadow: 0 1px 2px rgba($black, .24); - color: $white; - display: inline-block; - font-size: 10px; - font-weight: 600; - line-height: $unit * 2; - text-align: center; - width: $unit * 2; - - // SYMBOL TYPES - @each $name, $symbol in $api-symbols { - &.#{$name} { - background: map-get($symbol, background); - - &:before { - content: map-get($symbol, content); - } - } - } -} - -/* -* API Home Page -* -*/ - -/* -* API Filter Menu -* -*/ - -.api-filter { - .form-select-menu { - float: left; - } - - .form-search { - float: left; - } -} - - -/* -* API Class List -* -*/ - -.docs-content .api-list { - list-style: none; - margin: 0 0 ($unit * 6) 0; - padding: 0; - overflow: hidden; - - li { - font-size: 14px; - margin: 0 0 ($unit * 2) 0; - line-height: 14px; - padding: 0; - float: left; - width: 33%; - min-width: 220px; - text-overflow: ellipsis; - white-space: nowrap; - - .symbol { - margin-right: $unit; - } - a { - color: $blue-grey-600; - display: inline-block; - line-height: $unit * 2; - padding: 0 ($unit * 2) 0 0; - text-decoration: none; - transition: all .3s; - - &:hover { - background: $blue-grey-50; - color: $blue-500; - } - } - } -} - -.docs-content .h2-api-docs, -.docs-content .h2-api-docs:first-of-type { - font-size: 18px; - line-height: 24px; - margin-top: 0; -} - -.code-links { - a { - code, .api-doc-code { - color: #1E88E5 !important; - } - } -} - -.openParens { - margin-top: 15px; -} - -.endParens { - margin-bottom: 20px !important; -} - -p { - - &.selector { - margin: 0; - } - - &.location-badge { - margin: 0 0 16px 16px !important; - } - - .api-doc-code { - border-bottom: 0; - - :hover { - border-bottom: none; - } - } -} - -.row-margin { - margin-bottom: 36px; - h2 { - line-height: 28px; - } -} - -.code-margin { - margin-bottom: $unit; -} - -.hr-margin { - display: block; - height: 1px; - border: 0; - border-top: 1px solid $lightgrey; - margin-top: 15px; - margin-bottom: 20px; - padding: 0; -} - -.no-bg { - background: none; - padding: 0; -} - -.no-bg-with-indent { - padding-top: 0; - padding-bottom: 0; - padding-left: 16px; - margin-top: 6px; - margin-bottom: 0; - background: none; -} - -.code-background { - padding: 0 5px 0; - - span.pln { - color: #1E88E5 !important; - } -} - -.code-anchor { - cursor: pointer; - text-decoration: none; - - // Override highlight.js - .kwd { - color: #1E88E5 !important; - } - - &:hover { - text-decoration: underline; - } -} - -.api-doc-code { - font-size: 14px; - color: #1a2326; - - // the last .pln (white space) creates additional spacing between sections of the api doc. Remove it. - &.no-pln { - .pln:last-child { - display: none; - } - } -} - - -@media screen and (max-width: 600px) { - .docs-content { - // Overrides display flex from angular material. - // This was added because Safari doesn't play nice with layout="column". - // Look of API doc in Chrome and Firefox remains the same, and is fixed for Safari. - .layout-xs-column { - display: block !important; - } - } - - .api-doc-code { - font-size: 12px; - } - - p.location-badge { - position: relative; - font-size: 11px; - } -} diff --git a/aio/src/app/embedded/api/api-list.component.ts b/aio/src/app/embedded/api/api-list.component.ts index 9f1144de67..c252699fc2 100644 --- a/aio/src/app/embedded/api/api-list.component.ts +++ b/aio/src/app/embedded/api/api-list.component.ts @@ -28,8 +28,7 @@ class SearchCriteria { @Component({ selector: 'aio-api-list', - templateUrl: './api-list.component.html', - styleUrls: ['./api-list.component.scss'] + templateUrl: './api-list.component.html' }) export class ApiListComponent implements OnInit { diff --git a/aio/src/styles/2-modules/_api-list.scss b/aio/src/styles/2-modules/_api-list.scss index d927140faa..f42f0d9cbe 100644 --- a/aio/src/styles/2-modules/_api-list.scss +++ b/aio/src/styles/2-modules/_api-list.scss @@ -1,3 +1,4 @@ +/* API LIST STYLES */ aio-api-list { div.form-search i.material-icons { @@ -20,4 +21,434 @@ aio-api-list > div { > div { margin: 8px; } +} + +$phone-breakpoint: 480px; +$tablet-breakpoint: 800px; + +$layer-1: 1; +$layer-2: 2; +$layer-3: 3; +$layer-4: 4; +$layer-5: 5; + + +$amber-700: #FFA000; +$blue-400: #42A5F5; +$blue-500: #2196F3; +$blue-600: #1E88E5; +$blue-800: #1565C0; +$blue-grey-50: #ECEFF1; +$blue-grey-100: #CFD8DC; +$blue-grey-500: #607D8B; +$blue-grey-600: #546E7A; +$green-500: #4CAF50; +$green-800: #2E7D32; +$light-green-600: #7CB342; +$pink-600: #D81B60; +$purple-600: #8E24AA; +$teal-500: #009688; + +$lightgrey: #F5F6F7; + + +/* LAYOUT */ + +.docs-content { + position: relative; +} + +.l-content-small { + padding: 16px; + max-width: 1100px; + margin: 0; + + @media handheld and (max-width: $phone-breakpoint), + screen and (max-device-width: $phone-breakpoint), + screen and (max-width: $tablet-breakpoint) { + padding: 0; + padding-top: ($unit * 3); + } +} + +/* SEARCH BAR */ + +.form-search { + position: relative; + + input { + box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12); + box-sizing: border-box; + border: 1px solid $white; + color: $blue-600; + font-size: 16px; + height: 32px; + line-height: 32px; + outline: none; + padding: 0 16px 0 32px; + transition: all .2s; + + // PLACEHOLDER TEXT + &::-webkit-input-placeholder { /* Chrome/Opera/Safari */ + color: $blue-grey-100; + font-size: 14px; + } + &::-moz-placeholder { /* Firefox 19+ */ + color: $blue-grey-100; + font-size: 14px; + } + &:-ms-input-placeholder { /* IE 10+ */ + color: $blue-grey-100; + font-size: 14px; + } + &:-moz-placeholder { /* Firefox 18- */ + color: $blue-grey-100; + font-size: 14px; + } + + &:focus { + border: 1px solid $blue-400; + box-shadow: 0 2px 2px rgba($blue-400, 0.24), 0 0 2px rgba($blue-400, 0.12); + } + } + + .material-icons { + color: $blue-grey-100; + font-size: 20px; + left: $unit; + position: absolute; + top: 6px; + z-index: $layer-1; + } +} + +/* SELECT MENU */ + +$form-select-width: 200px; + +.form-select-menu { + position: relative; +} + +.form-select-button { + background: $white; + box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12); + box-sizing: border-box; + border: 1px solid $white; + color: $blue-grey-600; + font-size: 12px; + font-weight: 400; + height: 32px; + line-height: 32px; + outline: none; + padding: 0 16px; + text-align: left; + width: $form-select-width - 16px; + + strong { + font-weight: 600; + margin-right: 8px; + text-transform: uppercase; + } + + &.has-symbol { + .symbol { + margin-right: 8px; + } + } +} + +.form-select-dropdown { + background: $white; + box-shadow: 0 16px 16px rgba($black, 0.24), 0 0 16px rgba($black, 0.12); + border-radius: 4px; + left: -8px; + list-style-type: none; + margin: 0; + padding: 8px 0; + position: absolute; + top: -8px; + width: 200px; + z-index: $layer-2; + + li { + cursor: pointer; + font-size: 14px; + line-height: 32px; + margin: 0; + padding: 0 16px 0 40px; + position: relative; + transition: all .2s; + + &:hover { + background: $blue-grey-50; + color: $blue-500; + } + + &.selected { + background-color: $blue-grey-100; + } + + .symbol { + left: 16px; + position: absolute; + top: 8px; + z-index: $layer-5; + } + } +} + +/* API SYMBOLS */ + +/* VARIABLES */ + +$api-symbols: ( + all: ( + content: ' ', + background: $white + ), + decorator: ( + content: '@', + background: $blue-800 + ), + directive: ( + content: 'D', + background: $pink-600 + ), + pipe: ( + content: 'P', + background: $blue-grey-600 + ), + class: ( + content: 'C', + background: $blue-500 + ), + interface: ( + content: 'I', + background: $teal-500 + ), + function: ( + content: 'F', + background: $green-500 + ), + enum: ( + content: 'E', + background: $amber-700 + ), + const: ( + content: 'K', + background: $purple-600 + ), + type-alias: ( + content: 'T', + background: $light-green-600 + ) +); + +/* SYMBOL CLASS */ + +.symbol { + border-radius: 2px; + box-shadow: 0 1px 2px rgba($black, .24); + color: $white; + display: inline-block; + font-size: 10px; + font-weight: 600; + line-height: 16px; + text-align: center; + width: 16px; + + // SYMBOL TYPES + @each $name, $symbol in $api-symbols { + &.#{$name} { + background: map-get($symbol, background); + + &:before { + content: map-get($symbol, content); + } + } + } +} + +/* API HOMEE PAGE */ + +/* API FILTER MENU */ + +.api-filter { + .form-select-menu { + float: left; + } + + .form-search { + float: left; + } +} + +/* API CLASS LIST */ + +.docs-content .api-list { + list-style: none; + margin: 0 0 48px 0; + padding: 0; + overflow: hidden; + + li { + font-size: 14px; + margin: 0 0 16px 0; + line-height: 14px; + padding: 0; + float: left; + width: 33%; + min-width: 220px; + text-overflow: ellipsis; + white-space: nowrap; + + .symbol { + margin-right: $unit; + } + a { + color: $blue-grey-600; + display: inline-block; + line-height: 16px; + padding: 0 16px 0 0; + text-decoration: none; + transition: all .3s; + + &:hover { + background: $blue-grey-50; + color: $blue-500; + } + } + } +} + +.docs-content .h2-api-docs, +.docs-content .h2-api-docs:first-of-type { + font-size: 18px; + line-height: 24px; + margin-top: 0; +} + +.code-links { + a { + code, .api-doc-code { + color: #1E88E5 !important; + } + } +} + +.openParens { + margin-top: 15px; +} + +.endParens { + margin-bottom: 20px !important; +} + +p { + + &.selector { + margin: 0; + } + + &.location-badge { + margin: 0 0 16px 16px !important; + } + + .api-doc-code { + border-bottom: 0; + + :hover { + border-bottom: none; + } + } +} + +.row-margin { + margin-bottom: 36px; + h2 { + line-height: 28px; + } +} + +.code-margin { + margin-bottom: $unit; +} + +.hr-margin { + display: block; + height: 1px; + border: 0; + border-top: 1px solid $lightgrey; + margin-top: 15px; + margin-bottom: 20px; + padding: 0; +} + +.no-bg { + background: none; + padding: 0; +} + +.no-bg-with-indent { + padding-top: 0; + padding-bottom: 0; + padding-left: 16px; + margin-top: 6px; + margin-bottom: 0; + background: none; +} + +.code-background { + padding: 0 5px 0; + + span.pln { + color: #1E88E5 !important; + } +} + +.code-anchor { + cursor: pointer; + text-decoration: none; + + // Override highlight.js + .kwd { + color: #1E88E5 !important; + } + + &:hover { + text-decoration: underline; + } +} + +.api-doc-code { + font-size: 14px; + color: #1a2326; + + // the last .pln (white space) creates additional spacing between sections of the api doc. Remove it. + &.no-pln { + .pln:last-child { + display: none; + } + } +} + +@media screen and (max-width: 600px) { + .docs-content { + // Overrides display flex from angular material. + // This was added because Safari doesn't play nice with layout="column". + // Look of API doc in Chrome and Firefox remains the same, and is fixed for Safari. + .layout-xs-column { + display: block !important; + } + } + + .api-doc-code { + font-size: 12px; + } + + p.location-badge { + position: relative; + font-size: 11px; + } } \ No newline at end of file From 909264feb5e8e36b2792127d303e4b9a25d07b99 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Tue, 11 Apr 2017 19:46:19 -0700 Subject: [PATCH 0008/1039] hr update --- aio/content/marketing/events.html | 2 +- aio/src/styles/2-modules/_api-list.scss | 5 +++++ aio/src/styles/2-modules/_buttons.scss | 2 ++ aio/src/styles/2-modules/_features.scss | 1 - aio/src/styles/2-modules/_hr.scss | 6 ++++++ aio/src/styles/2-modules/_table.scss | 2 -- 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/aio/content/marketing/events.html b/aio/content/marketing/events.html index 8712691448..0214c01d4c 100755 --- a/aio/content/marketing/events.html +++ b/aio/content/marketing/events.html @@ -1,4 +1,4 @@ -

Events

+

Events

Where we'll be presenting:

diff --git a/aio/src/styles/2-modules/_api-list.scss b/aio/src/styles/2-modules/_api-list.scss index f42f0d9cbe..54f172e283 100644 --- a/aio/src/styles/2-modules/_api-list.scss +++ b/aio/src/styles/2-modules/_api-list.scss @@ -9,6 +9,11 @@ aio-api-list { .form-search input { width: 182px; } + + .banner { + border: 1px solid rgba($lightgray, 0.5); + border-radius: 4px; + } } aio-api-list > div { diff --git a/aio/src/styles/2-modules/_buttons.scss b/aio/src/styles/2-modules/_buttons.scss index fd3ed6c003..36bd942ad1 100644 --- a/aio/src/styles/2-modules/_buttons.scss +++ b/aio/src/styles/2-modules/_buttons.scss @@ -66,10 +66,12 @@ a.button.md-button { background: $blue url('assets/images/logos/angular/angular_whiteTransparent.svg') 24px 13px no-repeat; color: rgba($white, .87); padding-left: 54px; + background-size: 22px 22px; @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) { background: $blue url('assets/images/logos/angular/angular_whiteTransparent.svg') 24px 13px no-repeat; background-size: 22px 22px; + } } } diff --git a/aio/src/styles/2-modules/_features.scss b/aio/src/styles/2-modules/_features.scss index 15398725c6..e09a8e4a22 100644 --- a/aio/src/styles/2-modules/_features.scss +++ b/aio/src/styles/2-modules/_features.scss @@ -1,6 +1,5 @@ .feature-row { display: flex; - flex-direction: row; flex-wrap: wrap; margin: 24px 0 0; diff --git a/aio/src/styles/2-modules/_hr.scss b/aio/src/styles/2-modules/_hr.scss index c45daad44f..c5de8d54c6 100644 --- a/aio/src/styles/2-modules/_hr.scss +++ b/aio/src/styles/2-modules/_hr.scss @@ -1,3 +1,9 @@ +hr { + border: none; + background: $lightgray; + height: 1px; +} + .hr-margin { display: block; height: 1px; diff --git a/aio/src/styles/2-modules/_table.scss b/aio/src/styles/2-modules/_table.scss index 78ea0e6617..31e2a309b6 100644 --- a/aio/src/styles/2-modules/_table.scss +++ b/aio/src/styles/2-modules/_table.scss @@ -9,12 +9,10 @@ table { } thead { - display: table-header-group; vertical-align: middle; border-color: inherit; tr { - display: table-row; vertical-align: inherit; border-color: inherit; } From 8cfa58715cf882af2fd1e18344e8f7d240029cba Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Mon, 10 Apr 2017 12:44:54 -0700 Subject: [PATCH 0009/1039] feat(aio): hide side nav in mobile mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don’t show the side nav in mobile (not side-by-side) view when arriving or navigating. Only show it by request. The side nav should continue to appear in wide mode (side-by-side) when navigating from a marketing page to a guide page. --- aio/src/app/app.component.spec.ts | 67 +++++++++++++++++++++++++------ aio/src/app/app.component.ts | 4 +- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index 01cfb6a1bd..587dc46c36 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -71,35 +71,76 @@ describe('AppComponent', () => { }); }); - describe('shows/hide SideNav based on doc\'s navigation view', () => { + describe('SideNav when side-by-side', () => { + let hamburger: HTMLButtonElement; + let locationService: MockLocationService; + let sidenav: Element; + + beforeEach(() => { + component.onResize(1033); // side-by-side + locationService = fixture.debugElement.injector.get(LocationService) as any; + hamburger = fixture.debugElement.query(By.css('.hamburger')).nativeElement; + sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; + }); + + it('should open when nav to a guide page (guide/pipes)', () => { + locationService.urlSubject.next('guide/pipes'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-open/); + }); + + it('should open when nav to an api page', () => { + locationService.urlSubject.next('api/a/b/c/d'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-open/); + }); + + it('should be closed when nav to a marketing page (features)', () => { + locationService.urlSubject.next('features'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + describe('when manually closed', () => { + + beforeEach(() => { + locationService.urlSubject.next('guide/pipes'); + fixture.detectChanges(); + hamburger.click(); + fixture.detectChanges(); + }); + + it('should be closed', () => { + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + }); + }); + + describe('SideNav when NOT side-by-side (mobile)', () => { + let sidenav: Element; let locationService: MockLocationService; beforeEach(() => { + component.onResize(1000); // NOT side-by-side locationService = fixture.debugElement.injector.get(LocationService) as any; - component.onResize(1000); // side-by-side + sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; }); - it('should have sidenav open when doc in the sidenav (guide/pipes)', () => { + it('should be closed when nav to a guide page (guide/pipes)', () => { locationService.urlSubject.next('guide/pipes'); - fixture.detectChanges(); - const sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; - expect(sidenav.className).toMatch(/sidenav-open/); + expect(sidenav.className).toMatch(/sidenav-clos/); }); - it('should have sidenav open when doc is an api page', () => { + it('should be closed when nav to an api page', () => { locationService.urlSubject.next('api/a/b/c/d'); - fixture.detectChanges(); - const sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; - expect(sidenav.className).toMatch(/sidenav-open/); + expect(sidenav.className).toMatch(/sidenav-clos/); }); - it('should have sidenav closed when doc not in the sidenav (features)', () => { + it('should be closed when nav to a marketing page (features)', () => { locationService.urlSubject.next('features'); - fixture.detectChanges(); - const sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; expect(sidenav.className).toMatch(/sidenav-clos/); }); }); diff --git a/aio/src/app/app.component.ts b/aio/src/app/app.component.ts index 3aae674c89..1891d46792 100644 --- a/aio/src/app/app.component.ts +++ b/aio/src/app/app.component.ts @@ -76,11 +76,11 @@ export class AppComponent implements OnInit { this.currentNode = currentNode; this.pageId = this.currentNode.url.replace('/', '-') || 'home'; - // Toggle the sidenav if the kind of view changed + // Toggle the sidenav if side-by-side and the kind of view changed if (this.previousNavView === currentNode.view) { return; } this.previousNavView = currentNode.view; this.isSideNavDoc = currentNode.view === sideNavView; - this.sideNavToggle(this.isSideNavDoc); + this.sideNavToggle(this.isSideNavDoc && this.isSideBySide); }); this.navigationService.navigationViews.subscribe(views => { From f09fd6ec16b499f38a4362986c25e1877a3601f9 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Tue, 11 Apr 2017 16:08:53 -0700 Subject: [PATCH 0010/1039] test(aio): add sidenav tests and refactor related tests --- aio/src/app/app.component.spec.ts | 158 +++++++++++++----- .../app/documents/document.service.spec.ts | 58 +++---- .../app/navigation/navigation.service.spec.ts | 52 +++--- aio/src/testing/location.service.ts | 4 +- 4 files changed, 174 insertions(+), 98 deletions(-) diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index 587dc46c36..fcd33ff5e4 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -23,6 +23,12 @@ import { MockSwUpdateNotificationsService } from 'testing/sw-update-notification describe('AppComponent', () => { let component: AppComponent; let fixture: ComponentFixture; + + let docViewer: HTMLElement; + let hamburger: HTMLButtonElement; + let locationService: MockLocationService; + let sidenav: HTMLElement; + const initialUrl = 'a/b'; beforeEach(async(() => { @@ -45,6 +51,11 @@ describe('AppComponent', () => { fixture = TestBed.createComponent(AppComponent); component = fixture.componentInstance; fixture.detectChanges(); + + docViewer = fixture.debugElement.query(By.css('aio-doc-viewer')).nativeElement; + hamburger = fixture.debugElement.query(By.css('.hamburger')).nativeElement; + locationService = fixture.debugElement.injector.get(LocationService) as any; + sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; }); it('should create', () => { @@ -58,10 +69,6 @@ describe('AppComponent', () => { }); }); - describe('is Hamburger Visible', () => { - console.log('PENDING: AppComponent'); - }); - describe('onResize', () => { it('should update `isSideBySide` accordingly', () => { component.onResize(1033); @@ -71,32 +78,26 @@ describe('AppComponent', () => { }); }); - describe('SideNav when side-by-side', () => { - let hamburger: HTMLButtonElement; - let locationService: MockLocationService; - let sidenav: Element; + describe('SideNav when side-by-side (wide)', () => { beforeEach(() => { component.onResize(1033); // side-by-side - locationService = fixture.debugElement.injector.get(LocationService) as any; - hamburger = fixture.debugElement.query(By.css('.hamburger')).nativeElement; - sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; }); it('should open when nav to a guide page (guide/pipes)', () => { - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-open/); }); it('should open when nav to an api page', () => { - locationService.urlSubject.next('api/a/b/c/d'); + locationService.go('api/a/b/c/d'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-open/); }); it('should be closed when nav to a marketing page (features)', () => { - locationService.urlSubject.next('features'); + locationService.go('features'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-clos/); }); @@ -104,7 +105,7 @@ describe('AppComponent', () => { describe('when manually closed', () => { beforeEach(() => { - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); hamburger.click(); fixture.detectChanges(); @@ -113,59 +114,110 @@ describe('AppComponent', () => { it('should be closed', () => { expect(sidenav.className).toMatch(/sidenav-clos/); }); + + it('should stay closed when nav to another guide page', () => { + locationService.go('guide/bags'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + it('should stay closed when nav to api page', () => { + locationService.go('api'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + it('should reopen when nav to market page and back to guide page', () => { + locationService.go('features'); + fixture.detectChanges(); + locationService.go('guide/bags'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-open/); + }); }); }); - describe('SideNav when NOT side-by-side (mobile)', () => { - let sidenav: Element; - let locationService: MockLocationService; + describe('SideNav when NOT side-by-side (narrow)', () => { beforeEach(() => { component.onResize(1000); // NOT side-by-side - locationService = fixture.debugElement.injector.get(LocationService) as any; - sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement; }); it('should be closed when nav to a guide page (guide/pipes)', () => { - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-clos/); }); it('should be closed when nav to an api page', () => { - locationService.urlSubject.next('api/a/b/c/d'); + locationService.go('api/a/b/c/d'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-clos/); }); it('should be closed when nav to a marketing page (features)', () => { - locationService.urlSubject.next('features'); + locationService.go('features'); fixture.detectChanges(); expect(sidenav.className).toMatch(/sidenav-clos/); }); + + describe('when manually opened', () => { + + beforeEach(() => { + locationService.go('guide/pipes'); + fixture.detectChanges(); + hamburger.click(); + fixture.detectChanges(); + }); + + it('should be open', () => { + expect(sidenav.className).toMatch(/sidenav-open/); + }); + + it('should close when click in gray content area overlay', () => { + const sidenavBackdrop = fixture.debugElement.query(By.css('.mat-sidenav-backdrop')).nativeElement; + sidenavBackdrop.click(); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + it('should stay open when nav to another guide page', () => { + locationService.go('guide/bags'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-open/); + }); + + it('should stay open when nav to api page', () => { + locationService.go('api'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-open/); + }); + + it('should close again when nav to market page', () => { + locationService.go('features'); + fixture.detectChanges(); + expect(sidenav.className).toMatch(/sidenav-clos/); + }); + + }); }); describe('pageId', () => { - let locationService: MockLocationService; - - beforeEach(() => { - locationService = fixture.debugElement.injector.get(LocationService) as any; - }); it('should set the id of the doc viewer container based on the current url', () => { const container = fixture.debugElement.query(By.css('section.sidenav-content')); - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); expect(component.pageId).toEqual('guide-pipes'); expect(container.properties['id']).toEqual('guide-pipes'); - locationService.urlSubject.next('news'); + locationService.go('news'); fixture.detectChanges(); expect(component.pageId).toEqual('news'); expect(container.properties['id']).toEqual('news'); - locationService.urlSubject.next(''); + locationService.go(''); fixture.detectChanges(); expect(component.pageId).toEqual('home'); expect(container.properties['id']).toEqual('home'); @@ -174,15 +226,15 @@ describe('AppComponent', () => { it('should not be affected by changes to the query or hash', () => { const container = fixture.debugElement.query(By.css('section.sidenav-content')); - locationService.urlSubject.next('guide/pipes'); + locationService.go('guide/pipes'); fixture.detectChanges(); - locationService.urlSubject.next('guide/other?search=http'); + locationService.go('guide/other?search=http'); fixture.detectChanges(); expect(component.pageId).toEqual('guide-other'); expect(container.properties['id']).toEqual('guide-other'); - locationService.urlSubject.next('guide/http#anchor-1'); + locationService.go('guide/http#anchor-1'); fixture.detectChanges(); expect(component.pageId).toEqual('guide-http'); expect(container.properties['id']).toEqual('guide-http'); @@ -190,19 +242,32 @@ describe('AppComponent', () => { }); describe('currentDocument', () => { - console.log('PENDING: AppComponent currentDocument'); - }); - describe('navigationViews', () => { - console.log('PENDING: AppComponent navigationViews'); + it('should display a guide page (guide/pipes)', () => { + locationService.go('guide/pipes'); + fixture.detectChanges(); + expect(docViewer.innerText).toMatch(/Pipes/i); + }); + + it('should display the api page', () => { + locationService.go('api'); + fixture.detectChanges(); + expect(docViewer.innerText).toMatch(/API/i); + }); + + it('should display a marketing page', () => { + locationService.go('features'); + fixture.detectChanges(); + expect(docViewer.innerText).toMatch(/Test Doc/i); + }); + }); describe('autoScrolling', () => { it('should AutoScrollService.scroll when the url changes', () => { - const locationService: MockLocationService = fixture.debugElement.injector.get(LocationService) as any; const scrollService: AutoScrollService = fixture.debugElement.injector.get(AutoScrollService); spyOn(scrollService, 'scroll'); - locationService.urlSubject.next('some/url#fragment'); + locationService.go('some/url#fragment'); expect(scrollService.scroll).toHaveBeenCalledWith(jasmine.any(HTMLElement)); }); @@ -234,9 +299,9 @@ describe('AppComponent', () => { it('should intercept clicks not on the search elements and hide the search results', () => { const searchResults: SearchResultsComponent = fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance; - const docViewer = fixture.debugElement.query(By.css('aio-doc-viewer')); spyOn(searchResults, 'hideResults'); - docViewer.nativeElement.click(); + // docViewer is a commonly-clicked, non-search element + docViewer.click(); expect(searchResults.hideResults).toHaveBeenCalled(); }); @@ -263,6 +328,8 @@ describe('AppComponent', () => { }); +//// test helpers //// + class TestGaService { locationChanged = jasmine.createSpy('locationChanged'); } @@ -292,7 +359,12 @@ class TestHttp { "url": "guide/pipes", "title": "Pipes", "tooltip": "Pipes transform displayed values within a template." - } + }, + { + "url": "guide/bags", + "title": "Bags", + "tooltip": "Pack your bags for a code adventure." + }, ] }, { diff --git a/aio/src/app/documents/document.service.spec.ts b/aio/src/app/documents/document.service.spec.ts index f53648094a..94859bc8bb 100644 --- a/aio/src/app/documents/document.service.spec.ts +++ b/aio/src/app/documents/document.service.spec.ts @@ -34,8 +34,8 @@ function getServices(initialUrl: string = '') { const injector = createInjector(initialUrl); return { backend: injector.get(ConnectionBackend) as MockBackend, - location: injector.get(LocationService) as MockLocationService, - service: injector.get(DocumentService) as DocumentService, + locationService: injector.get(LocationService) as MockLocationService, + docService: injector.get(DocumentService) as DocumentService, logger: injector.get(Logger) as MockLogger }; } @@ -43,16 +43,16 @@ function getServices(initialUrl: string = '') { describe('DocumentService', () => { it('should be creatable', () => { - const { service } = getServices(); - expect(service).toBeTruthy(); + const { docService } = getServices(); + expect(docService).toBeTruthy(); }); describe('currentDocument', () => { it('should fetch a document for the initial location url', () => { - const { service, backend } = getServices('initial/url'); + const { docService, backend } = getServices('initial/url'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(); + docService.currentDocument.subscribe(); expect(connections.length).toEqual(1); expect(connections[0].request.url).toEqual(CONTENT_URL_PREFIX + 'initial/url.json'); @@ -63,24 +63,24 @@ describe('DocumentService', () => { let latestDocument: DocumentContents; const doc0 = { title: 'doc 0' }; const doc1 = { title: 'doc 1' }; - const { service, backend, location } = getServices('initial/url'); + const { docService, backend, locationService } = getServices('initial/url'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(doc => latestDocument = doc); + docService.currentDocument.subscribe(doc => latestDocument = doc); expect(latestDocument).toBeUndefined(); connections[0].mockRespond(createResponse(doc0)); expect(latestDocument).toEqual(doc0); - location.urlSubject.next('new/url'); + locationService.go('new/url'); connections[1].mockRespond(createResponse(doc1)); expect(latestDocument).toEqual(doc1); }); it('should emit the not-found document if the document is not found on the server', () => { - const { service, backend } = getServices('missing/url'); + const { docService, backend } = getServices('missing/url'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(); + docService.currentDocument.subscribe(); connections[0].mockError(new Response(new ResponseOptions({ status: 404, statusText: 'NOT FOUND'})) as any); expect(connections.length).toEqual(2); @@ -91,32 +91,32 @@ describe('DocumentService', () => { let currentDocument: DocumentContents; const notFoundDoc = { title: 'Not Found', contents: 'Document not found' }; const nextDoc = { title: 'Next Doc' }; - const { service, backend, location } = getServices('file-not-found'); + const { docService, backend, locationService } = getServices('file-not-found'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(doc => currentDocument = doc); + docService.currentDocument.subscribe(doc => currentDocument = doc); connections[0].mockError(new Response(new ResponseOptions({ status: 404, statusText: 'NOT FOUND'})) as any); expect(connections.length).toEqual(1); expect(currentDocument).toEqual(notFoundDoc); // now check that we haven't killed the currentDocument observable sequence - location.urlSubject.next('new/url'); + locationService.go('new/url'); connections[1].mockRespond(createResponse(nextDoc)); expect(currentDocument).toEqual(nextDoc); }); it('should not crash the app if the response is not valid JSON', () => { let latestDocument: DocumentContents; - const { service, backend, location } = getServices('initial/url'); + const { docService, backend, locationService} = getServices('initial/url'); const connections = backend.connectionsArray; - service.currentDocument.subscribe(doc => latestDocument = doc); + docService.currentDocument.subscribe(doc => latestDocument = doc); connections[0].mockRespond(new Response(new ResponseOptions({ body: 'this is invalid JSON' }))); expect(latestDocument.title).toEqual('Error fetching document'); const doc1 = { title: 'doc 1' }; - location.urlSubject.next('new/url'); + locationService.go('new/url'); connections[1].mockRespond(createResponse(doc1)); expect(latestDocument).toEqual(doc1); }); @@ -127,10 +127,10 @@ describe('DocumentService', () => { const doc0 = { title: 'doc 0' }; const doc1 = { title: 'doc 1' }; - const { service, backend, location } = getServices('url/0'); + const { docService, backend, locationService} = getServices('url/0'); const connections = backend.connectionsArray; - subscription = service.currentDocument.subscribe(doc => latestDocument = doc); + subscription = docService.currentDocument.subscribe(doc => latestDocument = doc); expect(connections.length).toEqual(1); connections[0].mockRespond(createResponse(doc0)); expect(latestDocument).toEqual(doc0); @@ -139,8 +139,8 @@ describe('DocumentService', () => { // modify the response so we can check that future subscriptions do not trigger another request connections[0].response.next(createResponse({ title: 'error 0' })); - subscription = service.currentDocument.subscribe(doc => latestDocument = doc); - location.urlSubject.next('url/1'); + subscription = docService.currentDocument.subscribe(doc => latestDocument = doc); + locationService.go('url/1'); expect(connections.length).toEqual(2); connections[1].mockRespond(createResponse(doc1)); expect(latestDocument).toEqual(doc1); @@ -149,14 +149,14 @@ describe('DocumentService', () => { // modify the response so we can check that future subscriptions do not trigger another request connections[1].response.next(createResponse({ title: 'error 1' })); - subscription = service.currentDocument.subscribe(doc => latestDocument = doc); - location.urlSubject.next('url/0'); + subscription = docService.currentDocument.subscribe(doc => latestDocument = doc); + locationService.go('url/0'); expect(connections.length).toEqual(2); expect(latestDocument).toEqual(doc0); subscription.unsubscribe(); - subscription = service.currentDocument.subscribe(doc => latestDocument = doc); - location.urlSubject.next('url/1'); + subscription = docService.currentDocument.subscribe(doc => latestDocument = doc); + locationService.go('url/1'); expect(connections.length).toEqual(2); expect(latestDocument).toEqual(doc1); subscription.unsubscribe(); @@ -166,15 +166,15 @@ describe('DocumentService', () => { describe('computeMap', () => { it('should map the "empty" location to the correct document request', () => { let latestDocument: DocumentContents; - const { service, backend } = getServices(); - service.currentDocument.subscribe(doc => latestDocument = doc); + const { docService, backend } = getServices(); + docService.currentDocument.subscribe(doc => latestDocument = doc); expect(backend.connectionsArray[0].request.url).toEqual(CONTENT_URL_PREFIX + 'index.json'); }); it('should map the "folder" locations to the correct document request', () => { - const { service, backend, location } = getServices('guide/'); - service.currentDocument.subscribe(); + const { docService, backend, locationService} = getServices('guide/'); + docService.currentDocument.subscribe(); expect(backend.connectionsArray[0].request.url).toEqual(CONTENT_URL_PREFIX + 'guide.json'); }); diff --git a/aio/src/app/navigation/navigation.service.spec.ts b/aio/src/app/navigation/navigation.service.spec.ts index 873fb927e5..167bfc2a8a 100644 --- a/aio/src/app/navigation/navigation.service.spec.ts +++ b/aio/src/app/navigation/navigation.service.spec.ts @@ -26,16 +26,17 @@ describe('NavigationService', () => { }); it('should be creatable', () => { - const service: NavigationService = injector.get(NavigationService); - expect(service).toBeTruthy(); + const navService: NavigationService = injector.get(NavigationService); + expect(navService).toBeTruthy(); }); describe('navigationViews', () => { - let service: NavigationService, backend: MockBackend; + let backend: MockBackend; + let navService: NavigationService; beforeEach(() => { backend = injector.get(ConnectionBackend); - service = injector.get(NavigationService); + navService = injector.get(NavigationService); }); it('should make a single connection to the server', () => { @@ -45,7 +46,7 @@ describe('NavigationService', () => { it('should expose the server response', () => { const viewsEvents: NavigationViews[] = []; - service.navigationViews.subscribe(views => viewsEvents.push(views)); + navService.navigationViews.subscribe(views => viewsEvents.push(views)); expect(viewsEvents).toEqual([]); backend.connectionsArray[0].mockRespond(createResponse({ TopBar: [ { url: 'a' }] })); @@ -55,10 +56,10 @@ describe('NavigationService', () => { it('should return the same object to all subscribers', () => { let views1: NavigationViews; - service.navigationViews.subscribe(views => views1 = views); + navService.navigationViews.subscribe(views => views1 = views); let views2: NavigationViews; - service.navigationViews.subscribe(views => views2 = views); + navService.navigationViews.subscribe(views => views2 = views); backend.connectionsArray[0].mockRespond(createResponse({ TopBar: [{ url: 'a' }] })); @@ -66,7 +67,7 @@ describe('NavigationService', () => { backend.connectionsArray[0].response.next(createResponse({ TopBar: [{ url: 'error 1' }] })); let views3: NavigationViews; - service.navigationViews.subscribe(views => views3 = views); + navService.navigationViews.subscribe(views => views3 = views); expect(views2).toBe(views1); expect(views3).toBe(views1); @@ -77,8 +78,9 @@ describe('NavigationService', () => { }); describe('currentNode', () => { - let service: NavigationService, location: MockLocationService; let currentNode: CurrentNode; + let locationService: MockLocationService; + let navService: NavigationService; const topBarNodes: NavigationNode[] = [{ url: 'features', title: 'Features' }]; const sideNavNodes: NavigationNode[] = [ @@ -100,17 +102,17 @@ describe('NavigationService', () => { beforeEach(() => { - location = injector.get(LocationService); + locationService = injector.get(LocationService); - service = injector.get(NavigationService); - service.currentNode.subscribe(selected => currentNode = selected); + navService = injector.get(NavigationService); + navService.currentNode.subscribe(selected => currentNode = selected); const backend = injector.get(ConnectionBackend); backend.connectionsArray[0].mockRespond(createResponse(navJson)); }); it('should list the side navigation node that matches the current location, and all its ancestors', () => { - location.urlSubject.next('b'); + locationService.go('b'); expect(currentNode).toEqual({ url: 'b', view: 'SideNav', @@ -120,7 +122,7 @@ describe('NavigationService', () => { ] }); - location.urlSubject.next('d'); + locationService.go('d'); expect(currentNode).toEqual({ url: 'd', view: 'SideNav', @@ -131,7 +133,7 @@ describe('NavigationService', () => { ] }); - location.urlSubject.next('f'); + locationService.go('f'); expect(currentNode).toEqual({ url: 'f', view: 'SideNav', @@ -140,7 +142,7 @@ describe('NavigationService', () => { }); it('should be a TopBar selected node if the current location is a top menu node', () => { - location.urlSubject.next('features'); + locationService.go('features'); expect(currentNode).toEqual({ url: 'features', view: 'TopBar', @@ -149,7 +151,7 @@ describe('NavigationService', () => { }); it('should be a plain object if no side navigation node matches the current location', () => { - location.urlSubject.next('g?search=moo#anchor-1'); + locationService.go('g?search=moo#anchor-1'); expect(currentNode).toEqual({ url: 'g', view: '', @@ -168,29 +170,29 @@ describe('NavigationService', () => { ] }; - location.urlSubject.next('c'); + locationService.go('c'); expect(currentNode).toEqual(cnode, 'location: c'); - location.urlSubject.next('c/'); + locationService.go('c/'); expect(currentNode).toEqual(cnode, 'location: c/'); - location.urlSubject.next('c#foo'); + locationService.go('c#foo'); expect(currentNode).toEqual(cnode, 'location: c#foo'); - location.urlSubject.next('c?foo=1'); + locationService.go('c?foo=1'); expect(currentNode).toEqual(cnode, 'location: c?foo=1'); - location.urlSubject.next('c#foo?bar=1&baz=2'); + locationService.go('c#foo?bar=1&baz=2'); expect(currentNode).toEqual(cnode, 'location: c#foo?bar=1&baz=2'); }); }); describe('versionInfo', () => { - let service: NavigationService, versionInfo: VersionInfo; + let navService: NavigationService, versionInfo: VersionInfo; beforeEach(() => { - service = injector.get(NavigationService); - service.versionInfo.subscribe(info => versionInfo = info); + navService = injector.get(NavigationService); + navService.versionInfo.subscribe(info => versionInfo = info); const backend = injector.get(ConnectionBackend); backend.connectionsArray[0].mockRespond(createResponse({ diff --git a/aio/src/testing/location.service.ts b/aio/src/testing/location.service.ts index 49c47b30d5..1eb3272cad 100644 --- a/aio/src/testing/location.service.ts +++ b/aio/src/testing/location.service.ts @@ -5,9 +5,11 @@ export class MockLocationService { currentUrl = this.urlSubject.asObservable(); search = jasmine.createSpy('search').and.returnValue({}); setSearch = jasmine.createSpy('setSearch'); - go = jasmine.createSpy('Location.go'); + go = jasmine.createSpy('Location.go').and + .callFake((url: string) => this.urlSubject.next(url)); handleAnchorClick = jasmine.createSpy('Location.handleAnchorClick') .and.returnValue(false); // prevent click from causing a browser navigation + constructor(private initialUrl) {} } From 3ad0cc57361bcd7d272896e7217954b9adf957aa Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 12 Apr 2017 14:39:22 -0700 Subject: [PATCH 0011/1039] release: cut the 4.1.0-beta.1 release --- package.json | 2 +- packages/compiler-cli/package.json | 2 +- tools/@angular/tsc-wrapped/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ff31bd2f38..5b7fcdc454 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "4.1.0-beta.0", + "version": "4.1.0-beta.1", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json index 20e7968234..59a6eb2164 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -9,7 +9,7 @@ "ng-xi18n": "./src/extract_i18n.js" }, "dependencies": { - "@angular/tsc-wrapped": "4.1.0-beta.0", + "@angular/tsc-wrapped": "4.1.0-beta.1", "reflect-metadata": "^0.1.2", "minimist": "^1.2.0" }, diff --git a/tools/@angular/tsc-wrapped/package.json b/tools/@angular/tsc-wrapped/package.json index 4e7c6c7b7b..63de3af0a3 100644 --- a/tools/@angular/tsc-wrapped/package.json +++ b/tools/@angular/tsc-wrapped/package.json @@ -1,6 +1,6 @@ { "name": "@angular/tsc-wrapped", - "version": "4.1.0-beta.0", + "version": "4.1.0-beta.1", "description": "Wraps the tsc CLI, allowing extensions.", "homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped", "bugs": "https://github.com/angular/angular/issues", From 9cb5964b4dc9e9c220beaa3733584c4336b08d83 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 12 Apr 2017 14:49:29 -0700 Subject: [PATCH 0012/1039] docs: add changelog for 4.1.0-beta.1 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2319b6adea..89b422658e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ + +# [4.1.0-beta.1](https://github.com/angular/angular/compare/4.1.0-beta.0...4.1.0-beta.1) (2017-04-12) + + +### Bug Fixes + +* **compiler:** fix inheritance for AOT with summaries ([#15583](https://github.com/angular/angular/issues/15583)) ([8ef621a](https://github.com/angular/angular/commit/8ef621a)) +* **language-service:** avoid throwing exceptions when reporting metadata errors ([7764c5c](https://github.com/angular/angular/commit/7764c5c)) +* **language-service:** detect when there isn't a tsconfig.json ([258d539](https://github.com/angular/angular/commit/258d539)), closes [#15874](https://github.com/angular/angular/issues/15874) +* **language-service:** improve resilience to incomplete information ([71a8627](https://github.com/angular/angular/commit/71a8627)) +* **language-service:** initialize static reflector correctly ([fe0d02f](https://github.com/angular/angular/commit/fe0d02f)), closes [#15768](https://github.com/angular/angular/issues/15768) +* **language-service:** parse extended i18n forms ([bde9771](https://github.com/angular/angular/commit/bde9771)) +* **language-service:** resolve any parameter types to any result ([5fbb0d0](https://github.com/angular/angular/commit/5fbb0d0)) +* **router:** fix query param parsing ([a487563](https://github.com/angular/angular/commit/a487563)) +* **router:** the preloader use the module from the loaded config ([6d12aa9](https://github.com/angular/angular/commit/6d12aa9)) +* **tsc-wrapped:** ensure valid path separators in metadata ([96aa236](https://github.com/angular/angular/commit/96aa236)) + + +### Features + +* **animations:** Update types for TypeScript nullability support ([38d75d4](https://github.com/angular/angular/commit/38d75d4)), closes [#15870](https://github.com/angular/angular/issues/15870) +* **benchpress:** Update types for TypeScript nullability support ([14669f2](https://github.com/angular/angular/commit/14669f2)) +* **common:** Update types for TypeScript nullability support ([d8b73e4](https://github.com/angular/angular/commit/d8b73e4)) +* **compiler:** Update types for TypeScript nullability support ([09d9f5f](https://github.com/angular/angular/commit/09d9f5f)) +* **language-service:** Update types for TypeScript nullability support ([540581d](https://github.com/angular/angular/commit/540581d)) +* Update types for TypeScript nullability support in examples ([6f5fccf](https://github.com/angular/angular/commit/6f5fccf)) + + + ## [4.0.2](https://github.com/angular/angular/compare/4.0.1...4.0.2) (2017-04-11) From d263595c6397f9c2dd150590ac4a44ee6d389b1d Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Thu, 13 Apr 2017 00:38:21 +0300 Subject: [PATCH 0013/1039] ci(aio): do not fail when re-deploying preview for the same PR/SHA Previously, when trying to upload the build artifacts for a PR/SHA that was already successfully deployed (e.g. when re-running a Travis job), the preview server would return a 403 and the build would fail. Since we have other mechanisms to verify that the PR author is trusted and the artifacts do indeed come from the specified PR and since the new artifacts should be the same with the already deployed ones (same SHA), there is no reason to fail the build. The preview server will reject the request with a special HTTP status code (409 - Conflict), which the `deploy-preview` script will recognize and exit with 0. --- .../scripts-js/lib/upload-server/build-creator.ts | 2 +- .../scripts-js/lib/verify-setup/server-integration.e2e.ts | 2 +- .../scripts-js/lib/verify-setup/upload-server.e2e.ts | 2 +- .../scripts-js/test/upload-server/build-creator.spec.ts | 2 +- aio/scripts/deploy-preview.sh | 5 +++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/build-creator.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/build-creator.ts index 4197da7ab3..0ae0b20da8 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/build-creator.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/build-creator.ts @@ -26,7 +26,7 @@ export class BuildCreator extends EventEmitter { all([this.exists(prDir), this.exists(shaDir)]). then(([prDirExisted, shaDirExisted]) => { if (shaDirExisted) { - throw new UploadError(403, `Request to overwrite existing directory: ${shaDir}`); + throw new UploadError(409, `Request to overwrite existing directory: ${shaDir}`); } dirToRemoveOnError = prDirExisted ? shaDir : prDir; diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/server-integration.e2e.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/server-integration.e2e.ts index 93ad1fcb88..3f0ffa1418 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/server-integration.e2e.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/server-integration.e2e.ts @@ -73,7 +73,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme h.createDummyArchive(pr9, sha9, archivePath); uploadBuild(pr9, sha9, archivePath). - then(h.verifyResponse(403)). + then(h.verifyResponse(409)). then(() => Promise.all([ getFile(pr9, sha9, 'index.html').then(h.verifyResponse(200, idxContentRegex9)), getFile(pr9, sha9, 'foo/bar.js').then(h.verifyResponse(200, barContentRegex9)), diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/upload-server.e2e.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/upload-server.e2e.ts index 84ea36d8f7..198192a522 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/upload-server.e2e.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/upload-server.e2e.ts @@ -101,7 +101,7 @@ describe('upload-server (on HTTP)', () => { expect(h.readBuildFile(pr, sha9, 'index.html')).toBe('My content'); h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`). - then(h.verifyResponse(403, /^Request to overwrite existing directory/)). + then(h.verifyResponse(409, /^Request to overwrite existing directory/)). then(() => expect(h.readBuildFile(pr, sha9, 'index.html')).toBe('My content')). then(done); }); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/build-creator.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/build-creator.spec.ts index 329b41b2e4..962b38b83c 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/build-creator.spec.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/build-creator.spec.ts @@ -66,7 +66,7 @@ describe('BuildCreator', () => { it('should throw if the build does already exist', done => { bcExistsSpy.and.returnValue(true); bc.create(pr, sha, archive).catch(err => { - expectToBeUploadError(err, 403, `Request to overwrite existing directory: ${shaDir}`); + expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`); done(); }); }); diff --git a/aio/scripts/deploy-preview.sh b/aio/scripts/deploy-preview.sh index b032388fd2..9c94d7da4f 100755 --- a/aio/scripts/deploy-preview.sh +++ b/aio/scripts/deploy-preview.sh @@ -24,8 +24,9 @@ httpCode=$( | sed 's/HTTP_CODE: //' ) -# Exit with an error if the request failed -if [ $httpCode -lt 200 ] || [ $httpCode -ge 400 ]; then +# Exit with an error if the request failed. +# (Ignore 409 failures, which mean trying to re-deploy for the same PR/SHA.) +if [ $httpCode -lt 200 ] || ([ $httpCode -ge 400 ] && [ $httpCode -ne 409 ]); then exit 1 fi From 70b1d6dd9d4cad4dbbf395c009c7f5bac5297432 Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Thu, 13 Apr 2017 11:53:26 -0700 Subject: [PATCH 0014/1039] fix(tsc-wrapped): collect new expressions with no arguments (#15908) Fixes #15906 --- tools/@angular/tsc-wrapped/src/evaluator.ts | 22 ++++++++++++++----- .../tsc-wrapped/test/evaluator.spec.ts | 13 +++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/tools/@angular/tsc-wrapped/src/evaluator.ts b/tools/@angular/tsc-wrapped/src/evaluator.ts index f3445e2df7..5f83f6fe5c 100644 --- a/tools/@angular/tsc-wrapped/src/evaluator.ts +++ b/tools/@angular/tsc-wrapped/src/evaluator.ts @@ -154,7 +154,8 @@ export class Evaluator { case ts.SyntaxKind.CallExpression: const callExpression = node; // We can fold a .concat(). - if (isMethodCallOf(callExpression, 'concat') && callExpression.arguments.length === 1) { + if (isMethodCallOf(callExpression, 'concat') && + arrayOrEmpty(callExpression.arguments).length === 1) { const arrayNode = (callExpression.expression).expression; if (this.isFoldableWorker(arrayNode, folding) && this.isFoldableWorker(callExpression.arguments[0], folding)) { @@ -167,7 +168,8 @@ export class Evaluator { } // We can fold a call to CONST_EXPR - if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1) + if (isCallOf(callExpression, 'CONST_EXPR') && + arrayOrEmpty(callExpression.arguments).length === 1) return this.isFoldableWorker(callExpression.arguments[0], folding); return false; case ts.SyntaxKind.NoSubstitutionTemplateLiteral: @@ -295,14 +297,15 @@ export class Evaluator { return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node); case ts.SyntaxKind.CallExpression: const callExpression = node; - if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) { + if (isCallOf(callExpression, 'forwardRef') && + arrayOrEmpty(callExpression.arguments).length === 1) { const firstArgument = callExpression.arguments[0]; if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) { const arrowFunction = firstArgument; return recordEntry(this.evaluateNode(arrowFunction.body), node); } } - const args = callExpression.arguments.map(arg => this.evaluateNode(arg)); + const args = arrayOrEmpty(callExpression.arguments).map(arg => this.evaluateNode(arg)); if (!this.options.verboseInvalidExpression && args.some(isMetadataError)) { return args.find(isMetadataError); } @@ -315,7 +318,8 @@ export class Evaluator { } } // Always fold a CONST_EXPR even if the argument is not foldable. - if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1) { + if (isCallOf(callExpression, 'CONST_EXPR') && + arrayOrEmpty(callExpression.arguments).length === 1) { return recordEntry(args[0], node); } const expression = this.evaluateNode(callExpression.expression); @@ -329,7 +333,7 @@ export class Evaluator { return recordEntry(result, node); case ts.SyntaxKind.NewExpression: const newExpression = node; - const newArgs = newExpression.arguments.map(arg => this.evaluateNode(arg)); + const newArgs = arrayOrEmpty(newExpression.arguments).map(arg => this.evaluateNode(arg)); if (!this.options.verboseInvalidExpression && newArgs.some(isMetadataError)) { return recordEntry(newArgs.find(isMetadataError), node); } @@ -575,3 +579,9 @@ export class Evaluator { function isPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment { return node.kind == ts.SyntaxKind.PropertyAssignment; } + +const empty = [] as ts.NodeArray; + +function arrayOrEmpty(v: ts.NodeArray): ts.NodeArray { + return v || empty; +} \ No newline at end of file diff --git a/tools/@angular/tsc-wrapped/test/evaluator.spec.ts b/tools/@angular/tsc-wrapped/test/evaluator.spec.ts index 30d28ac32c..0e5e05350a 100644 --- a/tools/@angular/tsc-wrapped/test/evaluator.spec.ts +++ b/tools/@angular/tsc-wrapped/test/evaluator.spec.ts @@ -215,8 +215,21 @@ describe('Evaluator', () => { 0, {__symbolic: 'spread', expression: {__symbolic: 'reference', name: 'arrImport'}}, 5 ]); }); + + it('should be able to handle a new expression with no arguments', () => { + const source = sourceFileOf(` + export var a = new f; + `); + const expr = findVar(source, 'a'); + expect(evaluator.evaluateNode(expr.initializer)) + .toEqual({__symbolic: 'new', expression: {__symbolic: 'reference', name: 'f'}}); + }); }); +function sourceFileOf(text: string): ts.SourceFile { + return ts.createSourceFile('test.ts', text, ts.ScriptTarget.Latest, true); +} + const FILES: Directory = { 'directives.ts': ` export function Pipe(options: { name?: string, pure?: boolean}) { From 9394835db46b6adb797addf563d48fd139e2a32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Boull=C3=A9?= Date: Thu, 13 Apr 2017 20:54:57 +0200 Subject: [PATCH 0015/1039] fix(platform-server): handle innerText (#15818) --- packages/platform-server/src/parse5_adapter.ts | 2 ++ packages/platform-server/test/integration_spec.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/platform-server/src/parse5_adapter.ts b/packages/platform-server/src/parse5_adapter.ts index 9e76abd5ae..baf4ce6160 100644 --- a/packages/platform-server/src/parse5_adapter.ts +++ b/packages/platform-server/src/parse5_adapter.ts @@ -80,6 +80,8 @@ export class Parse5DomAdapter extends DomAdapter { setProperty(el: any, name: string, value: any) { if (name === 'innerHTML') { this.setInnerHTML(el, value); + } else if (name === 'innerText') { + this.setText(el, value); } else if (name === 'className') { el.attribs['class'] = el.className = value; } else { diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index deb3964aa1..920f221ffd 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -54,15 +54,19 @@ class TitleApp { class TitleAppModule { } -@Component({selector: 'app', template: '{{text}}'}) +@Component({selector: 'app', template: '{{text}}

'}) class MyAsyncServerApp { text = ''; + h1 = ''; @HostListener('window:scroll') track() { console.error('scroll'); } ngOnInit() { - Promise.resolve(null).then(() => setTimeout(() => { this.text = 'Works!'; }, 10)); + Promise.resolve(null).then(() => setTimeout(() => { + this.text = 'Works!'; + this.h1 = 'fine'; + }, 10)); } } @@ -353,7 +357,7 @@ export function main() { let doc: string; let called: boolean; let expectedOutput = - 'Works!'; + 'Works!

fine

'; beforeEach(() => { // PlatformConfig takes in a parsed document so that it can be cached across requests. From 14b7dfa00792cc571fd58d3a7adc6cb50abb42db Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 13 Apr 2017 21:55:33 +0300 Subject: [PATCH 0016/1039] fix(aio): create a proper commit link on preview comments (#15941) Previously, only a few characters of the SHA would appear on the preview link comment posted on the PR. This was usually enough for GitHub to create a link to the corresponding commit, but it was possible to have collisions with other commits with the same first characters (which prevented GitHub from identifying the correct commit and create a link.) This commit fixes this issue by including the full SHA on the commentso GitHub can identify the correct commit and create the link. GitHub will automatically truncate the link text (by default to 7 chars unless more are necessary to uniquely identify the commit). --- .../scripts-js/lib/upload-server/upload-server-factory.ts | 2 +- .../scripts-js/test/upload-server/upload-server-factory.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/upload-server-factory.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/upload-server-factory.ts index 7f174be45a..cace76d61f 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/upload-server-factory.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/upload-server-factory.ts @@ -58,7 +58,7 @@ class UploadServerFactory { const githubPullRequests = new GithubPullRequests(githubToken, repoSlug); buildCreator.on(CreatedBuildEvent.type, ({pr, sha}: CreatedBuildEvent) => { - const body = `The angular.io preview for ${sha.slice(0, 7)} is available [here][1].\n\n` + + const body = `The angular.io preview for ${sha} is available [here][1].\n\n` + `[1]: https://pr${pr}-${sha}.${domainName}/`; githubPullRequests.addComment(pr, body); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/upload-server-factory.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/upload-server-factory.spec.ts index 3784b747a2..276c328210 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/upload-server-factory.spec.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/upload-server-factory.spec.ts @@ -143,7 +143,7 @@ describe('uploadServerFactory', () => { it('should post a comment on GitHub on \'build.created\'', () => { const prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment'); - const commentBody = 'The angular.io preview for 1234567 is available [here][1].\n\n' + + const commentBody = 'The angular.io preview for 1234567890 is available [here][1].\n\n' + '[1]: https://pr42-1234567890.domain.name/'; buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890'}); From fdb3f26448bda7a45bf98670cdfe86ec4a0a6adb Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 13 Apr 2017 11:56:00 -0700 Subject: [PATCH 0017/1039] refactor(compiler): cleanup (#15960) --- packages/compiler/src/template_parser/template_parser.ts | 8 ++++---- packages/language-service/src/language_service.ts | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/template_parser/template_parser.ts b/packages/compiler/src/template_parser/template_parser.ts index 22736d68b6..fccec81903 100644 --- a/packages/compiler/src/template_parser/template_parser.ts +++ b/packages/compiler/src/template_parser/template_parser.ts @@ -140,13 +140,13 @@ export class TemplateParser { return this.tryParseHtml( this.expandHtml(this._htmlParser !.parse( template, templateUrl, true, this.getInterpolationConfig(component))), - component, template, directives, pipes, schemas, templateUrl); + component, directives, pipes, schemas); } tryParseHtml( - htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata, template: string, - directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[], - templateUrl: string): TemplateParseResult { + htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata, + directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], + schemas: SchemaMetadata[]): TemplateParseResult { let result: TemplateAst[]; const errors = htmlAstWithErrors.errors; const usedPipes: CompilePipeSummary[] = []; diff --git a/packages/language-service/src/language_service.ts b/packages/language-service/src/language_service.ts index 36b60bb334..6cb1089ea1 100644 --- a/packages/language-service/src/language_service.ts +++ b/packages/language-service/src/language_service.ts @@ -8,12 +8,12 @@ import {CompileMetadataResolver, CompileNgModuleMetadata, CompilerConfig, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgAnalyzedModules, Parser, TemplateParser} from '@angular/compiler'; -import {AstResult, AttrInfo, TemplateInfo} from './common'; +import {AstResult, TemplateInfo} from './common'; import {getTemplateCompletions} from './completions'; import {getDefinition} from './definitions'; import {getDeclarationDiagnostics, getTemplateDiagnostics} from './diagnostics'; import {getHover} from './hover'; -import {Completion, CompletionKind, Completions, Declaration, Declarations, Definition, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types'; +import {Completions, Definition, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Pipes, Span, TemplateSource} from './types'; /** @@ -126,8 +126,7 @@ class LanguageServiceImpl implements LanguageService { const pipes = ngModule.transitiveModule.pipes.map( p => this.host.resolver.getOrLoadPipeMetadata(p.reference).toSummary()); const schemas = ngModule.schemas; - const parseResult = parser.tryParseHtml( - htmlResult, metadata, template.source, directives, pipes, schemas, ''); + const parseResult = parser.tryParseHtml(htmlResult, metadata, directives, pipes, schemas); result = { htmlAst: htmlResult.rootNodes, templateAst: parseResult.templateAst, From 6a2e08d0a8ae24b73d457d58d1b224ce1487ffac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Thu, 13 Apr 2017 17:14:08 -0700 Subject: [PATCH 0018/1039] fix(forms): Update types for TypeScript nullability support (#15859) --- .../directives/abstract_control_directive.ts | 34 +++---- .../abstract_form_group_directive.ts | 12 +-- .../forms/src/directives/control_container.ts | 6 +- packages/forms/src/directives/ng_control.ts | 10 +-- packages/forms/src/directives/ng_form.ts | 2 +- packages/forms/src/directives/ng_model.ts | 6 +- .../src/directives/number_value_accessor.ts | 2 +- .../src/directives/range_value_accessor.ts | 2 +- .../form_control_directive.ts | 8 +- .../reactive_directives/form_control_name.ts | 10 +-- .../form_group_directive.ts | 6 +- .../reactive_directives/form_group_name.ts | 12 +-- .../select_control_value_accessor.ts | 6 +- .../select_multiple_control_value_accessor.ts | 6 +- packages/forms/src/directives/shared.ts | 45 +++++----- packages/forms/src/form_builder.ts | 10 +-- packages/forms/src/model.ts | 47 +++++----- packages/forms/src/validators.ts | 12 +-- packages/forms/test/directives_spec.ts | 38 ++++---- packages/forms/test/form_array_spec.ts | 48 +++++----- packages/forms/test/form_control_spec.ts | 29 +++--- packages/forms/test/form_group_spec.ts | 56 ++++++------ .../forms/test/reactive_integration_spec.ts | 52 +++++------ .../forms/test/template_integration_spec.ts | 66 +++++++------- packages/forms/test/validators_spec.ts | 58 ++++++------ packages/forms/tsconfig-build.json | 1 + tools/public_api_guard/forms/forms.d.ts | 89 ++++++++++--------- 27 files changed, 343 insertions(+), 330 deletions(-) diff --git a/packages/forms/src/directives/abstract_control_directive.ts b/packages/forms/src/directives/abstract_control_directive.ts index f2cef2e96f..1bf7524dae 100644 --- a/packages/forms/src/directives/abstract_control_directive.ts +++ b/packages/forms/src/directives/abstract_control_directive.ts @@ -18,45 +18,49 @@ import {ValidationErrors} from './validators'; * @stable */ export abstract class AbstractControlDirective { - get control(): AbstractControl { throw new Error('unimplemented'); } + abstract get control(): AbstractControl|null; get value(): any { return this.control ? this.control.value : null; } - get valid(): boolean { return this.control ? this.control.valid : null; } + get valid(): boolean|null { return this.control ? this.control.valid : null; } - get invalid(): boolean { return this.control ? this.control.invalid : null; } + get invalid(): boolean|null { return this.control ? this.control.invalid : null; } - get pending(): boolean { return this.control ? this.control.pending : null; } + get pending(): boolean|null { return this.control ? this.control.pending : null; } get errors(): ValidationErrors|null { return this.control ? this.control.errors : null; } - get pristine(): boolean { return this.control ? this.control.pristine : null; } + get pristine(): boolean|null { return this.control ? this.control.pristine : null; } - get dirty(): boolean { return this.control ? this.control.dirty : null; } + get dirty(): boolean|null { return this.control ? this.control.dirty : null; } - get touched(): boolean { return this.control ? this.control.touched : null; } + get touched(): boolean|null { return this.control ? this.control.touched : null; } - get untouched(): boolean { return this.control ? this.control.untouched : null; } + get untouched(): boolean|null { return this.control ? this.control.untouched : null; } - get disabled(): boolean { return this.control ? this.control.disabled : null; } + get disabled(): boolean|null { return this.control ? this.control.disabled : null; } - get enabled(): boolean { return this.control ? this.control.enabled : null; } + get enabled(): boolean|null { return this.control ? this.control.enabled : null; } - get statusChanges(): Observable { return this.control ? this.control.statusChanges : null; } + get statusChanges(): Observable|null { + return this.control ? this.control.statusChanges : null; + } - get valueChanges(): Observable { return this.control ? this.control.valueChanges : null; } + get valueChanges(): Observable|null { + return this.control ? this.control.valueChanges : null; + } - get path(): string[] { return null; } + get path(): string[]|null { return null; } reset(value: any = undefined): void { if (this.control) this.control.reset(value); } - hasError(errorCode: string, path: string[] = null): boolean { + hasError(errorCode: string, path?: string[]): boolean { return this.control ? this.control.hasError(errorCode, path) : false; } - getError(errorCode: string, path: string[] = null): any { + getError(errorCode: string, path?: string[]): any { return this.control ? this.control.getError(errorCode, path) : null; } } diff --git a/packages/forms/src/directives/abstract_form_group_directive.ts b/packages/forms/src/directives/abstract_form_group_directive.ts index 19661ec147..30829df9f2 100644 --- a/packages/forms/src/directives/abstract_form_group_directive.ts +++ b/packages/forms/src/directives/abstract_form_group_directive.ts @@ -34,7 +34,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn ngOnInit(): void { this._checkParentType(); - this.formDirective.addFormGroup(this); + this.formDirective !.addFormGroup(this); } ngOnDestroy(): void { @@ -46,7 +46,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn /** * Get the {@link FormGroup} backing this binding. */ - get control(): FormGroup { return this.formDirective.getFormGroup(this); } + get control(): FormGroup { return this.formDirective !.getFormGroup(this); } /** * Get the path to this control group. @@ -56,11 +56,13 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn /** * Get the {@link Form} to which this group belongs. */ - get formDirective(): Form { return this._parent ? this._parent.formDirective : null; } + get formDirective(): Form|null { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn { return composeValidators(this._validators); } + get validator(): ValidatorFn|null { return composeValidators(this._validators); } - get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); } + get asyncValidator(): AsyncValidatorFn|null { + return composeAsyncValidators(this._asyncValidators); + } /** @internal */ _checkParentType(): void {} diff --git a/packages/forms/src/directives/control_container.ts b/packages/forms/src/directives/control_container.ts index a0ee276426..39225c0dca 100644 --- a/packages/forms/src/directives/control_container.ts +++ b/packages/forms/src/directives/control_container.ts @@ -17,16 +17,16 @@ import {Form} from './form_interface'; * * @stable */ -export class ControlContainer extends AbstractControlDirective { +export abstract class ControlContainer extends AbstractControlDirective { name: string; /** * Get the form to which this container belongs. */ - get formDirective(): Form { return null; } + get formDirective(): Form|null { return null; } /** * Get the path to this container. */ - get path(): string[] { return null; } + get path(): string[]|null { return null; } } diff --git a/packages/forms/src/directives/ng_control.ts b/packages/forms/src/directives/ng_control.ts index 7f2d4b04cc..503adf9281 100644 --- a/packages/forms/src/directives/ng_control.ts +++ b/packages/forms/src/directives/ng_control.ts @@ -26,16 +26,16 @@ function unimplemented(): any { */ export abstract class NgControl extends AbstractControlDirective { /** @internal */ - _parent: ControlContainer = null; - name: string = null; - valueAccessor: ControlValueAccessor = null; + _parent: ControlContainer|null = null; + name: string|null = null; + valueAccessor: ControlValueAccessor|null = null; /** @internal */ _rawValidators: Array = []; /** @internal */ _rawAsyncValidators: Array = []; - get validator(): ValidatorFn { return unimplemented(); } - get asyncValidator(): AsyncValidatorFn { return unimplemented(); } + get validator(): ValidatorFn|null { return unimplemented(); } + get asyncValidator(): AsyncValidatorFn|null { return unimplemented(); } abstract viewToModelUpdate(newValue: any): void; } diff --git a/packages/forms/src/directives/ng_form.ts b/packages/forms/src/directives/ng_form.ts index 6803a6d296..6f69007d69 100644 --- a/packages/forms/src/directives/ng_form.ts +++ b/packages/forms/src/directives/ng_form.ts @@ -130,7 +130,7 @@ export class NgForm extends ControlContainer implements Form { updateModel(dir: NgControl, value: any): void { resolvedPromise.then(() => { - const ctrl = this.form.get(dir.path); + const ctrl = this.form.get(dir.path !); ctrl.setValue(value); }); } diff --git a/packages/forms/src/directives/ng_model.ts b/packages/forms/src/directives/ng_model.ts index c27b28d026..7d58e51e80 100644 --- a/packages/forms/src/directives/ng_model.ts +++ b/packages/forms/src/directives/ng_model.ts @@ -158,9 +158,9 @@ export class NgModel extends NgControl implements OnChanges, get formDirective(): any { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } - get asyncValidator(): AsyncValidatorFn { + get asyncValidator(): AsyncValidatorFn|null { return composeAsyncValidators(this._rawAsyncValidators); } @@ -176,7 +176,7 @@ export class NgModel extends NgControl implements OnChanges, } private _isStandalone(): boolean { - return !this._parent || (this.options && this.options.standalone); + return !this._parent || !!(this.options && this.options.standalone); } private _setUpStandalone(): void { diff --git a/packages/forms/src/directives/number_value_accessor.ts b/packages/forms/src/directives/number_value_accessor.ts index 8c4baf8433..3710faa248 100644 --- a/packages/forms/src/directives/number_value_accessor.ts +++ b/packages/forms/src/directives/number_value_accessor.ts @@ -47,7 +47,7 @@ export class NumberValueAccessor implements ControlValueAccessor { this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue); } - registerOnChange(fn: (_: number) => void): void { + registerOnChange(fn: (_: number|null) => void): void { this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } diff --git a/packages/forms/src/directives/range_value_accessor.ts b/packages/forms/src/directives/range_value_accessor.ts index cf89bc920c..01b5dc0ff3 100644 --- a/packages/forms/src/directives/range_value_accessor.ts +++ b/packages/forms/src/directives/range_value_accessor.ts @@ -45,7 +45,7 @@ export class RangeValueAccessor implements ControlValueAccessor { this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', parseFloat(value)); } - registerOnChange(fn: (_: number) => void): void { + registerOnChange(fn: (_: number|null) => void): void { this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; } diff --git a/packages/forms/src/directives/reactive_directives/form_control_directive.ts b/packages/forms/src/directives/reactive_directives/form_control_directive.ts index 9db23e2aeb..eefabe0702 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_directive.ts @@ -88,8 +88,8 @@ export class FormControlDirective extends NgControl implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (this._isControlChanged(changes)) { setUpControl(this.form, this); - if (this.control.disabled && this.valueAccessor.setDisabledState) { - this.valueAccessor.setDisabledState(true); + if (this.control.disabled && this.valueAccessor !.setDisabledState) { + this.valueAccessor !.setDisabledState !(true); } this.form.updateValueAndValidity({emitEvent: false}); } @@ -101,9 +101,9 @@ export class FormControlDirective extends NgControl implements OnChanges { get path(): string[] { return []; } - get validator(): ValidatorFn { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } - get asyncValidator(): AsyncValidatorFn { + get asyncValidator(): AsyncValidatorFn|null { return composeAsyncValidators(this._rawAsyncValidators); } diff --git a/packages/forms/src/directives/reactive_directives/form_control_name.ts b/packages/forms/src/directives/reactive_directives/form_control_name.ts index def1aeb898..b4e06d4a5c 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_name.ts @@ -125,14 +125,14 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { this.update.emit(newValue); } - get path(): string[] { return controlPath(this.name, this._parent); } + get path(): string[] { return controlPath(this.name, this._parent !); } get formDirective(): any { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } get asyncValidator(): AsyncValidatorFn { - return composeAsyncValidators(this._rawAsyncValidators); + return composeAsyncValidators(this._rawAsyncValidators) !; } get control(): FormControl { return this._control; } @@ -151,8 +151,8 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { private _setUpControl() { this._checkParentType(); this._control = this.formDirective.addControl(this); - if (this.control.disabled && this.valueAccessor.setDisabledState) { - this.valueAccessor.setDisabledState(true); + if (this.control.disabled && this.valueAccessor !.setDisabledState) { + this.valueAccessor !.setDisabledState !(true); } this._added = true; } diff --git a/packages/forms/src/directives/reactive_directives/form_group_directive.ts b/packages/forms/src/directives/reactive_directives/form_group_directive.ts index 6ec1773e2d..9b5ec7af0f 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_directive.ts @@ -69,7 +69,7 @@ export class FormGroupDirective extends ControlContainer implements Form, private _oldForm: FormGroup; directives: FormControlName[] = []; - @Input('formGroup') form: FormGroup = null; + @Input('formGroup') form: FormGroup = null !; @Output() ngSubmit = new EventEmitter(); constructor( @@ -167,10 +167,10 @@ export class FormGroupDirective extends ControlContainer implements Form, private _updateValidators() { const sync = composeValidators(this._validators); - this.form.validator = Validators.compose([this.form.validator, sync]); + this.form.validator = Validators.compose([this.form.validator !, sync !]); const async = composeAsyncValidators(this._asyncValidators); - this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]); + this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator !, async !]); } private _checkFormPresent() { diff --git a/packages/forms/src/directives/reactive_directives/form_group_name.ts b/packages/forms/src/directives/reactive_directives/form_group_name.ts index 3dca7c5a4c..3d7904f155 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_name.ts @@ -166,7 +166,7 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy ngOnInit(): void { this._checkParentType(); - this.formDirective.addFormArray(this); + this.formDirective !.addFormArray(this); } ngOnDestroy(): void { @@ -175,17 +175,19 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy } } - get control(): FormArray { return this.formDirective.getFormArray(this); } + get control(): FormArray { return this.formDirective !.getFormArray(this); } - get formDirective(): FormGroupDirective { + get formDirective(): FormGroupDirective|null { return this._parent ? this._parent.formDirective : null; } get path(): string[] { return controlPath(this.name, this._parent); } - get validator(): ValidatorFn { return composeValidators(this._validators); } + get validator(): ValidatorFn|null { return composeValidators(this._validators); } - get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); } + get asyncValidator(): AsyncValidatorFn|null { + return composeAsyncValidators(this._asyncValidators); + } private _checkParentType(): void { if (_hasInvalidParent(this._parent)) { diff --git a/packages/forms/src/directives/select_control_value_accessor.ts b/packages/forms/src/directives/select_control_value_accessor.ts index daf5d03727..4c5c58dd4a 100644 --- a/packages/forms/src/directives/select_control_value_accessor.ts +++ b/packages/forms/src/directives/select_control_value_accessor.ts @@ -15,7 +15,7 @@ export const SELECT_VALUE_ACCESSOR: Provider = { multi: true }; -function _buildValueString(id: string, value: any): string { +function _buildValueString(id: string | null, value: any): string { if (id == null) return `${value}`; if (value && typeof value === 'object') value = 'Object'; return `${id}: ${value}`.slice(0, 50); @@ -118,7 +118,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor { writeValue(value: any): void { this.value = value; - const id: string = this._getOptionId(value); + const id: string|null = this._getOptionId(value); if (id == null) { this._renderer.setElementProperty(this._elementRef.nativeElement, 'selectedIndex', -1); } @@ -142,7 +142,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor { _registerOption(): string { return (this._idCounter++).toString(); } /** @internal */ - _getOptionId(value: any): string { + _getOptionId(value: any): string|null { for (const id of Array.from(this._optionMap.keys())) { if (this._compareWith(this._optionMap.get(id), value)) return id; } diff --git a/packages/forms/src/directives/select_multiple_control_value_accessor.ts b/packages/forms/src/directives/select_multiple_control_value_accessor.ts index 960cea7a64..37591b5e6f 100644 --- a/packages/forms/src/directives/select_multiple_control_value_accessor.ts +++ b/packages/forms/src/directives/select_multiple_control_value_accessor.ts @@ -149,9 +149,9 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor } /** @internal */ - _getOptionId(value: any): string { + _getOptionId(value: any): string|null { for (const id of Array.from(this._optionMap.keys())) { - if (this._compareWith(this._optionMap.get(id)._value, value)) return id; + if (this._compareWith(this._optionMap.get(id) !._value, value)) return id; } return null; } @@ -159,7 +159,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor /** @internal */ _getOptionValue(valueString: string): any { const id: string = _extractId(valueString); - return this._optionMap.has(id) ? this._optionMap.get(id)._value : valueString; + return this._optionMap.has(id) ? this._optionMap.get(id) !._value : valueString; } } diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts index c8342bd334..8823b62818 100644 --- a/packages/forms/src/directives/shared.ts +++ b/packages/forms/src/directives/shared.ts @@ -27,55 +27,55 @@ import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './valida export function controlPath(name: string, parent: ControlContainer): string[] { - return [...parent.path, name]; + return [...parent.path !, name]; } export function setUpControl(control: FormControl, dir: NgControl): void { if (!control) _throwError(dir, 'Cannot find control with'); if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with'); - control.validator = Validators.compose([control.validator, dir.validator]); - control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]); - dir.valueAccessor.writeValue(control.value); + control.validator = Validators.compose([control.validator !, dir.validator]); + control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]); + dir.valueAccessor !.writeValue(control.value); // view -> model - dir.valueAccessor.registerOnChange((newValue: any) => { + dir.valueAccessor !.registerOnChange((newValue: any) => { dir.viewToModelUpdate(newValue); control.markAsDirty(); control.setValue(newValue, {emitModelToViewChange: false}); }); // touched - dir.valueAccessor.registerOnTouched(() => control.markAsTouched()); + dir.valueAccessor !.registerOnTouched(() => control.markAsTouched()); control.registerOnChange((newValue: any, emitModelEvent: boolean) => { // control -> view - dir.valueAccessor.writeValue(newValue); + dir.valueAccessor !.writeValue(newValue); // control -> ngModel if (emitModelEvent) dir.viewToModelUpdate(newValue); }); - if (dir.valueAccessor.setDisabledState) { + if (dir.valueAccessor !.setDisabledState) { control.registerOnDisabledChange( - (isDisabled: boolean) => { dir.valueAccessor.setDisabledState(isDisabled); }); + (isDisabled: boolean) => { dir.valueAccessor !.setDisabledState !(isDisabled); }); } // re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4 dir._rawValidators.forEach((validator: Validator | ValidatorFn) => { if ((validator).registerOnValidatorChange) - (validator).registerOnValidatorChange(() => control.updateValueAndValidity()); + (validator).registerOnValidatorChange !(() => control.updateValueAndValidity()); }); dir._rawAsyncValidators.forEach((validator: AsyncValidator | AsyncValidatorFn) => { if ((validator).registerOnValidatorChange) - (validator).registerOnValidatorChange(() => control.updateValueAndValidity()); + (validator).registerOnValidatorChange !(() => control.updateValueAndValidity()); }); } export function cleanUpControl(control: FormControl, dir: NgControl) { - dir.valueAccessor.registerOnChange(() => _noControlError(dir)); - dir.valueAccessor.registerOnTouched(() => _noControlError(dir)); + dir.valueAccessor !.registerOnChange(() => _noControlError(dir)); + dir.valueAccessor !.registerOnTouched(() => _noControlError(dir)); dir._rawValidators.forEach((validator: any) => { if (validator.registerOnValidatorChange) { @@ -105,9 +105,9 @@ function _noControlError(dir: NgControl) { function _throwError(dir: AbstractControlDirective, message: string): void { let messageEnd: string; - if (dir.path.length > 1) { - messageEnd = `path: '${dir.path.join(' -> ')}'`; - } else if (dir.path[0]) { + if (dir.path !.length > 1) { + messageEnd = `path: '${dir.path!.join(' -> ')}'`; + } else if (dir.path ![0]) { messageEnd = `name: '${dir.path}'`; } else { messageEnd = 'unspecified name attribute'; @@ -115,11 +115,12 @@ function _throwError(dir: AbstractControlDirective, message: string): void { throw new Error(`${message} ${messageEnd}`); } -export function composeValidators(validators: Array): ValidatorFn { +export function composeValidators(validators: Array): ValidatorFn|null { return validators != null ? Validators.compose(validators.map(normalizeValidator)) : null; } -export function composeAsyncValidators(validators: Array): AsyncValidatorFn { +export function composeAsyncValidators(validators: Array): AsyncValidatorFn| + null { return validators != null ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) : null; } @@ -147,12 +148,12 @@ export function isBuiltInAccessor(valueAccessor: ControlValueAccessor): boolean // TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented export function selectValueAccessor( - dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor { + dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor|null { if (!valueAccessors) return null; - let defaultAccessor: ControlValueAccessor; - let builtinAccessor: ControlValueAccessor; - let customAccessor: ControlValueAccessor; + let defaultAccessor: ControlValueAccessor|undefined = undefined; + let builtinAccessor: ControlValueAccessor|undefined = undefined; + let customAccessor: ControlValueAccessor|undefined = undefined; valueAccessors.forEach((v: ControlValueAccessor) => { if (v.constructor === DefaultValueAccessor) { defaultAccessor = v; diff --git a/packages/forms/src/form_builder.ts b/packages/forms/src/form_builder.ts index 1384ea1d38..b10797c04d 100644 --- a/packages/forms/src/form_builder.ts +++ b/packages/forms/src/form_builder.ts @@ -39,7 +39,7 @@ export class FormBuilder { * * See the {@link FormGroup} constructor for more details. */ - group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): FormGroup { + group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any}|null = null): FormGroup { const controls = this._reduceControls(controlsConfig); const validator: ValidatorFn = extra != null ? extra['validator'] : null; const asyncValidator: AsyncValidatorFn = extra != null ? extra['asyncValidator'] : null; @@ -54,8 +54,8 @@ export class FormBuilder { * */ control( - formState: Object, validator: ValidatorFn|ValidatorFn[] = null, - asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): FormControl { + formState: Object, validator?: ValidatorFn|ValidatorFn[]|null, + asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl { return new FormControl(formState, validator, asyncValidator); } @@ -64,8 +64,8 @@ export class FormBuilder { * configuration, with the given optional `validator` and `asyncValidator`. */ array( - controlsConfig: any[], validator: ValidatorFn = null, - asyncValidator: AsyncValidatorFn = null): FormArray { + controlsConfig: any[], validator?: ValidatorFn|null, + asyncValidator?: AsyncValidatorFn|null): FormArray { const controls = controlsConfig.map(c => this._createControl(c)); return new FormArray(controls, validator, asyncValidator); } diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index cd00942f60..d072b7ed05 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -42,7 +42,7 @@ function _find(control: AbstractControl, path: Array| string, del } if (path instanceof Array && (path.length === 0)) return null; - return (>path).reduce((v, name) => { + return (>path).reduce((v: AbstractControl, name) => { if (v instanceof FormGroup) { return v.controls[name] || null; } @@ -55,13 +55,14 @@ function _find(control: AbstractControl, path: Array| string, del }, control); } -function coerceToValidator(validator: ValidatorFn | ValidatorFn[]): ValidatorFn { - return Array.isArray(validator) ? composeValidators(validator) : validator; +function coerceToValidator(validator?: ValidatorFn | ValidatorFn[] | null): ValidatorFn|null { + return Array.isArray(validator) ? composeValidators(validator) : validator || null; } -function coerceToAsyncValidator(asyncValidator: AsyncValidatorFn | AsyncValidatorFn[]): - AsyncValidatorFn { - return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) : asyncValidator; +function coerceToAsyncValidator(asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): + AsyncValidatorFn|null { + return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) : + asyncValidator || null; } /** @@ -90,7 +91,7 @@ export abstract class AbstractControl { private _parent: FormGroup|FormArray; private _asyncValidationSubscription: any; - constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {} + constructor(public validator: ValidatorFn|null, public asyncValidator: AsyncValidatorFn|null) {} /** * The value of the control. @@ -209,7 +210,7 @@ export abstract class AbstractControl { * Sets the synchronous validators that are active on this control. Calling * this will overwrite any existing sync validators. */ - setValidators(newValidator: ValidatorFn|ValidatorFn[]): void { + setValidators(newValidator: ValidatorFn|ValidatorFn[]|null): void { this.validator = coerceToValidator(newValidator); } @@ -322,7 +323,7 @@ export abstract class AbstractControl { this._statusChanges.emit(this._status); } - this._updateAncestors(onlySelf); + this._updateAncestors(!!onlySelf); this._onDisabledChange.forEach((changeFn) => changeFn(true)); } @@ -338,7 +339,7 @@ export abstract class AbstractControl { this._forEachChild((control: AbstractControl) => { control.enable({onlySelf: true}); }); this.updateValueAndValidity({onlySelf: true, emitEvent}); - this._updateAncestors(onlySelf); + this._updateAncestors(!!onlySelf); this._onDisabledChange.forEach((changeFn) => changeFn(false)); } @@ -409,7 +410,7 @@ export abstract class AbstractControl { return this.validator ? this.validator(this) : null; } - private _runAsyncValidator(emitEvent: boolean): void { + private _runAsyncValidator(emitEvent?: boolean): void { if (this.asyncValidator) { this._status = PENDING; const obs = toObservable(this.asyncValidator(this)); @@ -465,7 +466,7 @@ export abstract class AbstractControl { * * * `this.form.get(['person', 'name']);` */ - get(path: Array|string): AbstractControl { return _find(this, path, '.'); } + get(path: Array|string): AbstractControl|null { return _find(this, path, '.'); } /** * Returns true if the control with the given path has the error specified. Otherwise @@ -473,7 +474,7 @@ export abstract class AbstractControl { * * If no path is given, it checks for the error on the present control. */ - getError(errorCode: string, path: string[] = null): any { + getError(errorCode: string, path?: string[]): any { const control = path ? this.get(path) : this; return control && control._errors ? control._errors[errorCode] : null; } @@ -484,9 +485,7 @@ export abstract class AbstractControl { * * If no path is given, it checks for the error on the present control. */ - hasError(errorCode: string, path: string[] = null): boolean { - return !!this.getError(errorCode, path); - } + hasError(errorCode: string, path?: string[]): boolean { return !!this.getError(errorCode, path); } /** * Retrieves the top-level ancestor of this control. @@ -635,8 +634,8 @@ export class FormControl extends AbstractControl { _onChange: Function[] = []; constructor( - formState: any = null, validator: ValidatorFn|ValidatorFn[] = null, - asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null) { + formState: any = null, validator?: ValidatorFn|ValidatorFn[]|null, + asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) { super(coerceToValidator(validator), coerceToAsyncValidator(asyncValidator)); this._applyFormState(formState); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); @@ -831,9 +830,9 @@ export class FormControl extends AbstractControl { */ export class FormGroup extends AbstractControl { constructor( - public controls: {[key: string]: AbstractControl}, validator: ValidatorFn = null, - asyncValidator: AsyncValidatorFn = null) { - super(validator, asyncValidator); + public controls: {[key: string]: AbstractControl}, validator?: ValidatorFn|null, + asyncValidator?: AsyncValidatorFn|null) { + super(validator || null, asyncValidator || null); this._initObservables(); this._setUpControls(); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); @@ -1137,9 +1136,9 @@ export class FormGroup extends AbstractControl { */ export class FormArray extends AbstractControl { constructor( - public controls: AbstractControl[], validator: ValidatorFn = null, - asyncValidator: AsyncValidatorFn = null) { - super(validator, asyncValidator); + public controls: AbstractControl[], validator?: ValidatorFn|null, + asyncValidator?: AsyncValidatorFn|null) { + super(validator || null, asyncValidator || null); this._initObservables(); this._setUpControls(); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); diff --git a/packages/forms/src/validators.ts b/packages/forms/src/validators.ts index 78cbee570f..de355fcbd4 100644 --- a/packages/forms/src/validators.ts +++ b/packages/forms/src/validators.ts @@ -143,9 +143,11 @@ export class Validators { * Compose multiple validators into a single function that returns the union * of the individual error maps. */ - static compose(validators: ValidatorFn[]): ValidatorFn { + static compose(validators: null): null; + static compose(validators: (ValidatorFn|null|undefined)[]): ValidatorFn|null; + static compose(validators: (ValidatorFn|null|undefined)[]|null): ValidatorFn|null { if (!validators) return null; - const presentValidators = validators.filter(isPresent); + const presentValidators: ValidatorFn[] = validators.filter(isPresent) as any; if (presentValidators.length == 0) return null; return function(control: AbstractControl) { @@ -153,9 +155,9 @@ export class Validators { }; } - static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn { + static composeAsync(validators: (AsyncValidatorFn|null)[]): AsyncValidatorFn|null { if (!validators) return null; - const presentValidators = validators.filter(isPresent); + const presentValidators: AsyncValidatorFn[] = validators.filter(isPresent) as any; if (presentValidators.length == 0) return null; return function(control: AbstractControl) { @@ -188,7 +190,7 @@ function _executeAsyncValidators(control: AbstractControl, validators: AsyncVali function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null { const res: {[key: string]: any} = arrayOfErrors.reduce((res: ValidationErrors | null, errors: ValidationErrors | null) => { - return errors != null ? {...res, ...errors} : res; + return errors != null ? {...res !, ...errors} : res !; }, {}); return Object.keys(res).length === 0 ? null : res; } diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index 642886c991..00001a6fff 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -28,7 +28,7 @@ class CustomValidatorDirective implements Validator { function asyncValidator(expected: any, timeout = 0) { return (c: AbstractControl): any => { - let resolve: (result: any) => void; + let resolve: (result: any) => void = undefined !; const promise = new Promise(res => { resolve = res; }); const res = c.value != expected ? {'async': true} : null; if (timeout == 0) { @@ -44,7 +44,7 @@ export function main() { describe('Form Directives', () => { let defaultAccessor: DefaultValueAccessor; - beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null, null); }); + beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null !, null !, null !); }); describe('shared', () => { describe('selectValueAccessor', () => { @@ -59,42 +59,42 @@ export function main() { () => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); }); it('should return checkbox accessor when provided', () => { - const checkboxAccessor = new CheckboxControlValueAccessor(null, null); + const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); expect(selectValueAccessor(dir, [ defaultAccessor, checkboxAccessor ])).toEqual(checkboxAccessor); }); it('should return select accessor when provided', () => { - const selectAccessor = new SelectControlValueAccessor(null, null); + const selectAccessor = new SelectControlValueAccessor(null !, null !); expect(selectValueAccessor(dir, [ defaultAccessor, selectAccessor ])).toEqual(selectAccessor); }); it('should return select multiple accessor when provided', () => { - const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null, null); + const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null !, null !); expect(selectValueAccessor(dir, [ defaultAccessor, selectMultipleAccessor ])).toEqual(selectMultipleAccessor); }); it('should throw when more than one build-in accessor is provided', () => { - const checkboxAccessor = new CheckboxControlValueAccessor(null, null); - const selectAccessor = new SelectControlValueAccessor(null, null); + const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); + const selectAccessor = new SelectControlValueAccessor(null !, null !); expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError(); }); it('should return custom accessor when provided', () => { const customAccessor = new SpyValueAccessor(); - const checkboxAccessor = new CheckboxControlValueAccessor(null, null); + const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); expect(selectValueAccessor(dir, [defaultAccessor, customAccessor, checkboxAccessor])) .toEqual(customAccessor); }); it('should return custom accessor when provided with select multiple', () => { const customAccessor = new SpyValueAccessor(); - const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null, null); + const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null !, null !); expect(selectValueAccessor( dir, [defaultAccessor, customAccessor, selectMultipleAccessor])) .toEqual(customAccessor); @@ -110,13 +110,13 @@ export function main() { it('should compose functions', () => { const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true}); const dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true}); - const v = composeValidators([dummy1, dummy2]); + const v = composeValidators([dummy1, dummy2]) !; expect(v(new FormControl(''))).toEqual({'dummy1': true, 'dummy2': true}); }); it('should compose validator directives', () => { const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true}); - const v = composeValidators([dummy1, new CustomValidatorDirective()]); + const v = composeValidators([dummy1, new CustomValidatorDirective()]) !; expect(v(new FormControl(''))).toEqual({'dummy1': true, 'custom': true}); }); }); @@ -168,7 +168,7 @@ export function main() { describe('addControl', () => { it('should throw when no control found', () => { - const dir = new FormControlName(form, null, null, [defaultAccessor]); + const dir = new FormControlName(form, null !, null !, [defaultAccessor]); dir.name = 'invalidName'; expect(() => form.addControl(dir)) @@ -176,7 +176,7 @@ export function main() { }); it('should throw for a named control when no value accessor', () => { - const dir = new FormControlName(form, null, null, null); + const dir = new FormControlName(form, null !, null !, null !); dir.name = 'login'; expect(() => form.addControl(dir)) @@ -184,8 +184,8 @@ export function main() { }); it('should throw when no value accessor with path', () => { - const group = new FormGroupName(form, null, null); - const dir = new FormControlName(group, null, null, null); + const group = new FormGroupName(form, null !, null !); + const dir = new FormControlName(group, null !, null !, null !); group.name = 'passwords'; dir.name = 'password'; @@ -315,7 +315,7 @@ export function main() { personControlGroupDir = new NgModelGroup(form, [], []); personControlGroupDir.name = 'person'; - loginControlDir = new NgModel(personControlGroupDir, null, null, [defaultAccessor]); + loginControlDir = new NgModel(personControlGroupDir, null !, null !, [defaultAccessor]); loginControlDir.name = 'login'; loginControlDir.valueAccessor = new DummyControlValueAccessor(); }); @@ -534,7 +534,7 @@ export function main() { beforeEach(() => { ngModel = new NgModel( - null, [Validators.required], [asyncValidator('expected')], [defaultAccessor]); + null !, [Validators.required], [asyncValidator('expected')], [defaultAccessor]); ngModel.valueAccessor = new DummyControlValueAccessor(); control = ngModel.control; }); @@ -566,7 +566,7 @@ export function main() { }); it('should throw when no value accessor with named control', () => { - const namedDir = new NgModel(null, null, null, null); + const namedDir = new NgModel(null !, null !, null !, null !); namedDir.name = 'one'; expect(() => namedDir.ngOnChanges({})) @@ -574,7 +574,7 @@ export function main() { }); it('should throw when no value accessor with unnamed control', () => { - const unnamedDir = new NgModel(null, null, null, null); + const unnamedDir = new NgModel(null !, null !, null !, null !); expect(() => unnamedDir.ngOnChanges({})) .toThrowError( diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts index 66976e9e74..2b94b80ec7 100644 --- a/packages/forms/test/form_array_spec.ts +++ b/packages/forms/test/form_array_spec.ts @@ -15,7 +15,7 @@ import {Validators} from '../src/validators'; export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: AbstractControl) => { - let resolve: (result: any) => void; + let resolve: (result: any) => void = undefined !; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -89,7 +89,7 @@ export function main() { new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}), new FormArray([new FormControl('v4'), new FormControl('v5')]) ]); - a.at(0).get('c3').disable(); + a.at(0).get('c3') !.disable(); (a.at(1) as FormArray).at(1).disable(); expect(a.getRawValue()).toEqual([{'c2': 'v2', 'c3': 'v3'}, ['v4', 'v5']]); @@ -693,7 +693,7 @@ export function main() { describe('get', () => { it('should return null when path is null', () => { const g = new FormGroup({}); - expect(g.get(null)).toEqual(null); + expect(g.get(null !)).toEqual(null); }); it('should return null when path is empty', () => { @@ -712,23 +712,23 @@ export function main() { 'nested': new FormGroup({'two': new FormControl('222')}) }); - expect(g.get(['one']).value).toEqual('111'); - expect(g.get('one').value).toEqual('111'); - expect(g.get(['nested', 'two']).value).toEqual('222'); - expect(g.get('nested.two').value).toEqual('222'); + expect(g.get(['one']) !.value).toEqual('111'); + expect(g.get('one') !.value).toEqual('111'); + expect(g.get(['nested', 'two']) !.value).toEqual('222'); + expect(g.get('nested.two') !.value).toEqual('222'); }); it('should return an element of an array', () => { const g = new FormGroup({'array': new FormArray([new FormControl('111')])}); - expect(g.get(['array', 0]).value).toEqual('111'); + expect(g.get(['array', 0]) !.value).toEqual('111'); }); }); describe('asyncValidator', () => { it('should run the async validator', fakeAsync(() => { const c = new FormControl('value'); - const g = new FormArray([c], null, asyncValidator('expected')); + const g = new FormArray([c], null !, asyncValidator('expected')); expect(g.pending).toEqual(true); @@ -793,10 +793,10 @@ export function main() { }); expect(g.valid).toBe(false); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.valid).toBe(true); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.valid).toBe(false); }); @@ -805,36 +805,36 @@ export function main() { {nested: new FormArray([new FormControl('one')]), two: new FormControl('two')}); expect(g.value).toEqual({'nested': ['one'], 'two': 'two'}); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.value).toEqual({'two': 'two'}); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.value).toEqual({'nested': ['one'], 'two': 'two'}); }); it('should ignore disabled controls when determining dirtiness', () => { const g = new FormGroup({nested: a, two: new FormControl('two')}); - g.get(['nested', 0]).markAsDirty(); + g.get(['nested', 0]) !.markAsDirty(); expect(g.dirty).toBe(true); - g.get('nested').disable(); - expect(g.get('nested').dirty).toBe(true); + g.get('nested') !.disable(); + expect(g.get('nested') !.dirty).toBe(true); expect(g.dirty).toEqual(false); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.dirty).toEqual(true); }); it('should ignore disabled controls when determining touched state', () => { const g = new FormGroup({nested: a, two: new FormControl('two')}); - g.get(['nested', 0]).markAsTouched(); + g.get(['nested', 0]) !.markAsTouched(); expect(g.touched).toBe(true); - g.get('nested').disable(); - expect(g.get('nested').touched).toBe(true); + g.get('nested') !.disable(); + expect(g.get('nested') !.touched).toBe(true); expect(g.touched).toEqual(false); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.touched).toEqual(true); }); @@ -901,7 +901,7 @@ export function main() { }); it('should clear out async array errors when disabled', fakeAsync(() => { - const arr = new FormArray([new FormControl()], null, asyncValidator('expected')); + const arr = new FormArray([new FormControl()], null !, asyncValidator('expected')); tick(); expect(arr.errors).toEqual({'async': true}); @@ -914,7 +914,7 @@ export function main() { })); it('should re-populate async array errors when enabled from a child', fakeAsync(() => { - const arr = new FormArray([new FormControl()], null, asyncValidator('expected')); + const arr = new FormArray([new FormControl()], null !, asyncValidator('expected')); tick(); expect(arr.errors).toEqual({'async': true}); @@ -988,7 +988,7 @@ export function main() { }); it('should remove control if new control is null', () => { - a.setControl(0, null); + a.setControl(0, null !); expect(a.controls[0]).not.toBeDefined(); expect(a.value).toEqual([]); }); diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index e52c23bbac..4fa4d9f3a2 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -16,7 +16,7 @@ import {FormArray} from '../src/model'; export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: FormControl) => { - let resolve: (result: any) => void; + let resolve: (result: any) => void = undefined !; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -49,7 +49,7 @@ export function main() { describe('boxed values', () => { it('should support valid boxed values on creation', () => { - const c = new FormControl({value: 'some val', disabled: true}, null, null); + const c = new FormControl({value: 'some val', disabled: true}, null !, null !); expect(c.disabled).toBe(true); expect(c.value).toBe('some val'); expect(c.status).toBe('DISABLED'); @@ -63,13 +63,13 @@ export function main() { }); it('should not treat objects as boxed values if they have more than two props', () => { - const c = new FormControl({value: '', disabled: true, test: 'test'}, null, null); + const c = new FormControl({value: '', disabled: true, test: 'test'}, null !, null !); expect(c.value).toEqual({value: '', disabled: true, test: 'test'}); expect(c.disabled).toBe(false); }); it('should not treat objects as boxed values if disabled is missing', () => { - const c = new FormControl({value: '', test: 'test'}, null, null); + const c = new FormControl({value: '', test: 'test'}, null !, null !); expect(c.value).toEqual({value: '', test: 'test'}); expect(c.disabled).toBe(false); }); @@ -156,7 +156,7 @@ export function main() { describe('asyncValidator', () => { it('should run validator with the initial value', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidator('expected')); + const c = new FormControl('value', null !, asyncValidator('expected')); tick(); expect(c.valid).toEqual(false); @@ -164,7 +164,7 @@ export function main() { })); it('should support validators returning observables', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidatorReturningObservable); + const c = new FormControl('value', null !, asyncValidatorReturningObservable); tick(); expect(c.valid).toEqual(false); @@ -172,7 +172,7 @@ export function main() { })); it('should rerun the validator when the value changes', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidator('expected')); + const c = new FormControl('value', null !, asyncValidator('expected')); c.setValue('expected'); tick(); @@ -193,7 +193,7 @@ export function main() { })); it('should mark the control as pending while running the async validation', fakeAsync(() => { - const c = new FormControl('', null, asyncValidator('expected')); + const c = new FormControl('', null !, asyncValidator('expected')); expect(c.pending).toEqual(true); @@ -204,7 +204,7 @@ export function main() { it('should only use the latest async validation run', fakeAsync(() => { const c = new FormControl( - '', null, asyncValidator('expected', {'long': 200, 'expected': 100})); + '', null !, asyncValidator('expected', {'long': 200, 'expected': 100})); c.setValue('long'); c.setValue('expected'); @@ -216,14 +216,14 @@ export function main() { it('should support arrays of async validator functions if passed', fakeAsync(() => { const c = - new FormControl('value', null, [asyncValidator('expected'), otherAsyncValidator]); + new FormControl('value', null !, [asyncValidator('expected'), otherAsyncValidator]); tick(); expect(c.errors).toEqual({'async': true, 'other': true}); })); it('should add single async validator', fakeAsync(() => { - const c = new FormControl('value', null); + const c = new FormControl('value', null !); c.setAsyncValidators(asyncValidator('expected')); expect(c.asyncValidator).not.toEqual(null); @@ -235,7 +235,7 @@ export function main() { })); it('should add async validator from array', fakeAsync(() => { - const c = new FormControl('value', null); + const c = new FormControl('value', null !); c.setAsyncValidators([asyncValidator('expected')]); expect(c.asyncValidator).not.toEqual(null); @@ -634,8 +634,7 @@ export function main() { tick(); expect(log).toEqual([ - '' + - 'value: \'\'', + 'value: \'\'', 'status: \'INVALID\'', 'value: \'nonEmpty\'', 'status: \'PENDING\'', @@ -935,7 +934,7 @@ export function main() { }); it('should clear out async errors when disabled', fakeAsync(() => { - const c = new FormControl('', null, asyncValidator('expected')); + const c = new FormControl('', null !, asyncValidator('expected')); tick(); expect(c.errors).toEqual({'async': true}); diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index dd2d222d10..aa62b78c9c 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -15,7 +15,7 @@ import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@a export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: AbstractControl) => { - let resolve: (result: any) => void; + let resolve: (result: any) => void = undefined !; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -70,7 +70,7 @@ export function main() { 'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}), 'array': new FormArray([new FormControl('v4'), new FormControl('v5')]) }); - fg.get('group').get('c3').disable(); + fg.get('group') !.get('c3') !.disable(); (fg.get('array') as FormArray).at(1).disable(); expect(fg.getRawValue()) @@ -690,7 +690,7 @@ export function main() { describe('asyncValidator', () => { it('should run the async validator', fakeAsync(() => { const c = new FormControl('value'); - const g = new FormGroup({'one': c}, null, asyncValidator('expected')); + const g = new FormGroup({'one': c}, null !, asyncValidator('expected')); expect(g.pending).toEqual(true); @@ -701,7 +701,7 @@ export function main() { })); it('should set the parent group\'s status to pending', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidator('expected')); + const c = new FormControl('value', null !, asyncValidator('expected')); const g = new FormGroup({'one': c}); expect(g.pending).toEqual(true); @@ -713,13 +713,13 @@ export function main() { it('should run the parent group\'s async validator when children are pending', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidator('expected')); - const g = new FormGroup({'one': c}, null, asyncValidator('expected')); + const c = new FormControl('value', null !, asyncValidator('expected')); + const g = new FormGroup({'one': c}, null !, asyncValidator('expected')); tick(1); expect(g.errors).toEqual({'async': true}); - expect(g.get('one').errors).toEqual({'async': true}); + expect(g.get('one') !.errors).toEqual({'async': true}); })); }); @@ -772,10 +772,10 @@ export function main() { }); expect(g.valid).toBe(false); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.valid).toBe(true); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.valid).toBe(false); }); @@ -784,10 +784,10 @@ export function main() { {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); expect(g.value).toEqual({'nested': {'one': 'one'}, 'two': 'two'}); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.value).toEqual({'two': 'two'}); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.value).toEqual({'nested': {'one': 'one'}, 'two': 'two'}); }); @@ -795,13 +795,13 @@ export function main() { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one'), two: new FormControl('two')})}); - g.get('nested.two').disable(); + g.get('nested.two') !.disable(); expect(g.value).toEqual({nested: {one: 'one'}}); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); }); @@ -809,38 +809,38 @@ export function main() { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one'), two: new FormControl('two')})}); - g.get('nested.two').disable(); + g.get('nested.two') !.disable(); expect(g.value).toEqual({nested: {one: 'one'}}); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); }); it('should ignore disabled controls when determining dirtiness', () => { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); - g.get('nested.one').markAsDirty(); + g.get('nested.one') !.markAsDirty(); expect(g.dirty).toBe(true); - g.get('nested').disable(); - expect(g.get('nested').dirty).toBe(true); + g.get('nested') !.disable(); + expect(g.get('nested') !.dirty).toBe(true); expect(g.dirty).toEqual(false); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.dirty).toEqual(true); }); it('should ignore disabled controls when determining touched state', () => { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); - g.get('nested.one').markAsTouched(); + g.get('nested.one') !.markAsTouched(); expect(g.touched).toBe(true); - g.get('nested').disable(); - expect(g.get('nested').touched).toBe(true); + g.get('nested') !.disable(); + expect(g.get('nested') !.touched).toBe(true); expect(g.touched).toEqual(false); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.touched).toEqual(true); }); @@ -907,7 +907,8 @@ export function main() { }); it('should clear out async group errors when disabled', fakeAsync(() => { - const g = new FormGroup({'one': new FormControl()}, null, asyncValidator('expected')); + const g = + new FormGroup({'one': new FormControl()}, null !, asyncValidator('expected')); tick(); expect(g.errors).toEqual({'async': true}); @@ -920,7 +921,8 @@ export function main() { })); it('should re-populate async group errors when enabled from a child', fakeAsync(() => { - const g = new FormGroup({'one': new FormControl()}, null, asyncValidator('expected')); + const g = + new FormGroup({'one': new FormControl()}, null !, asyncValidator('expected')); tick(); expect(g.errors).toEqual({'async': true}); @@ -1028,7 +1030,7 @@ export function main() { }); it('should remove control if new control is null', () => { - g.setControl('one', null); + g.setControl('one', null !); expect(g.controls['one']).not.toBeDefined(); expect(g.value).toEqual({}); }); diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts index 8d5ba36e43..1dfd3c63d3 100644 --- a/packages/forms/test/reactive_integration_spec.ts +++ b/packages/forms/test/reactive_integration_spec.ts @@ -166,7 +166,7 @@ export function main() { }); fixture.componentInstance.form = form; fixture.detectChanges(); - expect(form.get('login').errors).toEqual({required: true}); + expect(form.get('login') !.errors).toEqual({required: true}); const newForm = new FormGroup({ 'login': new FormControl(''), @@ -177,7 +177,7 @@ export function main() { fixture.componentInstance.form = newForm; fixture.detectChanges(); - expect(newForm.get('login').errors).toEqual({required: true}); + expect(newForm.get('login') !.errors).toEqual({required: true}); }); it('should pick up dir validators from nested form groups', () => { @@ -188,7 +188,7 @@ export function main() { }); fixture.componentInstance.form = form; fixture.detectChanges(); - expect(form.get('signin').valid).toBe(false); + expect(form.get('signin') !.valid).toBe(false); const newForm = new FormGroup({ 'signin': @@ -197,7 +197,7 @@ export function main() { fixture.componentInstance.form = newForm; fixture.detectChanges(); - expect(form.get('signin').valid).toBe(false); + expect(form.get('signin') !.valid).toBe(false); }); it('should strip named controls that are not found', () => { @@ -373,7 +373,7 @@ export function main() { it('should throw an error if compareWith is not a function', () => { const fixture = initTest(FormControlSelectWithCompareFn); - fixture.componentInstance.compareFn = null; + fixture.componentInstance.compareFn = null !; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -412,7 +412,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(FormControlSelectMultipleWithCompareFn); - fixture.componentInstance.compareFn = null; + fixture.componentInstance.compareFn = null !; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -623,7 +623,7 @@ export function main() { it('should emit ngSubmit event with the original submit event on submit', () => { const fixture = initTest(FormGroupComp); fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')}); - fixture.componentInstance.event = null; + fixture.componentInstance.event = null !; fixture.detectChanges(); const formEl = fixture.debugElement.query(By.css('form')).nativeElement; @@ -739,7 +739,7 @@ export function main() { it('should work with single fields and async validators', fakeAsync(() => { const fixture = initTest(FormControlComp); - const control = new FormControl('', null, uniqLoginAsyncValidator('good')); + const control = new FormControl('', null !, uniqLoginAsyncValidator('good')); fixture.debugElement.componentInstance.control = control; fixture.detectChanges(); @@ -995,10 +995,10 @@ export function main() { fixture.detectChanges(); // view -> model - expect(form.get('food').value).toEqual('chicken'); + expect(form.get('food') !.value).toEqual('chicken'); expect(inputs[1].nativeElement.checked).toEqual(false); - form.get('food').setValue('fish'); + form.get('food') !.setValue('fish'); fixture.detectChanges(); // programmatic change -> view @@ -1039,16 +1039,16 @@ export function main() { fixture.componentInstance.form = form; fixture.detectChanges(); - form.get('food').setValue(null); + form.get('food') !.setValue(null); fixture.detectChanges(); const inputs = fixture.debugElement.queryAll(By.css('input')); expect(inputs[0].nativeElement.checked).toEqual(false); - form.get('food').setValue('chicken'); + form.get('food') !.setValue('chicken'); fixture.detectChanges(); - form.get('food').setValue(undefined); + form.get('food') !.setValue(undefined); fixture.detectChanges(); expect(inputs[0].nativeElement.checked).toEqual(false); }); @@ -1139,8 +1139,8 @@ export function main() { fixture.detectChanges(); // view -> model - expect(form.get('food').value).toEqual('chicken'); - expect(form.get('nested.food').value).toEqual('fish'); + expect(form.get('food') !.value).toEqual('chicken'); + expect(form.get('nested.food') !.value).toEqual('fish'); expect(inputs[1].nativeElement.checked).toEqual(false); expect(inputs[2].nativeElement.checked).toEqual(false); @@ -1161,7 +1161,7 @@ export function main() { expect(inputs[2].nativeElement.disabled).toEqual(false); expect(inputs[3].nativeElement.disabled).toEqual(false); - form.get('food').disable(); + form.get('food') !.disable(); expect(inputs[0].nativeElement.disabled).toEqual(true); expect(inputs[1].nativeElement.disabled).toEqual(true); expect(inputs[2].nativeElement.disabled).toEqual(false); @@ -1267,9 +1267,9 @@ export function main() { expect(form.value).toEqual({'login': 'bb'}); // custom validator - expect(form.get('login').errors).toEqual({'err': true}); + expect(form.get('login') !.errors).toEqual({'err': true}); form.setValue({login: 'expected'}); - expect(form.get('login').errors).toEqual(null); + expect(form.get('login') !.errors).toEqual(null); }); it('should support non builtin input elements that fire a change event without a \'target\' property', @@ -1295,7 +1295,7 @@ export function main() { }); fixture.detectChanges(); expect(fixture.componentInstance.form.status).toEqual('DISABLED'); - expect(fixture.componentInstance.form.get('login').status).toEqual('DISABLED'); + expect(fixture.componentInstance.form.get('login') !.status).toEqual('DISABLED'); }); it('should support custom accessors without setDisabledState - formControlDirective', @@ -1539,9 +1539,9 @@ export function main() { .toEqual(pattern.nativeElement.getAttribute('pattern')); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null; - fixture.componentInstance.maxLen = null; - fixture.componentInstance.pattern = null; + fixture.componentInstance.minLen = null !; + fixture.componentInstance.maxLen = null !; + fixture.componentInstance.pattern = null !; fixture.detectChanges(); expect(form.hasError('required', ['login'])).toEqual(false); @@ -1581,9 +1581,9 @@ export function main() { fixture.detectChanges(); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null; - fixture.componentInstance.maxLen = null; - fixture.componentInstance.pattern = null; + fixture.componentInstance.minLen = null !; + fixture.componentInstance.maxLen = null !; + fixture.componentInstance.pattern = null !; fixture.detectChanges(); expect(newForm.hasError('required', ['login'])).toEqual(false); @@ -1681,7 +1681,7 @@ export function main() { const fixture = initTest(FormControlComp); const resultArr: number[] = []; fixture.componentInstance.control = - new FormControl('', null, observableValidator(resultArr)); + new FormControl('', null !, observableValidator(resultArr)); fixture.detectChanges(); tick(100); diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts index 4ae9c7cd08..87fd77bdf8 100644 --- a/packages/forms/test/template_integration_spec.ts +++ b/packages/forms/test/template_integration_spec.ts @@ -107,9 +107,9 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name').value).toEqual({first: 'Nancy', last: 'Drew'}); - expect(form.control.get('name.first').value).toEqual('Nancy'); - expect(form.control.get('email').value).toEqual('some email'); + expect(form.control.get('name') !.value).toEqual({first: 'Nancy', last: 'Drew'}); + expect(form.control.get('name.first') !.value).toEqual('Nancy'); + expect(form.control.get('email') !.value).toEqual('some email'); })); it('should remove controls and control groups from form control model', fakeAsync(() => { @@ -121,7 +121,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('email').value).toEqual('some email'); + expect(form.control.get('email') !.value).toEqual('some email'); expect(form.value).toEqual({name: {first: 'Nancy'}, email: 'some email'}); // should remove individual control successfully @@ -132,8 +132,8 @@ export function main() { expect(form.control.get('email')).toBe(null); expect(form.value).toEqual({name: {first: 'Nancy'}}); - expect(form.control.get('name').value).toEqual({first: 'Nancy'}); - expect(form.control.get('name.first').value).toEqual('Nancy'); + expect(form.control.get('name') !.value).toEqual({first: 'Nancy'}); + expect(form.control.get('name.first') !.value).toEqual('Nancy'); // should remove form group successfully fixture.componentInstance.groupShowing = false; @@ -228,7 +228,7 @@ export function main() { it('should not create a template-driven form when ngNoForm is used', () => { const fixture = initTest(NgNoFormComp); fixture.detectChanges(); - expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0); + expect(fixture.debugElement.children[0].providerTokens !.length).toEqual(0); }); it('should not add novalidate when ngNoForm is used', () => { @@ -282,7 +282,7 @@ export function main() { describe('submit and reset events', () => { it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => { const fixture = initTest(NgModelForm); - fixture.componentInstance.event = null; + fixture.componentInstance.event = null !; const form = fixture.debugElement.query(By.css('form')); dispatchEvent(form.nativeElement, 'submit'); @@ -355,11 +355,11 @@ export function main() { expect(form.valid).toEqual(true); expect(form.value).toEqual({}); - let formValidity: string; - let formValue: Object; + let formValidity: string = undefined !; + let formValue: Object = undefined !; - form.statusChanges.subscribe((status: string) => formValidity = status); - form.valueChanges.subscribe((value: string) => formValue = value); + form.statusChanges !.subscribe((status: string) => formValidity = status); + form.valueChanges !.subscribe((value: string) => formValue = value); tick(); @@ -374,8 +374,8 @@ export function main() { fixture.detectChanges(); tick(); - form.get('name').valueChanges.subscribe( - () => { expect(form.get('name').dirty).toBe(true); }); + form.get('name') !.valueChanges.subscribe( + () => { expect(form.get('name') !.dirty).toBe(true); }); const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; inputEl.value = 'newValue'; @@ -396,10 +396,10 @@ export function main() { inputEl.value = 'newValue'; dispatchEvent(inputEl, 'input'); - expect(form.get('name').pristine).toBe(false); + expect(form.get('name') !.pristine).toBe(false); - form.get('name').valueChanges.subscribe( - () => { expect(form.get('name').pristine).toBe(true); }); + form.get('name') !.valueChanges.subscribe( + () => { expect(form.get('name') !.pristine).toBe(true); }); dispatchEvent(formEl, 'reset'); })); @@ -418,7 +418,7 @@ export function main() { const form = fixture.debugElement.children[0].injector.get(NgForm); expect(form.value).toEqual({name: {first: '', last: 'Drew'}, email: 'some email'}); expect(form.valid).toBe(false); - expect(form.control.get('name.first').disabled).toBe(false); + expect(form.control.get('name.first') !.disabled).toBe(false); fixture.componentInstance.isDisabled = true; fixture.detectChanges(); @@ -426,7 +426,7 @@ export function main() { expect(form.value).toEqual({name: {last: 'Drew'}, email: 'some email'}); expect(form.valid).toBe(true); - expect(form.control.get('name.first').disabled).toBe(true); + expect(form.control.get('name.first') !.disabled).toBe(true); })); it('should add disabled attribute in the UI if disable() is called programmatically', @@ -438,7 +438,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - form.control.get('name.first').disable(); + form.control.get('name.first') !.disable(); fixture.detectChanges(); tick(); @@ -455,7 +455,7 @@ export function main() { fixture.detectChanges(); fixture.whenStable().then(() => { const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name').disabled).toBe(true); + expect(form.control.get('name') !.disabled).toBe(true); const customInput = fixture.debugElement.query(By.css('[name="custom"]')); expect(customInput.nativeElement.disabled).toEqual(true); @@ -477,7 +477,7 @@ export function main() { fixture.detectChanges(); tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name').disabled).toBe(true); + expect(form.control.get('name') !.disabled).toBe(true); const input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.disabled).toEqual(true); @@ -495,7 +495,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - form.control.get('food').disable(); + form.control.get('food') !.disable(); tick(); const inputs = fixture.debugElement.queryAll(By.css('input')); @@ -620,7 +620,7 @@ export function main() { fixture.detectChanges(); tick(); - fixture.componentInstance.food = null; + fixture.componentInstance.food = null !; fixture.detectChanges(); tick(); @@ -632,7 +632,7 @@ export function main() { fixture.detectChanges(); tick(); - fixture.componentInstance.food = undefined; + fixture.componentInstance.food = undefined !; fixture.detectChanges(); tick(); expect(inputs[0].nativeElement.checked).toEqual(false); @@ -724,7 +724,7 @@ export function main() { const fixture = initTest(NgModelSelectWithNullForm); const comp = fixture.componentInstance; comp.cities = [{'name': 'SF'}, {'name': 'NYC'}]; - comp.selectedCity = null; + comp.selectedCity = null !; fixture.detectChanges(); const select = fixture.debugElement.query(By.css('select')); @@ -745,7 +745,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(NgModelSelectWithCustomCompareFnForm); const comp = fixture.componentInstance; - comp.compareFn = null; + comp.compareFn = null !; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -833,7 +833,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(NgModelSelectMultipleWithCustomCompareFnForm); const comp = fixture.componentInstance; - comp.compareFn = null; + comp.compareFn = null !; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -885,7 +885,7 @@ export function main() { tick(); const control = - fixture.debugElement.children[0].injector.get(NgForm).control.get('checkbox'); + fixture.debugElement.children[0].injector.get(NgForm).control.get('checkbox') !; const input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.checked).toBe(false); @@ -921,7 +921,7 @@ export function main() { tick(); const control = - fixture.debugElement.children[0].injector.get(NgForm).control.get('email'); + fixture.debugElement.children[0].injector.get(NgForm).control.get('email') !; const input = fixture.debugElement.query(By.css('input')); expect(control.hasError('email')).toBe(false); @@ -1114,9 +1114,9 @@ export function main() { .toEqual(pattern.nativeElement.getAttribute('pattern')); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null; - fixture.componentInstance.maxLen = null; - fixture.componentInstance.pattern = null; + fixture.componentInstance.minLen = null !; + fixture.componentInstance.maxLen = null !; + fixture.componentInstance.pattern = null !; fixture.detectChanges(); expect(form.control.hasError('required', ['required'])).toEqual(false); diff --git a/packages/forms/test/validators_spec.ts b/packages/forms/test/validators_spec.ts index da1f6824dd..b44c8ce7c0 100644 --- a/packages/forms/test/validators_spec.ts +++ b/packages/forms/test/validators_spec.ts @@ -179,33 +179,33 @@ export function main() { }); it('should not error on "null" pattern', - () => expect(Validators.pattern(null)(new FormControl('aaAA'))).toBeNull()); + () => expect(Validators.pattern(null !)(new FormControl('aaAA'))).toBeNull()); it('should not error on "undefined" pattern', - () => expect(Validators.pattern(undefined)(new FormControl('aaAA'))).toBeNull()); + () => expect(Validators.pattern(undefined !)(new FormControl('aaAA'))).toBeNull()); }); describe('compose', () => { it('should return null when given null', - () => { expect(Validators.compose(null)).toBe(null); }); + () => { expect(Validators.compose(null !)).toBe(null); }); it('should collect errors from all the validators', () => { - const c = Validators.compose([validator('a', true), validator('b', true)]); + const c = Validators.compose([validator('a', true), validator('b', true)]) !; expect(c(new FormControl(''))).toEqual({'a': true, 'b': true}); }); it('should run validators left to right', () => { - const c = Validators.compose([validator('a', 1), validator('a', 2)]); + const c = Validators.compose([validator('a', 1), validator('a', 2)]) !; expect(c(new FormControl(''))).toEqual({'a': 2}); }); it('should return null when no errors', () => { - const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]); + const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]) !; expect(c(new FormControl(''))).toBeNull(); }); it('should ignore nulls', () => { - const c = Validators.compose([null, Validators.required]); + const c = Validators.compose([null !, Validators.required]) !; expect(c(new FormControl(''))).toEqual({'required': true}); }); }); @@ -221,13 +221,13 @@ export function main() { } it('should return null when given null', - () => { expect(Validators.composeAsync(null)).toBeNull(); }); + () => { expect(Validators.composeAsync(null !)).toBeNull(); }); it('should collect errors from all the validators', fakeAsync(() => { const v = Validators.composeAsync( - [promiseValidator({'one': true}), promiseValidator({'two': true})]); + [promiseValidator({'one': true}), promiseValidator({'two': true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -236,10 +236,10 @@ export function main() { })); it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => { - const v = Validators.composeAsync( - [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]); + const v = Validators.composeAsync([normalizeAsyncValidator( + new AsyncValidatorDirective('expected', {'one': true}))]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -248,9 +248,9 @@ export function main() { })); it('should return null when no errors', fakeAsync(() => { - const v = Validators.composeAsync([promiseValidator({'one': true})]); + const v = Validators.composeAsync([promiseValidator({'one': true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('expected'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -259,9 +259,9 @@ export function main() { })); it('should ignore nulls', fakeAsync(() => { - const v = Validators.composeAsync([promiseValidator({'one': true}), null]); + const v = Validators.composeAsync([promiseValidator({'one': true}), null !]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -279,13 +279,13 @@ export function main() { } it('should return null when given null', - () => { expect(Validators.composeAsync(null)).toBeNull(); }); + () => { expect(Validators.composeAsync(null !)).toBeNull(); }); it('should collect errors from all the validators', () => { const v = Validators.composeAsync( - [observableValidator({'one': true}), observableValidator({'two': true})]); + [observableValidator({'one': true}), observableValidator({'two': true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -294,19 +294,19 @@ export function main() { it('should normalize and evaluate async validator-directives correctly', () => { const v = Validators.composeAsync( - [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]); + [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) - .subscribe((errors: {[key: string]: any}) => errorMap = errors); + .subscribe((errors: {[key: string]: any}) => errorMap = errors) !; expect(errorMap).toEqual({'one': true}); }); it('should return null when no errors', () => { - const v = Validators.composeAsync([observableValidator({'one': true})]); + const v = Validators.composeAsync([observableValidator({'one': true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('expected'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -314,9 +314,9 @@ export function main() { }); it('should ignore nulls', () => { - const v = Validators.composeAsync([observableValidator({'one': true}), null]); + const v = Validators.composeAsync([observableValidator({'one': true}), null !]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -329,9 +329,9 @@ export function main() { } const v = Validators.composeAsync( - [getTimerObs(100, {one: true}), getTimerObs(200, {two: true})]); + [getTimerObs(100, {one: true}), getTimerObs(200, {two: true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); diff --git a/packages/forms/tsconfig-build.json b/packages/forms/tsconfig-build.json index 74d0fa0488..8cfa4196f1 100644 --- a/packages/forms/tsconfig-build.json +++ b/packages/forms/tsconfig-build.json @@ -4,6 +4,7 @@ "declaration": true, "stripInternal": true, "experimentalDecorators": true, + "strictNullChecks": true, "module": "es2015", "moduleResolution": "node", "outDir": "../../dist/packages/forms", diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts index 889a1fc264..fdfc00298b 100644 --- a/tools/public_api_guard/forms/forms.d.ts +++ b/tools/public_api_guard/forms/forms.d.ts @@ -1,6 +1,6 @@ /** @stable */ export declare abstract class AbstractControl { - asyncValidator: AsyncValidatorFn; + asyncValidator: AsyncValidatorFn | null; readonly dirty: boolean; readonly disabled: boolean; readonly enabled: boolean; @@ -15,10 +15,10 @@ export declare abstract class AbstractControl { readonly touched: boolean; readonly untouched: boolean; readonly valid: boolean; - validator: ValidatorFn; + validator: ValidatorFn | null; readonly value: any; readonly valueChanges: Observable; - constructor(validator: ValidatorFn, asyncValidator: AsyncValidatorFn); + constructor(validator: ValidatorFn | null, asyncValidator: AsyncValidatorFn | null); clearAsyncValidators(): void; clearValidators(): void; disable({onlySelf, emitEvent}?: { @@ -29,7 +29,7 @@ export declare abstract class AbstractControl { onlySelf?: boolean; emitEvent?: boolean; }): void; - get(path: Array | string): AbstractControl; + get(path: Array | string): AbstractControl | null; getError(errorCode: string, path?: string[]): any; hasError(errorCode: string, path?: string[]): boolean; markAsDirty({onlySelf}?: { @@ -54,7 +54,7 @@ export declare abstract class AbstractControl { emitEvent?: boolean; }): void; setParent(parent: FormGroup | FormArray): void; - setValidators(newValidator: ValidatorFn | ValidatorFn[]): void; + setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void; abstract setValue(value: any, options?: Object): void; updateValueAndValidity({onlySelf, emitEvent}?: { onlySelf?: boolean; @@ -64,21 +64,21 @@ export declare abstract class AbstractControl { /** @stable */ export declare abstract class AbstractControlDirective { - readonly control: AbstractControl; - readonly dirty: boolean; - readonly disabled: boolean; - readonly enabled: boolean; + readonly abstract control: AbstractControl | null; + readonly dirty: boolean | null; + readonly disabled: boolean | null; + readonly enabled: boolean | null; readonly errors: ValidationErrors | null; - readonly invalid: boolean; - readonly path: string[]; - readonly pending: boolean; - readonly pristine: boolean; - readonly statusChanges: Observable; - readonly touched: boolean; - readonly untouched: boolean; - readonly valid: boolean; + readonly invalid: boolean | null; + readonly path: string[] | null; + readonly pending: boolean | null; + readonly pristine: boolean | null; + readonly statusChanges: Observable | null; + readonly touched: boolean | null; + readonly untouched: boolean | null; + readonly valid: boolean | null; readonly value: any; - readonly valueChanges: Observable; + readonly valueChanges: Observable | null; getError(errorCode: string, path?: string[]): any; hasError(errorCode: string, path?: string[]): boolean; reset(value?: any): void; @@ -86,11 +86,11 @@ export declare abstract class AbstractControlDirective { /** @stable */ export declare class AbstractFormGroupDirective extends ControlContainer implements OnInit, OnDestroy { - readonly asyncValidator: AsyncValidatorFn; + readonly asyncValidator: AsyncValidatorFn | null; readonly control: FormGroup; - readonly formDirective: Form; + readonly formDirective: Form | null; readonly path: string[]; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; ngOnDestroy(): void; ngOnInit(): void; } @@ -125,10 +125,10 @@ export declare class CheckboxRequiredValidator extends RequiredValidator { export declare const COMPOSITION_BUFFER_MODE: InjectionToken; /** @stable */ -export declare class ControlContainer extends AbstractControlDirective { - readonly formDirective: Form; +export declare abstract class ControlContainer extends AbstractControlDirective { + readonly formDirective: Form | null; name: string; - readonly path: string[]; + readonly path: string[] | null; } /** @stable */ @@ -175,7 +175,7 @@ export interface Form { export declare class FormArray extends AbstractControl { controls: AbstractControl[]; readonly length: number; - constructor(controls: AbstractControl[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn); + constructor(controls: AbstractControl[], validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null); at(index: number): AbstractControl; getRawValue(): any[]; insert(index: number, control: AbstractControl): void; @@ -198,12 +198,12 @@ export declare class FormArray extends AbstractControl { /** @stable */ export declare class FormArrayName extends ControlContainer implements OnInit, OnDestroy { - readonly asyncValidator: AsyncValidatorFn; + readonly asyncValidator: AsyncValidatorFn | null; readonly control: FormArray; - readonly formDirective: FormGroupDirective; + readonly formDirective: FormGroupDirective | null; name: string; readonly path: string[]; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; constructor(parent: ControlContainer, validators: any[], asyncValidators: any[]); ngOnDestroy(): void; ngOnInit(): void; @@ -211,18 +211,18 @@ export declare class FormArrayName extends ControlContainer implements OnInit, O /** @stable */ export declare class FormBuilder { - array(controlsConfig: any[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn): FormArray; - control(formState: Object, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]): FormControl; + array(controlsConfig: any[], validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null): FormArray; + control(formState: Object, validator?: ValidatorFn | ValidatorFn[] | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl; group(controlsConfig: { [key: string]: any; }, extra?: { [key: string]: any; - }): FormGroup; + } | null): FormGroup; } /** @stable */ export declare class FormControl extends AbstractControl { - constructor(formState?: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]); + constructor(formState?: any, validator?: ValidatorFn | ValidatorFn[] | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null); patchValue(value: any, options?: { onlySelf?: boolean; emitEvent?: boolean; @@ -245,14 +245,14 @@ export declare class FormControl extends AbstractControl { /** @stable */ export declare class FormControlDirective extends NgControl implements OnChanges { - readonly asyncValidator: AsyncValidatorFn; + readonly asyncValidator: AsyncValidatorFn | null; readonly control: FormControl; form: FormControl; isDisabled: boolean; model: any; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; viewModel: any; constructor(validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; @@ -269,7 +269,7 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD name: string; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; @@ -283,7 +283,7 @@ export declare class FormGroup extends AbstractControl { }; constructor(controls: { [key: string]: AbstractControl; - }, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn); + }, validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null); addControl(name: string, control: AbstractControl): void; contains(controlName: string): boolean; getRawValue(): any; @@ -371,10 +371,10 @@ export declare const NG_VALUE_ACCESSOR: InjectionToken; /** @stable */ export declare abstract class NgControl extends AbstractControlDirective { - readonly asyncValidator: AsyncValidatorFn; - name: string; - readonly validator: ValidatorFn; - valueAccessor: ControlValueAccessor; + readonly asyncValidator: AsyncValidatorFn | null; + name: string | null; + readonly validator: ValidatorFn | null; + valueAccessor: ControlValueAccessor | null; abstract viewToModelUpdate(newValue: any): void; } @@ -417,7 +417,7 @@ export declare class NgForm extends ControlContainer implements Form { /** @stable */ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { - readonly asyncValidator: AsyncValidatorFn; + readonly asyncValidator: AsyncValidatorFn | null; readonly control: FormControl; readonly formDirective: any; isDisabled: boolean; @@ -429,7 +429,7 @@ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { }; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; viewModel: any; constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; @@ -532,8 +532,9 @@ export interface ValidatorFn { /** @stable */ export declare class Validators { - static compose(validators: ValidatorFn[]): ValidatorFn; - static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn; + static compose(validators: null): null; + static compose(validators: (ValidatorFn | null | undefined)[]): ValidatorFn | null; + static composeAsync(validators: (AsyncValidatorFn | null)[]): AsyncValidatorFn | null; static email(control: AbstractControl): ValidationErrors | null; static maxLength(maxLength: number): ValidatorFn; static minLength(minLength: number): ValidatorFn; From 2e47a0d19fa1d1527cf998d87cd8a49bc968606a Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 13 Apr 2017 18:03:42 -0700 Subject: [PATCH 0019/1039] Revert "fix(forms): Update types for TypeScript nullability support (#15859)" (#15977) This reverts commit 6a2e08d0a8ae24b73d457d58d1b224ce1487ffac. --- .../directives/abstract_control_directive.ts | 34 ++++--- .../abstract_form_group_directive.ts | 12 ++- .../forms/src/directives/control_container.ts | 6 +- packages/forms/src/directives/ng_control.ts | 10 +-- packages/forms/src/directives/ng_form.ts | 2 +- packages/forms/src/directives/ng_model.ts | 6 +- .../src/directives/number_value_accessor.ts | 2 +- .../src/directives/range_value_accessor.ts | 2 +- .../form_control_directive.ts | 8 +- .../reactive_directives/form_control_name.ts | 10 +-- .../form_group_directive.ts | 6 +- .../reactive_directives/form_group_name.ts | 12 ++- .../select_control_value_accessor.ts | 6 +- .../select_multiple_control_value_accessor.ts | 6 +- packages/forms/src/directives/shared.ts | 45 +++++----- packages/forms/src/form_builder.ts | 10 +-- packages/forms/src/model.ts | 47 +++++----- packages/forms/src/validators.ts | 12 ++- packages/forms/test/directives_spec.ts | 38 ++++---- packages/forms/test/form_array_spec.ts | 48 +++++----- packages/forms/test/form_control_spec.ts | 29 +++--- packages/forms/test/form_group_spec.ts | 56 ++++++------ .../forms/test/reactive_integration_spec.ts | 52 +++++------ .../forms/test/template_integration_spec.ts | 66 +++++++------- packages/forms/test/validators_spec.ts | 58 ++++++------ packages/forms/tsconfig-build.json | 1 - tools/public_api_guard/forms/forms.d.ts | 89 +++++++++---------- 27 files changed, 330 insertions(+), 343 deletions(-) diff --git a/packages/forms/src/directives/abstract_control_directive.ts b/packages/forms/src/directives/abstract_control_directive.ts index 1bf7524dae..f2cef2e96f 100644 --- a/packages/forms/src/directives/abstract_control_directive.ts +++ b/packages/forms/src/directives/abstract_control_directive.ts @@ -18,49 +18,45 @@ import {ValidationErrors} from './validators'; * @stable */ export abstract class AbstractControlDirective { - abstract get control(): AbstractControl|null; + get control(): AbstractControl { throw new Error('unimplemented'); } get value(): any { return this.control ? this.control.value : null; } - get valid(): boolean|null { return this.control ? this.control.valid : null; } + get valid(): boolean { return this.control ? this.control.valid : null; } - get invalid(): boolean|null { return this.control ? this.control.invalid : null; } + get invalid(): boolean { return this.control ? this.control.invalid : null; } - get pending(): boolean|null { return this.control ? this.control.pending : null; } + get pending(): boolean { return this.control ? this.control.pending : null; } get errors(): ValidationErrors|null { return this.control ? this.control.errors : null; } - get pristine(): boolean|null { return this.control ? this.control.pristine : null; } + get pristine(): boolean { return this.control ? this.control.pristine : null; } - get dirty(): boolean|null { return this.control ? this.control.dirty : null; } + get dirty(): boolean { return this.control ? this.control.dirty : null; } - get touched(): boolean|null { return this.control ? this.control.touched : null; } + get touched(): boolean { return this.control ? this.control.touched : null; } - get untouched(): boolean|null { return this.control ? this.control.untouched : null; } + get untouched(): boolean { return this.control ? this.control.untouched : null; } - get disabled(): boolean|null { return this.control ? this.control.disabled : null; } + get disabled(): boolean { return this.control ? this.control.disabled : null; } - get enabled(): boolean|null { return this.control ? this.control.enabled : null; } + get enabled(): boolean { return this.control ? this.control.enabled : null; } - get statusChanges(): Observable|null { - return this.control ? this.control.statusChanges : null; - } + get statusChanges(): Observable { return this.control ? this.control.statusChanges : null; } - get valueChanges(): Observable|null { - return this.control ? this.control.valueChanges : null; - } + get valueChanges(): Observable { return this.control ? this.control.valueChanges : null; } - get path(): string[]|null { return null; } + get path(): string[] { return null; } reset(value: any = undefined): void { if (this.control) this.control.reset(value); } - hasError(errorCode: string, path?: string[]): boolean { + hasError(errorCode: string, path: string[] = null): boolean { return this.control ? this.control.hasError(errorCode, path) : false; } - getError(errorCode: string, path?: string[]): any { + getError(errorCode: string, path: string[] = null): any { return this.control ? this.control.getError(errorCode, path) : null; } } diff --git a/packages/forms/src/directives/abstract_form_group_directive.ts b/packages/forms/src/directives/abstract_form_group_directive.ts index 30829df9f2..19661ec147 100644 --- a/packages/forms/src/directives/abstract_form_group_directive.ts +++ b/packages/forms/src/directives/abstract_form_group_directive.ts @@ -34,7 +34,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn ngOnInit(): void { this._checkParentType(); - this.formDirective !.addFormGroup(this); + this.formDirective.addFormGroup(this); } ngOnDestroy(): void { @@ -46,7 +46,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn /** * Get the {@link FormGroup} backing this binding. */ - get control(): FormGroup { return this.formDirective !.getFormGroup(this); } + get control(): FormGroup { return this.formDirective.getFormGroup(this); } /** * Get the path to this control group. @@ -56,13 +56,11 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn /** * Get the {@link Form} to which this group belongs. */ - get formDirective(): Form|null { return this._parent ? this._parent.formDirective : null; } + get formDirective(): Form { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn|null { return composeValidators(this._validators); } + get validator(): ValidatorFn { return composeValidators(this._validators); } - get asyncValidator(): AsyncValidatorFn|null { - return composeAsyncValidators(this._asyncValidators); - } + get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); } /** @internal */ _checkParentType(): void {} diff --git a/packages/forms/src/directives/control_container.ts b/packages/forms/src/directives/control_container.ts index 39225c0dca..a0ee276426 100644 --- a/packages/forms/src/directives/control_container.ts +++ b/packages/forms/src/directives/control_container.ts @@ -17,16 +17,16 @@ import {Form} from './form_interface'; * * @stable */ -export abstract class ControlContainer extends AbstractControlDirective { +export class ControlContainer extends AbstractControlDirective { name: string; /** * Get the form to which this container belongs. */ - get formDirective(): Form|null { return null; } + get formDirective(): Form { return null; } /** * Get the path to this container. */ - get path(): string[]|null { return null; } + get path(): string[] { return null; } } diff --git a/packages/forms/src/directives/ng_control.ts b/packages/forms/src/directives/ng_control.ts index 503adf9281..7f2d4b04cc 100644 --- a/packages/forms/src/directives/ng_control.ts +++ b/packages/forms/src/directives/ng_control.ts @@ -26,16 +26,16 @@ function unimplemented(): any { */ export abstract class NgControl extends AbstractControlDirective { /** @internal */ - _parent: ControlContainer|null = null; - name: string|null = null; - valueAccessor: ControlValueAccessor|null = null; + _parent: ControlContainer = null; + name: string = null; + valueAccessor: ControlValueAccessor = null; /** @internal */ _rawValidators: Array = []; /** @internal */ _rawAsyncValidators: Array = []; - get validator(): ValidatorFn|null { return unimplemented(); } - get asyncValidator(): AsyncValidatorFn|null { return unimplemented(); } + get validator(): ValidatorFn { return unimplemented(); } + get asyncValidator(): AsyncValidatorFn { return unimplemented(); } abstract viewToModelUpdate(newValue: any): void; } diff --git a/packages/forms/src/directives/ng_form.ts b/packages/forms/src/directives/ng_form.ts index 6f69007d69..6803a6d296 100644 --- a/packages/forms/src/directives/ng_form.ts +++ b/packages/forms/src/directives/ng_form.ts @@ -130,7 +130,7 @@ export class NgForm extends ControlContainer implements Form { updateModel(dir: NgControl, value: any): void { resolvedPromise.then(() => { - const ctrl = this.form.get(dir.path !); + const ctrl = this.form.get(dir.path); ctrl.setValue(value); }); } diff --git a/packages/forms/src/directives/ng_model.ts b/packages/forms/src/directives/ng_model.ts index 7d58e51e80..c27b28d026 100644 --- a/packages/forms/src/directives/ng_model.ts +++ b/packages/forms/src/directives/ng_model.ts @@ -158,9 +158,9 @@ export class NgModel extends NgControl implements OnChanges, get formDirective(): any { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn { return composeValidators(this._rawValidators); } - get asyncValidator(): AsyncValidatorFn|null { + get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._rawAsyncValidators); } @@ -176,7 +176,7 @@ export class NgModel extends NgControl implements OnChanges, } private _isStandalone(): boolean { - return !this._parent || !!(this.options && this.options.standalone); + return !this._parent || (this.options && this.options.standalone); } private _setUpStandalone(): void { diff --git a/packages/forms/src/directives/number_value_accessor.ts b/packages/forms/src/directives/number_value_accessor.ts index 3710faa248..8c4baf8433 100644 --- a/packages/forms/src/directives/number_value_accessor.ts +++ b/packages/forms/src/directives/number_value_accessor.ts @@ -47,7 +47,7 @@ export class NumberValueAccessor implements ControlValueAccessor { this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue); } - registerOnChange(fn: (_: number|null) => void): void { + registerOnChange(fn: (_: number) => void): void { this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } diff --git a/packages/forms/src/directives/range_value_accessor.ts b/packages/forms/src/directives/range_value_accessor.ts index 01b5dc0ff3..cf89bc920c 100644 --- a/packages/forms/src/directives/range_value_accessor.ts +++ b/packages/forms/src/directives/range_value_accessor.ts @@ -45,7 +45,7 @@ export class RangeValueAccessor implements ControlValueAccessor { this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', parseFloat(value)); } - registerOnChange(fn: (_: number|null) => void): void { + registerOnChange(fn: (_: number) => void): void { this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; } diff --git a/packages/forms/src/directives/reactive_directives/form_control_directive.ts b/packages/forms/src/directives/reactive_directives/form_control_directive.ts index eefabe0702..9db23e2aeb 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_directive.ts @@ -88,8 +88,8 @@ export class FormControlDirective extends NgControl implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (this._isControlChanged(changes)) { setUpControl(this.form, this); - if (this.control.disabled && this.valueAccessor !.setDisabledState) { - this.valueAccessor !.setDisabledState !(true); + if (this.control.disabled && this.valueAccessor.setDisabledState) { + this.valueAccessor.setDisabledState(true); } this.form.updateValueAndValidity({emitEvent: false}); } @@ -101,9 +101,9 @@ export class FormControlDirective extends NgControl implements OnChanges { get path(): string[] { return []; } - get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn { return composeValidators(this._rawValidators); } - get asyncValidator(): AsyncValidatorFn|null { + get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._rawAsyncValidators); } diff --git a/packages/forms/src/directives/reactive_directives/form_control_name.ts b/packages/forms/src/directives/reactive_directives/form_control_name.ts index b4e06d4a5c..def1aeb898 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_name.ts @@ -125,14 +125,14 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { this.update.emit(newValue); } - get path(): string[] { return controlPath(this.name, this._parent !); } + get path(): string[] { return controlPath(this.name, this._parent); } get formDirective(): any { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn { return composeValidators(this._rawValidators); } get asyncValidator(): AsyncValidatorFn { - return composeAsyncValidators(this._rawAsyncValidators) !; + return composeAsyncValidators(this._rawAsyncValidators); } get control(): FormControl { return this._control; } @@ -151,8 +151,8 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { private _setUpControl() { this._checkParentType(); this._control = this.formDirective.addControl(this); - if (this.control.disabled && this.valueAccessor !.setDisabledState) { - this.valueAccessor !.setDisabledState !(true); + if (this.control.disabled && this.valueAccessor.setDisabledState) { + this.valueAccessor.setDisabledState(true); } this._added = true; } diff --git a/packages/forms/src/directives/reactive_directives/form_group_directive.ts b/packages/forms/src/directives/reactive_directives/form_group_directive.ts index 9b5ec7af0f..6ec1773e2d 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_directive.ts @@ -69,7 +69,7 @@ export class FormGroupDirective extends ControlContainer implements Form, private _oldForm: FormGroup; directives: FormControlName[] = []; - @Input('formGroup') form: FormGroup = null !; + @Input('formGroup') form: FormGroup = null; @Output() ngSubmit = new EventEmitter(); constructor( @@ -167,10 +167,10 @@ export class FormGroupDirective extends ControlContainer implements Form, private _updateValidators() { const sync = composeValidators(this._validators); - this.form.validator = Validators.compose([this.form.validator !, sync !]); + this.form.validator = Validators.compose([this.form.validator, sync]); const async = composeAsyncValidators(this._asyncValidators); - this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator !, async !]); + this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]); } private _checkFormPresent() { diff --git a/packages/forms/src/directives/reactive_directives/form_group_name.ts b/packages/forms/src/directives/reactive_directives/form_group_name.ts index 3d7904f155..3dca7c5a4c 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_name.ts @@ -166,7 +166,7 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy ngOnInit(): void { this._checkParentType(); - this.formDirective !.addFormArray(this); + this.formDirective.addFormArray(this); } ngOnDestroy(): void { @@ -175,19 +175,17 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy } } - get control(): FormArray { return this.formDirective !.getFormArray(this); } + get control(): FormArray { return this.formDirective.getFormArray(this); } - get formDirective(): FormGroupDirective|null { + get formDirective(): FormGroupDirective { return this._parent ? this._parent.formDirective : null; } get path(): string[] { return controlPath(this.name, this._parent); } - get validator(): ValidatorFn|null { return composeValidators(this._validators); } + get validator(): ValidatorFn { return composeValidators(this._validators); } - get asyncValidator(): AsyncValidatorFn|null { - return composeAsyncValidators(this._asyncValidators); - } + get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); } private _checkParentType(): void { if (_hasInvalidParent(this._parent)) { diff --git a/packages/forms/src/directives/select_control_value_accessor.ts b/packages/forms/src/directives/select_control_value_accessor.ts index 4c5c58dd4a..daf5d03727 100644 --- a/packages/forms/src/directives/select_control_value_accessor.ts +++ b/packages/forms/src/directives/select_control_value_accessor.ts @@ -15,7 +15,7 @@ export const SELECT_VALUE_ACCESSOR: Provider = { multi: true }; -function _buildValueString(id: string | null, value: any): string { +function _buildValueString(id: string, value: any): string { if (id == null) return `${value}`; if (value && typeof value === 'object') value = 'Object'; return `${id}: ${value}`.slice(0, 50); @@ -118,7 +118,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor { writeValue(value: any): void { this.value = value; - const id: string|null = this._getOptionId(value); + const id: string = this._getOptionId(value); if (id == null) { this._renderer.setElementProperty(this._elementRef.nativeElement, 'selectedIndex', -1); } @@ -142,7 +142,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor { _registerOption(): string { return (this._idCounter++).toString(); } /** @internal */ - _getOptionId(value: any): string|null { + _getOptionId(value: any): string { for (const id of Array.from(this._optionMap.keys())) { if (this._compareWith(this._optionMap.get(id), value)) return id; } diff --git a/packages/forms/src/directives/select_multiple_control_value_accessor.ts b/packages/forms/src/directives/select_multiple_control_value_accessor.ts index 37591b5e6f..960cea7a64 100644 --- a/packages/forms/src/directives/select_multiple_control_value_accessor.ts +++ b/packages/forms/src/directives/select_multiple_control_value_accessor.ts @@ -149,9 +149,9 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor } /** @internal */ - _getOptionId(value: any): string|null { + _getOptionId(value: any): string { for (const id of Array.from(this._optionMap.keys())) { - if (this._compareWith(this._optionMap.get(id) !._value, value)) return id; + if (this._compareWith(this._optionMap.get(id)._value, value)) return id; } return null; } @@ -159,7 +159,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor /** @internal */ _getOptionValue(valueString: string): any { const id: string = _extractId(valueString); - return this._optionMap.has(id) ? this._optionMap.get(id) !._value : valueString; + return this._optionMap.has(id) ? this._optionMap.get(id)._value : valueString; } } diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts index 8823b62818..c8342bd334 100644 --- a/packages/forms/src/directives/shared.ts +++ b/packages/forms/src/directives/shared.ts @@ -27,55 +27,55 @@ import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './valida export function controlPath(name: string, parent: ControlContainer): string[] { - return [...parent.path !, name]; + return [...parent.path, name]; } export function setUpControl(control: FormControl, dir: NgControl): void { if (!control) _throwError(dir, 'Cannot find control with'); if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with'); - control.validator = Validators.compose([control.validator !, dir.validator]); - control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]); - dir.valueAccessor !.writeValue(control.value); + control.validator = Validators.compose([control.validator, dir.validator]); + control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]); + dir.valueAccessor.writeValue(control.value); // view -> model - dir.valueAccessor !.registerOnChange((newValue: any) => { + dir.valueAccessor.registerOnChange((newValue: any) => { dir.viewToModelUpdate(newValue); control.markAsDirty(); control.setValue(newValue, {emitModelToViewChange: false}); }); // touched - dir.valueAccessor !.registerOnTouched(() => control.markAsTouched()); + dir.valueAccessor.registerOnTouched(() => control.markAsTouched()); control.registerOnChange((newValue: any, emitModelEvent: boolean) => { // control -> view - dir.valueAccessor !.writeValue(newValue); + dir.valueAccessor.writeValue(newValue); // control -> ngModel if (emitModelEvent) dir.viewToModelUpdate(newValue); }); - if (dir.valueAccessor !.setDisabledState) { + if (dir.valueAccessor.setDisabledState) { control.registerOnDisabledChange( - (isDisabled: boolean) => { dir.valueAccessor !.setDisabledState !(isDisabled); }); + (isDisabled: boolean) => { dir.valueAccessor.setDisabledState(isDisabled); }); } // re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4 dir._rawValidators.forEach((validator: Validator | ValidatorFn) => { if ((validator).registerOnValidatorChange) - (validator).registerOnValidatorChange !(() => control.updateValueAndValidity()); + (validator).registerOnValidatorChange(() => control.updateValueAndValidity()); }); dir._rawAsyncValidators.forEach((validator: AsyncValidator | AsyncValidatorFn) => { if ((validator).registerOnValidatorChange) - (validator).registerOnValidatorChange !(() => control.updateValueAndValidity()); + (validator).registerOnValidatorChange(() => control.updateValueAndValidity()); }); } export function cleanUpControl(control: FormControl, dir: NgControl) { - dir.valueAccessor !.registerOnChange(() => _noControlError(dir)); - dir.valueAccessor !.registerOnTouched(() => _noControlError(dir)); + dir.valueAccessor.registerOnChange(() => _noControlError(dir)); + dir.valueAccessor.registerOnTouched(() => _noControlError(dir)); dir._rawValidators.forEach((validator: any) => { if (validator.registerOnValidatorChange) { @@ -105,9 +105,9 @@ function _noControlError(dir: NgControl) { function _throwError(dir: AbstractControlDirective, message: string): void { let messageEnd: string; - if (dir.path !.length > 1) { - messageEnd = `path: '${dir.path!.join(' -> ')}'`; - } else if (dir.path ![0]) { + if (dir.path.length > 1) { + messageEnd = `path: '${dir.path.join(' -> ')}'`; + } else if (dir.path[0]) { messageEnd = `name: '${dir.path}'`; } else { messageEnd = 'unspecified name attribute'; @@ -115,12 +115,11 @@ function _throwError(dir: AbstractControlDirective, message: string): void { throw new Error(`${message} ${messageEnd}`); } -export function composeValidators(validators: Array): ValidatorFn|null { +export function composeValidators(validators: Array): ValidatorFn { return validators != null ? Validators.compose(validators.map(normalizeValidator)) : null; } -export function composeAsyncValidators(validators: Array): AsyncValidatorFn| - null { +export function composeAsyncValidators(validators: Array): AsyncValidatorFn { return validators != null ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) : null; } @@ -148,12 +147,12 @@ export function isBuiltInAccessor(valueAccessor: ControlValueAccessor): boolean // TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented export function selectValueAccessor( - dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor|null { + dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor { if (!valueAccessors) return null; - let defaultAccessor: ControlValueAccessor|undefined = undefined; - let builtinAccessor: ControlValueAccessor|undefined = undefined; - let customAccessor: ControlValueAccessor|undefined = undefined; + let defaultAccessor: ControlValueAccessor; + let builtinAccessor: ControlValueAccessor; + let customAccessor: ControlValueAccessor; valueAccessors.forEach((v: ControlValueAccessor) => { if (v.constructor === DefaultValueAccessor) { defaultAccessor = v; diff --git a/packages/forms/src/form_builder.ts b/packages/forms/src/form_builder.ts index b10797c04d..1384ea1d38 100644 --- a/packages/forms/src/form_builder.ts +++ b/packages/forms/src/form_builder.ts @@ -39,7 +39,7 @@ export class FormBuilder { * * See the {@link FormGroup} constructor for more details. */ - group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any}|null = null): FormGroup { + group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): FormGroup { const controls = this._reduceControls(controlsConfig); const validator: ValidatorFn = extra != null ? extra['validator'] : null; const asyncValidator: AsyncValidatorFn = extra != null ? extra['asyncValidator'] : null; @@ -54,8 +54,8 @@ export class FormBuilder { * */ control( - formState: Object, validator?: ValidatorFn|ValidatorFn[]|null, - asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl { + formState: Object, validator: ValidatorFn|ValidatorFn[] = null, + asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): FormControl { return new FormControl(formState, validator, asyncValidator); } @@ -64,8 +64,8 @@ export class FormBuilder { * configuration, with the given optional `validator` and `asyncValidator`. */ array( - controlsConfig: any[], validator?: ValidatorFn|null, - asyncValidator?: AsyncValidatorFn|null): FormArray { + controlsConfig: any[], validator: ValidatorFn = null, + asyncValidator: AsyncValidatorFn = null): FormArray { const controls = controlsConfig.map(c => this._createControl(c)); return new FormArray(controls, validator, asyncValidator); } diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index d072b7ed05..cd00942f60 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -42,7 +42,7 @@ function _find(control: AbstractControl, path: Array| string, del } if (path instanceof Array && (path.length === 0)) return null; - return (>path).reduce((v: AbstractControl, name) => { + return (>path).reduce((v, name) => { if (v instanceof FormGroup) { return v.controls[name] || null; } @@ -55,14 +55,13 @@ function _find(control: AbstractControl, path: Array| string, del }, control); } -function coerceToValidator(validator?: ValidatorFn | ValidatorFn[] | null): ValidatorFn|null { - return Array.isArray(validator) ? composeValidators(validator) : validator || null; +function coerceToValidator(validator: ValidatorFn | ValidatorFn[]): ValidatorFn { + return Array.isArray(validator) ? composeValidators(validator) : validator; } -function coerceToAsyncValidator(asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): - AsyncValidatorFn|null { - return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) : - asyncValidator || null; +function coerceToAsyncValidator(asyncValidator: AsyncValidatorFn | AsyncValidatorFn[]): + AsyncValidatorFn { + return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) : asyncValidator; } /** @@ -91,7 +90,7 @@ export abstract class AbstractControl { private _parent: FormGroup|FormArray; private _asyncValidationSubscription: any; - constructor(public validator: ValidatorFn|null, public asyncValidator: AsyncValidatorFn|null) {} + constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {} /** * The value of the control. @@ -210,7 +209,7 @@ export abstract class AbstractControl { * Sets the synchronous validators that are active on this control. Calling * this will overwrite any existing sync validators. */ - setValidators(newValidator: ValidatorFn|ValidatorFn[]|null): void { + setValidators(newValidator: ValidatorFn|ValidatorFn[]): void { this.validator = coerceToValidator(newValidator); } @@ -323,7 +322,7 @@ export abstract class AbstractControl { this._statusChanges.emit(this._status); } - this._updateAncestors(!!onlySelf); + this._updateAncestors(onlySelf); this._onDisabledChange.forEach((changeFn) => changeFn(true)); } @@ -339,7 +338,7 @@ export abstract class AbstractControl { this._forEachChild((control: AbstractControl) => { control.enable({onlySelf: true}); }); this.updateValueAndValidity({onlySelf: true, emitEvent}); - this._updateAncestors(!!onlySelf); + this._updateAncestors(onlySelf); this._onDisabledChange.forEach((changeFn) => changeFn(false)); } @@ -410,7 +409,7 @@ export abstract class AbstractControl { return this.validator ? this.validator(this) : null; } - private _runAsyncValidator(emitEvent?: boolean): void { + private _runAsyncValidator(emitEvent: boolean): void { if (this.asyncValidator) { this._status = PENDING; const obs = toObservable(this.asyncValidator(this)); @@ -466,7 +465,7 @@ export abstract class AbstractControl { * * * `this.form.get(['person', 'name']);` */ - get(path: Array|string): AbstractControl|null { return _find(this, path, '.'); } + get(path: Array|string): AbstractControl { return _find(this, path, '.'); } /** * Returns true if the control with the given path has the error specified. Otherwise @@ -474,7 +473,7 @@ export abstract class AbstractControl { * * If no path is given, it checks for the error on the present control. */ - getError(errorCode: string, path?: string[]): any { + getError(errorCode: string, path: string[] = null): any { const control = path ? this.get(path) : this; return control && control._errors ? control._errors[errorCode] : null; } @@ -485,7 +484,9 @@ export abstract class AbstractControl { * * If no path is given, it checks for the error on the present control. */ - hasError(errorCode: string, path?: string[]): boolean { return !!this.getError(errorCode, path); } + hasError(errorCode: string, path: string[] = null): boolean { + return !!this.getError(errorCode, path); + } /** * Retrieves the top-level ancestor of this control. @@ -634,8 +635,8 @@ export class FormControl extends AbstractControl { _onChange: Function[] = []; constructor( - formState: any = null, validator?: ValidatorFn|ValidatorFn[]|null, - asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) { + formState: any = null, validator: ValidatorFn|ValidatorFn[] = null, + asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null) { super(coerceToValidator(validator), coerceToAsyncValidator(asyncValidator)); this._applyFormState(formState); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); @@ -830,9 +831,9 @@ export class FormControl extends AbstractControl { */ export class FormGroup extends AbstractControl { constructor( - public controls: {[key: string]: AbstractControl}, validator?: ValidatorFn|null, - asyncValidator?: AsyncValidatorFn|null) { - super(validator || null, asyncValidator || null); + public controls: {[key: string]: AbstractControl}, validator: ValidatorFn = null, + asyncValidator: AsyncValidatorFn = null) { + super(validator, asyncValidator); this._initObservables(); this._setUpControls(); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); @@ -1136,9 +1137,9 @@ export class FormGroup extends AbstractControl { */ export class FormArray extends AbstractControl { constructor( - public controls: AbstractControl[], validator?: ValidatorFn|null, - asyncValidator?: AsyncValidatorFn|null) { - super(validator || null, asyncValidator || null); + public controls: AbstractControl[], validator: ValidatorFn = null, + asyncValidator: AsyncValidatorFn = null) { + super(validator, asyncValidator); this._initObservables(); this._setUpControls(); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); diff --git a/packages/forms/src/validators.ts b/packages/forms/src/validators.ts index de355fcbd4..78cbee570f 100644 --- a/packages/forms/src/validators.ts +++ b/packages/forms/src/validators.ts @@ -143,11 +143,9 @@ export class Validators { * Compose multiple validators into a single function that returns the union * of the individual error maps. */ - static compose(validators: null): null; - static compose(validators: (ValidatorFn|null|undefined)[]): ValidatorFn|null; - static compose(validators: (ValidatorFn|null|undefined)[]|null): ValidatorFn|null { + static compose(validators: ValidatorFn[]): ValidatorFn { if (!validators) return null; - const presentValidators: ValidatorFn[] = validators.filter(isPresent) as any; + const presentValidators = validators.filter(isPresent); if (presentValidators.length == 0) return null; return function(control: AbstractControl) { @@ -155,9 +153,9 @@ export class Validators { }; } - static composeAsync(validators: (AsyncValidatorFn|null)[]): AsyncValidatorFn|null { + static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn { if (!validators) return null; - const presentValidators: AsyncValidatorFn[] = validators.filter(isPresent) as any; + const presentValidators = validators.filter(isPresent); if (presentValidators.length == 0) return null; return function(control: AbstractControl) { @@ -190,7 +188,7 @@ function _executeAsyncValidators(control: AbstractControl, validators: AsyncVali function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null { const res: {[key: string]: any} = arrayOfErrors.reduce((res: ValidationErrors | null, errors: ValidationErrors | null) => { - return errors != null ? {...res !, ...errors} : res !; + return errors != null ? {...res, ...errors} : res; }, {}); return Object.keys(res).length === 0 ? null : res; } diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index 00001a6fff..642886c991 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -28,7 +28,7 @@ class CustomValidatorDirective implements Validator { function asyncValidator(expected: any, timeout = 0) { return (c: AbstractControl): any => { - let resolve: (result: any) => void = undefined !; + let resolve: (result: any) => void; const promise = new Promise(res => { resolve = res; }); const res = c.value != expected ? {'async': true} : null; if (timeout == 0) { @@ -44,7 +44,7 @@ export function main() { describe('Form Directives', () => { let defaultAccessor: DefaultValueAccessor; - beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null !, null !, null !); }); + beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null, null); }); describe('shared', () => { describe('selectValueAccessor', () => { @@ -59,42 +59,42 @@ export function main() { () => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); }); it('should return checkbox accessor when provided', () => { - const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); + const checkboxAccessor = new CheckboxControlValueAccessor(null, null); expect(selectValueAccessor(dir, [ defaultAccessor, checkboxAccessor ])).toEqual(checkboxAccessor); }); it('should return select accessor when provided', () => { - const selectAccessor = new SelectControlValueAccessor(null !, null !); + const selectAccessor = new SelectControlValueAccessor(null, null); expect(selectValueAccessor(dir, [ defaultAccessor, selectAccessor ])).toEqual(selectAccessor); }); it('should return select multiple accessor when provided', () => { - const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null !, null !); + const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null, null); expect(selectValueAccessor(dir, [ defaultAccessor, selectMultipleAccessor ])).toEqual(selectMultipleAccessor); }); it('should throw when more than one build-in accessor is provided', () => { - const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); - const selectAccessor = new SelectControlValueAccessor(null !, null !); + const checkboxAccessor = new CheckboxControlValueAccessor(null, null); + const selectAccessor = new SelectControlValueAccessor(null, null); expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError(); }); it('should return custom accessor when provided', () => { const customAccessor = new SpyValueAccessor(); - const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); + const checkboxAccessor = new CheckboxControlValueAccessor(null, null); expect(selectValueAccessor(dir, [defaultAccessor, customAccessor, checkboxAccessor])) .toEqual(customAccessor); }); it('should return custom accessor when provided with select multiple', () => { const customAccessor = new SpyValueAccessor(); - const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null !, null !); + const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null, null); expect(selectValueAccessor( dir, [defaultAccessor, customAccessor, selectMultipleAccessor])) .toEqual(customAccessor); @@ -110,13 +110,13 @@ export function main() { it('should compose functions', () => { const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true}); const dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true}); - const v = composeValidators([dummy1, dummy2]) !; + const v = composeValidators([dummy1, dummy2]); expect(v(new FormControl(''))).toEqual({'dummy1': true, 'dummy2': true}); }); it('should compose validator directives', () => { const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true}); - const v = composeValidators([dummy1, new CustomValidatorDirective()]) !; + const v = composeValidators([dummy1, new CustomValidatorDirective()]); expect(v(new FormControl(''))).toEqual({'dummy1': true, 'custom': true}); }); }); @@ -168,7 +168,7 @@ export function main() { describe('addControl', () => { it('should throw when no control found', () => { - const dir = new FormControlName(form, null !, null !, [defaultAccessor]); + const dir = new FormControlName(form, null, null, [defaultAccessor]); dir.name = 'invalidName'; expect(() => form.addControl(dir)) @@ -176,7 +176,7 @@ export function main() { }); it('should throw for a named control when no value accessor', () => { - const dir = new FormControlName(form, null !, null !, null !); + const dir = new FormControlName(form, null, null, null); dir.name = 'login'; expect(() => form.addControl(dir)) @@ -184,8 +184,8 @@ export function main() { }); it('should throw when no value accessor with path', () => { - const group = new FormGroupName(form, null !, null !); - const dir = new FormControlName(group, null !, null !, null !); + const group = new FormGroupName(form, null, null); + const dir = new FormControlName(group, null, null, null); group.name = 'passwords'; dir.name = 'password'; @@ -315,7 +315,7 @@ export function main() { personControlGroupDir = new NgModelGroup(form, [], []); personControlGroupDir.name = 'person'; - loginControlDir = new NgModel(personControlGroupDir, null !, null !, [defaultAccessor]); + loginControlDir = new NgModel(personControlGroupDir, null, null, [defaultAccessor]); loginControlDir.name = 'login'; loginControlDir.valueAccessor = new DummyControlValueAccessor(); }); @@ -534,7 +534,7 @@ export function main() { beforeEach(() => { ngModel = new NgModel( - null !, [Validators.required], [asyncValidator('expected')], [defaultAccessor]); + null, [Validators.required], [asyncValidator('expected')], [defaultAccessor]); ngModel.valueAccessor = new DummyControlValueAccessor(); control = ngModel.control; }); @@ -566,7 +566,7 @@ export function main() { }); it('should throw when no value accessor with named control', () => { - const namedDir = new NgModel(null !, null !, null !, null !); + const namedDir = new NgModel(null, null, null, null); namedDir.name = 'one'; expect(() => namedDir.ngOnChanges({})) @@ -574,7 +574,7 @@ export function main() { }); it('should throw when no value accessor with unnamed control', () => { - const unnamedDir = new NgModel(null !, null !, null !, null !); + const unnamedDir = new NgModel(null, null, null, null); expect(() => unnamedDir.ngOnChanges({})) .toThrowError( diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts index 2b94b80ec7..66976e9e74 100644 --- a/packages/forms/test/form_array_spec.ts +++ b/packages/forms/test/form_array_spec.ts @@ -15,7 +15,7 @@ import {Validators} from '../src/validators'; export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: AbstractControl) => { - let resolve: (result: any) => void = undefined !; + let resolve: (result: any) => void; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -89,7 +89,7 @@ export function main() { new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}), new FormArray([new FormControl('v4'), new FormControl('v5')]) ]); - a.at(0).get('c3') !.disable(); + a.at(0).get('c3').disable(); (a.at(1) as FormArray).at(1).disable(); expect(a.getRawValue()).toEqual([{'c2': 'v2', 'c3': 'v3'}, ['v4', 'v5']]); @@ -693,7 +693,7 @@ export function main() { describe('get', () => { it('should return null when path is null', () => { const g = new FormGroup({}); - expect(g.get(null !)).toEqual(null); + expect(g.get(null)).toEqual(null); }); it('should return null when path is empty', () => { @@ -712,23 +712,23 @@ export function main() { 'nested': new FormGroup({'two': new FormControl('222')}) }); - expect(g.get(['one']) !.value).toEqual('111'); - expect(g.get('one') !.value).toEqual('111'); - expect(g.get(['nested', 'two']) !.value).toEqual('222'); - expect(g.get('nested.two') !.value).toEqual('222'); + expect(g.get(['one']).value).toEqual('111'); + expect(g.get('one').value).toEqual('111'); + expect(g.get(['nested', 'two']).value).toEqual('222'); + expect(g.get('nested.two').value).toEqual('222'); }); it('should return an element of an array', () => { const g = new FormGroup({'array': new FormArray([new FormControl('111')])}); - expect(g.get(['array', 0]) !.value).toEqual('111'); + expect(g.get(['array', 0]).value).toEqual('111'); }); }); describe('asyncValidator', () => { it('should run the async validator', fakeAsync(() => { const c = new FormControl('value'); - const g = new FormArray([c], null !, asyncValidator('expected')); + const g = new FormArray([c], null, asyncValidator('expected')); expect(g.pending).toEqual(true); @@ -793,10 +793,10 @@ export function main() { }); expect(g.valid).toBe(false); - g.get('nested') !.disable(); + g.get('nested').disable(); expect(g.valid).toBe(true); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.valid).toBe(false); }); @@ -805,36 +805,36 @@ export function main() { {nested: new FormArray([new FormControl('one')]), two: new FormControl('two')}); expect(g.value).toEqual({'nested': ['one'], 'two': 'two'}); - g.get('nested') !.disable(); + g.get('nested').disable(); expect(g.value).toEqual({'two': 'two'}); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.value).toEqual({'nested': ['one'], 'two': 'two'}); }); it('should ignore disabled controls when determining dirtiness', () => { const g = new FormGroup({nested: a, two: new FormControl('two')}); - g.get(['nested', 0]) !.markAsDirty(); + g.get(['nested', 0]).markAsDirty(); expect(g.dirty).toBe(true); - g.get('nested') !.disable(); - expect(g.get('nested') !.dirty).toBe(true); + g.get('nested').disable(); + expect(g.get('nested').dirty).toBe(true); expect(g.dirty).toEqual(false); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.dirty).toEqual(true); }); it('should ignore disabled controls when determining touched state', () => { const g = new FormGroup({nested: a, two: new FormControl('two')}); - g.get(['nested', 0]) !.markAsTouched(); + g.get(['nested', 0]).markAsTouched(); expect(g.touched).toBe(true); - g.get('nested') !.disable(); - expect(g.get('nested') !.touched).toBe(true); + g.get('nested').disable(); + expect(g.get('nested').touched).toBe(true); expect(g.touched).toEqual(false); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.touched).toEqual(true); }); @@ -901,7 +901,7 @@ export function main() { }); it('should clear out async array errors when disabled', fakeAsync(() => { - const arr = new FormArray([new FormControl()], null !, asyncValidator('expected')); + const arr = new FormArray([new FormControl()], null, asyncValidator('expected')); tick(); expect(arr.errors).toEqual({'async': true}); @@ -914,7 +914,7 @@ export function main() { })); it('should re-populate async array errors when enabled from a child', fakeAsync(() => { - const arr = new FormArray([new FormControl()], null !, asyncValidator('expected')); + const arr = new FormArray([new FormControl()], null, asyncValidator('expected')); tick(); expect(arr.errors).toEqual({'async': true}); @@ -988,7 +988,7 @@ export function main() { }); it('should remove control if new control is null', () => { - a.setControl(0, null !); + a.setControl(0, null); expect(a.controls[0]).not.toBeDefined(); expect(a.value).toEqual([]); }); diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index 4fa4d9f3a2..e52c23bbac 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -16,7 +16,7 @@ import {FormArray} from '../src/model'; export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: FormControl) => { - let resolve: (result: any) => void = undefined !; + let resolve: (result: any) => void; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -49,7 +49,7 @@ export function main() { describe('boxed values', () => { it('should support valid boxed values on creation', () => { - const c = new FormControl({value: 'some val', disabled: true}, null !, null !); + const c = new FormControl({value: 'some val', disabled: true}, null, null); expect(c.disabled).toBe(true); expect(c.value).toBe('some val'); expect(c.status).toBe('DISABLED'); @@ -63,13 +63,13 @@ export function main() { }); it('should not treat objects as boxed values if they have more than two props', () => { - const c = new FormControl({value: '', disabled: true, test: 'test'}, null !, null !); + const c = new FormControl({value: '', disabled: true, test: 'test'}, null, null); expect(c.value).toEqual({value: '', disabled: true, test: 'test'}); expect(c.disabled).toBe(false); }); it('should not treat objects as boxed values if disabled is missing', () => { - const c = new FormControl({value: '', test: 'test'}, null !, null !); + const c = new FormControl({value: '', test: 'test'}, null, null); expect(c.value).toEqual({value: '', test: 'test'}); expect(c.disabled).toBe(false); }); @@ -156,7 +156,7 @@ export function main() { describe('asyncValidator', () => { it('should run validator with the initial value', fakeAsync(() => { - const c = new FormControl('value', null !, asyncValidator('expected')); + const c = new FormControl('value', null, asyncValidator('expected')); tick(); expect(c.valid).toEqual(false); @@ -164,7 +164,7 @@ export function main() { })); it('should support validators returning observables', fakeAsync(() => { - const c = new FormControl('value', null !, asyncValidatorReturningObservable); + const c = new FormControl('value', null, asyncValidatorReturningObservable); tick(); expect(c.valid).toEqual(false); @@ -172,7 +172,7 @@ export function main() { })); it('should rerun the validator when the value changes', fakeAsync(() => { - const c = new FormControl('value', null !, asyncValidator('expected')); + const c = new FormControl('value', null, asyncValidator('expected')); c.setValue('expected'); tick(); @@ -193,7 +193,7 @@ export function main() { })); it('should mark the control as pending while running the async validation', fakeAsync(() => { - const c = new FormControl('', null !, asyncValidator('expected')); + const c = new FormControl('', null, asyncValidator('expected')); expect(c.pending).toEqual(true); @@ -204,7 +204,7 @@ export function main() { it('should only use the latest async validation run', fakeAsync(() => { const c = new FormControl( - '', null !, asyncValidator('expected', {'long': 200, 'expected': 100})); + '', null, asyncValidator('expected', {'long': 200, 'expected': 100})); c.setValue('long'); c.setValue('expected'); @@ -216,14 +216,14 @@ export function main() { it('should support arrays of async validator functions if passed', fakeAsync(() => { const c = - new FormControl('value', null !, [asyncValidator('expected'), otherAsyncValidator]); + new FormControl('value', null, [asyncValidator('expected'), otherAsyncValidator]); tick(); expect(c.errors).toEqual({'async': true, 'other': true}); })); it('should add single async validator', fakeAsync(() => { - const c = new FormControl('value', null !); + const c = new FormControl('value', null); c.setAsyncValidators(asyncValidator('expected')); expect(c.asyncValidator).not.toEqual(null); @@ -235,7 +235,7 @@ export function main() { })); it('should add async validator from array', fakeAsync(() => { - const c = new FormControl('value', null !); + const c = new FormControl('value', null); c.setAsyncValidators([asyncValidator('expected')]); expect(c.asyncValidator).not.toEqual(null); @@ -634,7 +634,8 @@ export function main() { tick(); expect(log).toEqual([ - 'value: \'\'', + '' + + 'value: \'\'', 'status: \'INVALID\'', 'value: \'nonEmpty\'', 'status: \'PENDING\'', @@ -934,7 +935,7 @@ export function main() { }); it('should clear out async errors when disabled', fakeAsync(() => { - const c = new FormControl('', null !, asyncValidator('expected')); + const c = new FormControl('', null, asyncValidator('expected')); tick(); expect(c.errors).toEqual({'async': true}); diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index aa62b78c9c..dd2d222d10 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -15,7 +15,7 @@ import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@a export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: AbstractControl) => { - let resolve: (result: any) => void = undefined !; + let resolve: (result: any) => void; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -70,7 +70,7 @@ export function main() { 'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}), 'array': new FormArray([new FormControl('v4'), new FormControl('v5')]) }); - fg.get('group') !.get('c3') !.disable(); + fg.get('group').get('c3').disable(); (fg.get('array') as FormArray).at(1).disable(); expect(fg.getRawValue()) @@ -690,7 +690,7 @@ export function main() { describe('asyncValidator', () => { it('should run the async validator', fakeAsync(() => { const c = new FormControl('value'); - const g = new FormGroup({'one': c}, null !, asyncValidator('expected')); + const g = new FormGroup({'one': c}, null, asyncValidator('expected')); expect(g.pending).toEqual(true); @@ -701,7 +701,7 @@ export function main() { })); it('should set the parent group\'s status to pending', fakeAsync(() => { - const c = new FormControl('value', null !, asyncValidator('expected')); + const c = new FormControl('value', null, asyncValidator('expected')); const g = new FormGroup({'one': c}); expect(g.pending).toEqual(true); @@ -713,13 +713,13 @@ export function main() { it('should run the parent group\'s async validator when children are pending', fakeAsync(() => { - const c = new FormControl('value', null !, asyncValidator('expected')); - const g = new FormGroup({'one': c}, null !, asyncValidator('expected')); + const c = new FormControl('value', null, asyncValidator('expected')); + const g = new FormGroup({'one': c}, null, asyncValidator('expected')); tick(1); expect(g.errors).toEqual({'async': true}); - expect(g.get('one') !.errors).toEqual({'async': true}); + expect(g.get('one').errors).toEqual({'async': true}); })); }); @@ -772,10 +772,10 @@ export function main() { }); expect(g.valid).toBe(false); - g.get('nested') !.disable(); + g.get('nested').disable(); expect(g.valid).toBe(true); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.valid).toBe(false); }); @@ -784,10 +784,10 @@ export function main() { {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); expect(g.value).toEqual({'nested': {'one': 'one'}, 'two': 'two'}); - g.get('nested') !.disable(); + g.get('nested').disable(); expect(g.value).toEqual({'two': 'two'}); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.value).toEqual({'nested': {'one': 'one'}, 'two': 'two'}); }); @@ -795,13 +795,13 @@ export function main() { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one'), two: new FormControl('two')})}); - g.get('nested.two') !.disable(); + g.get('nested.two').disable(); expect(g.value).toEqual({nested: {one: 'one'}}); - g.get('nested') !.disable(); + g.get('nested').disable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); }); @@ -809,38 +809,38 @@ export function main() { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one'), two: new FormControl('two')})}); - g.get('nested.two') !.disable(); + g.get('nested.two').disable(); expect(g.value).toEqual({nested: {one: 'one'}}); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); }); it('should ignore disabled controls when determining dirtiness', () => { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); - g.get('nested.one') !.markAsDirty(); + g.get('nested.one').markAsDirty(); expect(g.dirty).toBe(true); - g.get('nested') !.disable(); - expect(g.get('nested') !.dirty).toBe(true); + g.get('nested').disable(); + expect(g.get('nested').dirty).toBe(true); expect(g.dirty).toEqual(false); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.dirty).toEqual(true); }); it('should ignore disabled controls when determining touched state', () => { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); - g.get('nested.one') !.markAsTouched(); + g.get('nested.one').markAsTouched(); expect(g.touched).toBe(true); - g.get('nested') !.disable(); - expect(g.get('nested') !.touched).toBe(true); + g.get('nested').disable(); + expect(g.get('nested').touched).toBe(true); expect(g.touched).toEqual(false); - g.get('nested') !.enable(); + g.get('nested').enable(); expect(g.touched).toEqual(true); }); @@ -907,8 +907,7 @@ export function main() { }); it('should clear out async group errors when disabled', fakeAsync(() => { - const g = - new FormGroup({'one': new FormControl()}, null !, asyncValidator('expected')); + const g = new FormGroup({'one': new FormControl()}, null, asyncValidator('expected')); tick(); expect(g.errors).toEqual({'async': true}); @@ -921,8 +920,7 @@ export function main() { })); it('should re-populate async group errors when enabled from a child', fakeAsync(() => { - const g = - new FormGroup({'one': new FormControl()}, null !, asyncValidator('expected')); + const g = new FormGroup({'one': new FormControl()}, null, asyncValidator('expected')); tick(); expect(g.errors).toEqual({'async': true}); @@ -1030,7 +1028,7 @@ export function main() { }); it('should remove control if new control is null', () => { - g.setControl('one', null !); + g.setControl('one', null); expect(g.controls['one']).not.toBeDefined(); expect(g.value).toEqual({}); }); diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts index 1dfd3c63d3..8d5ba36e43 100644 --- a/packages/forms/test/reactive_integration_spec.ts +++ b/packages/forms/test/reactive_integration_spec.ts @@ -166,7 +166,7 @@ export function main() { }); fixture.componentInstance.form = form; fixture.detectChanges(); - expect(form.get('login') !.errors).toEqual({required: true}); + expect(form.get('login').errors).toEqual({required: true}); const newForm = new FormGroup({ 'login': new FormControl(''), @@ -177,7 +177,7 @@ export function main() { fixture.componentInstance.form = newForm; fixture.detectChanges(); - expect(newForm.get('login') !.errors).toEqual({required: true}); + expect(newForm.get('login').errors).toEqual({required: true}); }); it('should pick up dir validators from nested form groups', () => { @@ -188,7 +188,7 @@ export function main() { }); fixture.componentInstance.form = form; fixture.detectChanges(); - expect(form.get('signin') !.valid).toBe(false); + expect(form.get('signin').valid).toBe(false); const newForm = new FormGroup({ 'signin': @@ -197,7 +197,7 @@ export function main() { fixture.componentInstance.form = newForm; fixture.detectChanges(); - expect(form.get('signin') !.valid).toBe(false); + expect(form.get('signin').valid).toBe(false); }); it('should strip named controls that are not found', () => { @@ -373,7 +373,7 @@ export function main() { it('should throw an error if compareWith is not a function', () => { const fixture = initTest(FormControlSelectWithCompareFn); - fixture.componentInstance.compareFn = null !; + fixture.componentInstance.compareFn = null; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -412,7 +412,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(FormControlSelectMultipleWithCompareFn); - fixture.componentInstance.compareFn = null !; + fixture.componentInstance.compareFn = null; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -623,7 +623,7 @@ export function main() { it('should emit ngSubmit event with the original submit event on submit', () => { const fixture = initTest(FormGroupComp); fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')}); - fixture.componentInstance.event = null !; + fixture.componentInstance.event = null; fixture.detectChanges(); const formEl = fixture.debugElement.query(By.css('form')).nativeElement; @@ -739,7 +739,7 @@ export function main() { it('should work with single fields and async validators', fakeAsync(() => { const fixture = initTest(FormControlComp); - const control = new FormControl('', null !, uniqLoginAsyncValidator('good')); + const control = new FormControl('', null, uniqLoginAsyncValidator('good')); fixture.debugElement.componentInstance.control = control; fixture.detectChanges(); @@ -995,10 +995,10 @@ export function main() { fixture.detectChanges(); // view -> model - expect(form.get('food') !.value).toEqual('chicken'); + expect(form.get('food').value).toEqual('chicken'); expect(inputs[1].nativeElement.checked).toEqual(false); - form.get('food') !.setValue('fish'); + form.get('food').setValue('fish'); fixture.detectChanges(); // programmatic change -> view @@ -1039,16 +1039,16 @@ export function main() { fixture.componentInstance.form = form; fixture.detectChanges(); - form.get('food') !.setValue(null); + form.get('food').setValue(null); fixture.detectChanges(); const inputs = fixture.debugElement.queryAll(By.css('input')); expect(inputs[0].nativeElement.checked).toEqual(false); - form.get('food') !.setValue('chicken'); + form.get('food').setValue('chicken'); fixture.detectChanges(); - form.get('food') !.setValue(undefined); + form.get('food').setValue(undefined); fixture.detectChanges(); expect(inputs[0].nativeElement.checked).toEqual(false); }); @@ -1139,8 +1139,8 @@ export function main() { fixture.detectChanges(); // view -> model - expect(form.get('food') !.value).toEqual('chicken'); - expect(form.get('nested.food') !.value).toEqual('fish'); + expect(form.get('food').value).toEqual('chicken'); + expect(form.get('nested.food').value).toEqual('fish'); expect(inputs[1].nativeElement.checked).toEqual(false); expect(inputs[2].nativeElement.checked).toEqual(false); @@ -1161,7 +1161,7 @@ export function main() { expect(inputs[2].nativeElement.disabled).toEqual(false); expect(inputs[3].nativeElement.disabled).toEqual(false); - form.get('food') !.disable(); + form.get('food').disable(); expect(inputs[0].nativeElement.disabled).toEqual(true); expect(inputs[1].nativeElement.disabled).toEqual(true); expect(inputs[2].nativeElement.disabled).toEqual(false); @@ -1267,9 +1267,9 @@ export function main() { expect(form.value).toEqual({'login': 'bb'}); // custom validator - expect(form.get('login') !.errors).toEqual({'err': true}); + expect(form.get('login').errors).toEqual({'err': true}); form.setValue({login: 'expected'}); - expect(form.get('login') !.errors).toEqual(null); + expect(form.get('login').errors).toEqual(null); }); it('should support non builtin input elements that fire a change event without a \'target\' property', @@ -1295,7 +1295,7 @@ export function main() { }); fixture.detectChanges(); expect(fixture.componentInstance.form.status).toEqual('DISABLED'); - expect(fixture.componentInstance.form.get('login') !.status).toEqual('DISABLED'); + expect(fixture.componentInstance.form.get('login').status).toEqual('DISABLED'); }); it('should support custom accessors without setDisabledState - formControlDirective', @@ -1539,9 +1539,9 @@ export function main() { .toEqual(pattern.nativeElement.getAttribute('pattern')); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null !; - fixture.componentInstance.maxLen = null !; - fixture.componentInstance.pattern = null !; + fixture.componentInstance.minLen = null; + fixture.componentInstance.maxLen = null; + fixture.componentInstance.pattern = null; fixture.detectChanges(); expect(form.hasError('required', ['login'])).toEqual(false); @@ -1581,9 +1581,9 @@ export function main() { fixture.detectChanges(); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null !; - fixture.componentInstance.maxLen = null !; - fixture.componentInstance.pattern = null !; + fixture.componentInstance.minLen = null; + fixture.componentInstance.maxLen = null; + fixture.componentInstance.pattern = null; fixture.detectChanges(); expect(newForm.hasError('required', ['login'])).toEqual(false); @@ -1681,7 +1681,7 @@ export function main() { const fixture = initTest(FormControlComp); const resultArr: number[] = []; fixture.componentInstance.control = - new FormControl('', null !, observableValidator(resultArr)); + new FormControl('', null, observableValidator(resultArr)); fixture.detectChanges(); tick(100); diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts index 87fd77bdf8..4ae9c7cd08 100644 --- a/packages/forms/test/template_integration_spec.ts +++ b/packages/forms/test/template_integration_spec.ts @@ -107,9 +107,9 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name') !.value).toEqual({first: 'Nancy', last: 'Drew'}); - expect(form.control.get('name.first') !.value).toEqual('Nancy'); - expect(form.control.get('email') !.value).toEqual('some email'); + expect(form.control.get('name').value).toEqual({first: 'Nancy', last: 'Drew'}); + expect(form.control.get('name.first').value).toEqual('Nancy'); + expect(form.control.get('email').value).toEqual('some email'); })); it('should remove controls and control groups from form control model', fakeAsync(() => { @@ -121,7 +121,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('email') !.value).toEqual('some email'); + expect(form.control.get('email').value).toEqual('some email'); expect(form.value).toEqual({name: {first: 'Nancy'}, email: 'some email'}); // should remove individual control successfully @@ -132,8 +132,8 @@ export function main() { expect(form.control.get('email')).toBe(null); expect(form.value).toEqual({name: {first: 'Nancy'}}); - expect(form.control.get('name') !.value).toEqual({first: 'Nancy'}); - expect(form.control.get('name.first') !.value).toEqual('Nancy'); + expect(form.control.get('name').value).toEqual({first: 'Nancy'}); + expect(form.control.get('name.first').value).toEqual('Nancy'); // should remove form group successfully fixture.componentInstance.groupShowing = false; @@ -228,7 +228,7 @@ export function main() { it('should not create a template-driven form when ngNoForm is used', () => { const fixture = initTest(NgNoFormComp); fixture.detectChanges(); - expect(fixture.debugElement.children[0].providerTokens !.length).toEqual(0); + expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0); }); it('should not add novalidate when ngNoForm is used', () => { @@ -282,7 +282,7 @@ export function main() { describe('submit and reset events', () => { it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => { const fixture = initTest(NgModelForm); - fixture.componentInstance.event = null !; + fixture.componentInstance.event = null; const form = fixture.debugElement.query(By.css('form')); dispatchEvent(form.nativeElement, 'submit'); @@ -355,11 +355,11 @@ export function main() { expect(form.valid).toEqual(true); expect(form.value).toEqual({}); - let formValidity: string = undefined !; - let formValue: Object = undefined !; + let formValidity: string; + let formValue: Object; - form.statusChanges !.subscribe((status: string) => formValidity = status); - form.valueChanges !.subscribe((value: string) => formValue = value); + form.statusChanges.subscribe((status: string) => formValidity = status); + form.valueChanges.subscribe((value: string) => formValue = value); tick(); @@ -374,8 +374,8 @@ export function main() { fixture.detectChanges(); tick(); - form.get('name') !.valueChanges.subscribe( - () => { expect(form.get('name') !.dirty).toBe(true); }); + form.get('name').valueChanges.subscribe( + () => { expect(form.get('name').dirty).toBe(true); }); const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; inputEl.value = 'newValue'; @@ -396,10 +396,10 @@ export function main() { inputEl.value = 'newValue'; dispatchEvent(inputEl, 'input'); - expect(form.get('name') !.pristine).toBe(false); + expect(form.get('name').pristine).toBe(false); - form.get('name') !.valueChanges.subscribe( - () => { expect(form.get('name') !.pristine).toBe(true); }); + form.get('name').valueChanges.subscribe( + () => { expect(form.get('name').pristine).toBe(true); }); dispatchEvent(formEl, 'reset'); })); @@ -418,7 +418,7 @@ export function main() { const form = fixture.debugElement.children[0].injector.get(NgForm); expect(form.value).toEqual({name: {first: '', last: 'Drew'}, email: 'some email'}); expect(form.valid).toBe(false); - expect(form.control.get('name.first') !.disabled).toBe(false); + expect(form.control.get('name.first').disabled).toBe(false); fixture.componentInstance.isDisabled = true; fixture.detectChanges(); @@ -426,7 +426,7 @@ export function main() { expect(form.value).toEqual({name: {last: 'Drew'}, email: 'some email'}); expect(form.valid).toBe(true); - expect(form.control.get('name.first') !.disabled).toBe(true); + expect(form.control.get('name.first').disabled).toBe(true); })); it('should add disabled attribute in the UI if disable() is called programmatically', @@ -438,7 +438,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - form.control.get('name.first') !.disable(); + form.control.get('name.first').disable(); fixture.detectChanges(); tick(); @@ -455,7 +455,7 @@ export function main() { fixture.detectChanges(); fixture.whenStable().then(() => { const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name') !.disabled).toBe(true); + expect(form.control.get('name').disabled).toBe(true); const customInput = fixture.debugElement.query(By.css('[name="custom"]')); expect(customInput.nativeElement.disabled).toEqual(true); @@ -477,7 +477,7 @@ export function main() { fixture.detectChanges(); tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name') !.disabled).toBe(true); + expect(form.control.get('name').disabled).toBe(true); const input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.disabled).toEqual(true); @@ -495,7 +495,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - form.control.get('food') !.disable(); + form.control.get('food').disable(); tick(); const inputs = fixture.debugElement.queryAll(By.css('input')); @@ -620,7 +620,7 @@ export function main() { fixture.detectChanges(); tick(); - fixture.componentInstance.food = null !; + fixture.componentInstance.food = null; fixture.detectChanges(); tick(); @@ -632,7 +632,7 @@ export function main() { fixture.detectChanges(); tick(); - fixture.componentInstance.food = undefined !; + fixture.componentInstance.food = undefined; fixture.detectChanges(); tick(); expect(inputs[0].nativeElement.checked).toEqual(false); @@ -724,7 +724,7 @@ export function main() { const fixture = initTest(NgModelSelectWithNullForm); const comp = fixture.componentInstance; comp.cities = [{'name': 'SF'}, {'name': 'NYC'}]; - comp.selectedCity = null !; + comp.selectedCity = null; fixture.detectChanges(); const select = fixture.debugElement.query(By.css('select')); @@ -745,7 +745,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(NgModelSelectWithCustomCompareFnForm); const comp = fixture.componentInstance; - comp.compareFn = null !; + comp.compareFn = null; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -833,7 +833,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(NgModelSelectMultipleWithCustomCompareFnForm); const comp = fixture.componentInstance; - comp.compareFn = null !; + comp.compareFn = null; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -885,7 +885,7 @@ export function main() { tick(); const control = - fixture.debugElement.children[0].injector.get(NgForm).control.get('checkbox') !; + fixture.debugElement.children[0].injector.get(NgForm).control.get('checkbox'); const input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.checked).toBe(false); @@ -921,7 +921,7 @@ export function main() { tick(); const control = - fixture.debugElement.children[0].injector.get(NgForm).control.get('email') !; + fixture.debugElement.children[0].injector.get(NgForm).control.get('email'); const input = fixture.debugElement.query(By.css('input')); expect(control.hasError('email')).toBe(false); @@ -1114,9 +1114,9 @@ export function main() { .toEqual(pattern.nativeElement.getAttribute('pattern')); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null !; - fixture.componentInstance.maxLen = null !; - fixture.componentInstance.pattern = null !; + fixture.componentInstance.minLen = null; + fixture.componentInstance.maxLen = null; + fixture.componentInstance.pattern = null; fixture.detectChanges(); expect(form.control.hasError('required', ['required'])).toEqual(false); diff --git a/packages/forms/test/validators_spec.ts b/packages/forms/test/validators_spec.ts index b44c8ce7c0..da1f6824dd 100644 --- a/packages/forms/test/validators_spec.ts +++ b/packages/forms/test/validators_spec.ts @@ -179,33 +179,33 @@ export function main() { }); it('should not error on "null" pattern', - () => expect(Validators.pattern(null !)(new FormControl('aaAA'))).toBeNull()); + () => expect(Validators.pattern(null)(new FormControl('aaAA'))).toBeNull()); it('should not error on "undefined" pattern', - () => expect(Validators.pattern(undefined !)(new FormControl('aaAA'))).toBeNull()); + () => expect(Validators.pattern(undefined)(new FormControl('aaAA'))).toBeNull()); }); describe('compose', () => { it('should return null when given null', - () => { expect(Validators.compose(null !)).toBe(null); }); + () => { expect(Validators.compose(null)).toBe(null); }); it('should collect errors from all the validators', () => { - const c = Validators.compose([validator('a', true), validator('b', true)]) !; + const c = Validators.compose([validator('a', true), validator('b', true)]); expect(c(new FormControl(''))).toEqual({'a': true, 'b': true}); }); it('should run validators left to right', () => { - const c = Validators.compose([validator('a', 1), validator('a', 2)]) !; + const c = Validators.compose([validator('a', 1), validator('a', 2)]); expect(c(new FormControl(''))).toEqual({'a': 2}); }); it('should return null when no errors', () => { - const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]) !; + const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]); expect(c(new FormControl(''))).toBeNull(); }); it('should ignore nulls', () => { - const c = Validators.compose([null !, Validators.required]) !; + const c = Validators.compose([null, Validators.required]); expect(c(new FormControl(''))).toEqual({'required': true}); }); }); @@ -221,13 +221,13 @@ export function main() { } it('should return null when given null', - () => { expect(Validators.composeAsync(null !)).toBeNull(); }); + () => { expect(Validators.composeAsync(null)).toBeNull(); }); it('should collect errors from all the validators', fakeAsync(() => { const v = Validators.composeAsync( - [promiseValidator({'one': true}), promiseValidator({'two': true})]) !; + [promiseValidator({'one': true}), promiseValidator({'two': true})]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -236,10 +236,10 @@ export function main() { })); it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => { - const v = Validators.composeAsync([normalizeAsyncValidator( - new AsyncValidatorDirective('expected', {'one': true}))]) !; + const v = Validators.composeAsync( + [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -248,9 +248,9 @@ export function main() { })); it('should return null when no errors', fakeAsync(() => { - const v = Validators.composeAsync([promiseValidator({'one': true})]) !; + const v = Validators.composeAsync([promiseValidator({'one': true})]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('expected'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -259,9 +259,9 @@ export function main() { })); it('should ignore nulls', fakeAsync(() => { - const v = Validators.composeAsync([promiseValidator({'one': true}), null !]) !; + const v = Validators.composeAsync([promiseValidator({'one': true}), null]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -279,13 +279,13 @@ export function main() { } it('should return null when given null', - () => { expect(Validators.composeAsync(null !)).toBeNull(); }); + () => { expect(Validators.composeAsync(null)).toBeNull(); }); it('should collect errors from all the validators', () => { const v = Validators.composeAsync( - [observableValidator({'one': true}), observableValidator({'two': true})]) !; + [observableValidator({'one': true}), observableValidator({'two': true})]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -294,19 +294,19 @@ export function main() { it('should normalize and evaluate async validator-directives correctly', () => { const v = Validators.composeAsync( - [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]) !; + [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('invalid'))) - .subscribe((errors: {[key: string]: any}) => errorMap = errors) !; + .subscribe((errors: {[key: string]: any}) => errorMap = errors); expect(errorMap).toEqual({'one': true}); }); it('should return null when no errors', () => { - const v = Validators.composeAsync([observableValidator({'one': true})]) !; + const v = Validators.composeAsync([observableValidator({'one': true})]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('expected'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -314,9 +314,9 @@ export function main() { }); it('should ignore nulls', () => { - const v = Validators.composeAsync([observableValidator({'one': true}), null !]) !; + const v = Validators.composeAsync([observableValidator({'one': true}), null]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -329,9 +329,9 @@ export function main() { } const v = Validators.composeAsync( - [getTimerObs(100, {one: true}), getTimerObs(200, {two: true})]) !; + [getTimerObs(100, {one: true}), getTimerObs(200, {two: true})]); - let errorMap: {[key: string]: any} = undefined !; + let errorMap: {[key: string]: any}; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); diff --git a/packages/forms/tsconfig-build.json b/packages/forms/tsconfig-build.json index 8cfa4196f1..74d0fa0488 100644 --- a/packages/forms/tsconfig-build.json +++ b/packages/forms/tsconfig-build.json @@ -4,7 +4,6 @@ "declaration": true, "stripInternal": true, "experimentalDecorators": true, - "strictNullChecks": true, "module": "es2015", "moduleResolution": "node", "outDir": "../../dist/packages/forms", diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts index fdfc00298b..889a1fc264 100644 --- a/tools/public_api_guard/forms/forms.d.ts +++ b/tools/public_api_guard/forms/forms.d.ts @@ -1,6 +1,6 @@ /** @stable */ export declare abstract class AbstractControl { - asyncValidator: AsyncValidatorFn | null; + asyncValidator: AsyncValidatorFn; readonly dirty: boolean; readonly disabled: boolean; readonly enabled: boolean; @@ -15,10 +15,10 @@ export declare abstract class AbstractControl { readonly touched: boolean; readonly untouched: boolean; readonly valid: boolean; - validator: ValidatorFn | null; + validator: ValidatorFn; readonly value: any; readonly valueChanges: Observable; - constructor(validator: ValidatorFn | null, asyncValidator: AsyncValidatorFn | null); + constructor(validator: ValidatorFn, asyncValidator: AsyncValidatorFn); clearAsyncValidators(): void; clearValidators(): void; disable({onlySelf, emitEvent}?: { @@ -29,7 +29,7 @@ export declare abstract class AbstractControl { onlySelf?: boolean; emitEvent?: boolean; }): void; - get(path: Array | string): AbstractControl | null; + get(path: Array | string): AbstractControl; getError(errorCode: string, path?: string[]): any; hasError(errorCode: string, path?: string[]): boolean; markAsDirty({onlySelf}?: { @@ -54,7 +54,7 @@ export declare abstract class AbstractControl { emitEvent?: boolean; }): void; setParent(parent: FormGroup | FormArray): void; - setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void; + setValidators(newValidator: ValidatorFn | ValidatorFn[]): void; abstract setValue(value: any, options?: Object): void; updateValueAndValidity({onlySelf, emitEvent}?: { onlySelf?: boolean; @@ -64,21 +64,21 @@ export declare abstract class AbstractControl { /** @stable */ export declare abstract class AbstractControlDirective { - readonly abstract control: AbstractControl | null; - readonly dirty: boolean | null; - readonly disabled: boolean | null; - readonly enabled: boolean | null; + readonly control: AbstractControl; + readonly dirty: boolean; + readonly disabled: boolean; + readonly enabled: boolean; readonly errors: ValidationErrors | null; - readonly invalid: boolean | null; - readonly path: string[] | null; - readonly pending: boolean | null; - readonly pristine: boolean | null; - readonly statusChanges: Observable | null; - readonly touched: boolean | null; - readonly untouched: boolean | null; - readonly valid: boolean | null; + readonly invalid: boolean; + readonly path: string[]; + readonly pending: boolean; + readonly pristine: boolean; + readonly statusChanges: Observable; + readonly touched: boolean; + readonly untouched: boolean; + readonly valid: boolean; readonly value: any; - readonly valueChanges: Observable | null; + readonly valueChanges: Observable; getError(errorCode: string, path?: string[]): any; hasError(errorCode: string, path?: string[]): boolean; reset(value?: any): void; @@ -86,11 +86,11 @@ export declare abstract class AbstractControlDirective { /** @stable */ export declare class AbstractFormGroupDirective extends ControlContainer implements OnInit, OnDestroy { - readonly asyncValidator: AsyncValidatorFn | null; + readonly asyncValidator: AsyncValidatorFn; readonly control: FormGroup; - readonly formDirective: Form | null; + readonly formDirective: Form; readonly path: string[]; - readonly validator: ValidatorFn | null; + readonly validator: ValidatorFn; ngOnDestroy(): void; ngOnInit(): void; } @@ -125,10 +125,10 @@ export declare class CheckboxRequiredValidator extends RequiredValidator { export declare const COMPOSITION_BUFFER_MODE: InjectionToken; /** @stable */ -export declare abstract class ControlContainer extends AbstractControlDirective { - readonly formDirective: Form | null; +export declare class ControlContainer extends AbstractControlDirective { + readonly formDirective: Form; name: string; - readonly path: string[] | null; + readonly path: string[]; } /** @stable */ @@ -175,7 +175,7 @@ export interface Form { export declare class FormArray extends AbstractControl { controls: AbstractControl[]; readonly length: number; - constructor(controls: AbstractControl[], validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null); + constructor(controls: AbstractControl[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn); at(index: number): AbstractControl; getRawValue(): any[]; insert(index: number, control: AbstractControl): void; @@ -198,12 +198,12 @@ export declare class FormArray extends AbstractControl { /** @stable */ export declare class FormArrayName extends ControlContainer implements OnInit, OnDestroy { - readonly asyncValidator: AsyncValidatorFn | null; + readonly asyncValidator: AsyncValidatorFn; readonly control: FormArray; - readonly formDirective: FormGroupDirective | null; + readonly formDirective: FormGroupDirective; name: string; readonly path: string[]; - readonly validator: ValidatorFn | null; + readonly validator: ValidatorFn; constructor(parent: ControlContainer, validators: any[], asyncValidators: any[]); ngOnDestroy(): void; ngOnInit(): void; @@ -211,18 +211,18 @@ export declare class FormArrayName extends ControlContainer implements OnInit, O /** @stable */ export declare class FormBuilder { - array(controlsConfig: any[], validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null): FormArray; - control(formState: Object, validator?: ValidatorFn | ValidatorFn[] | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl; + array(controlsConfig: any[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn): FormArray; + control(formState: Object, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]): FormControl; group(controlsConfig: { [key: string]: any; }, extra?: { [key: string]: any; - } | null): FormGroup; + }): FormGroup; } /** @stable */ export declare class FormControl extends AbstractControl { - constructor(formState?: any, validator?: ValidatorFn | ValidatorFn[] | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null); + constructor(formState?: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]); patchValue(value: any, options?: { onlySelf?: boolean; emitEvent?: boolean; @@ -245,14 +245,14 @@ export declare class FormControl extends AbstractControl { /** @stable */ export declare class FormControlDirective extends NgControl implements OnChanges { - readonly asyncValidator: AsyncValidatorFn | null; + readonly asyncValidator: AsyncValidatorFn; readonly control: FormControl; form: FormControl; isDisabled: boolean; model: any; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn | null; + readonly validator: ValidatorFn; viewModel: any; constructor(validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; @@ -269,7 +269,7 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD name: string; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn | null; + readonly validator: ValidatorFn; constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; @@ -283,7 +283,7 @@ export declare class FormGroup extends AbstractControl { }; constructor(controls: { [key: string]: AbstractControl; - }, validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null); + }, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn); addControl(name: string, control: AbstractControl): void; contains(controlName: string): boolean; getRawValue(): any; @@ -371,10 +371,10 @@ export declare const NG_VALUE_ACCESSOR: InjectionToken; /** @stable */ export declare abstract class NgControl extends AbstractControlDirective { - readonly asyncValidator: AsyncValidatorFn | null; - name: string | null; - readonly validator: ValidatorFn | null; - valueAccessor: ControlValueAccessor | null; + readonly asyncValidator: AsyncValidatorFn; + name: string; + readonly validator: ValidatorFn; + valueAccessor: ControlValueAccessor; abstract viewToModelUpdate(newValue: any): void; } @@ -417,7 +417,7 @@ export declare class NgForm extends ControlContainer implements Form { /** @stable */ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { - readonly asyncValidator: AsyncValidatorFn | null; + readonly asyncValidator: AsyncValidatorFn; readonly control: FormControl; readonly formDirective: any; isDisabled: boolean; @@ -429,7 +429,7 @@ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { }; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn | null; + readonly validator: ValidatorFn; viewModel: any; constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; @@ -532,9 +532,8 @@ export interface ValidatorFn { /** @stable */ export declare class Validators { - static compose(validators: null): null; - static compose(validators: (ValidatorFn | null | undefined)[]): ValidatorFn | null; - static composeAsync(validators: (AsyncValidatorFn | null)[]): AsyncValidatorFn | null; + static compose(validators: ValidatorFn[]): ValidatorFn; + static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn; static email(control: AbstractControl): ValidationErrors | null; static maxLength(maxLength: number): ValidatorFn; static minLength(minLength: number): ValidatorFn; From ce47d33cd94a242f23b47f287e975c80506c9a05 Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Fri, 14 Apr 2017 09:02:32 -0700 Subject: [PATCH 0020/1039] fix(compiler): ignore calls to unresolved symbols in metadata (#15970) This only shows up in the language service. Calls to symbols that are not resolve resulted in null instead of being resolved causing the language service to see exceptions when the null was not expected such as in the animations array. Fixes #15969 --- packages/compiler/src/aot/static_reflector.ts | 2 +- .../test/aot/static_reflector_spec.ts | 24 +++++++++++++++++++ .../test/aot/static_symbol_resolver_spec.ts | 3 +++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/aot/static_reflector.ts b/packages/compiler/src/aot/static_reflector.ts index bb97bd1676..6af817f97b 100644 --- a/packages/compiler/src/aot/static_reflector.ts +++ b/packages/compiler/src/aot/static_reflector.ts @@ -601,7 +601,7 @@ export class StaticReflector implements ɵReflectorReader { case 'ignore': return expression; } - return null; + return IGNORE; } return mapStringMap(expression, (value, name) => simplify(value)); } diff --git a/packages/compiler/test/aot/static_reflector_spec.ts b/packages/compiler/test/aot/static_reflector_spec.ts index dd6fc683b3..9ea0ffda15 100644 --- a/packages/compiler/test/aot/static_reflector_spec.ts +++ b/packages/compiler/test/aot/static_reflector_spec.ts @@ -546,6 +546,30 @@ describe('StaticReflector', () => { expect(annotation.providers).toEqual([1, 2, 3, 4, 5, 6, 7]); }); + it('should ignore unresolved calls', () => { + const data = Object.create(DEFAULT_TEST_DATA); + const file = '/tmp/src/invalid-component.ts'; + data[file] = ` + import {Component} from '@angular/core'; + import {unknown} from 'unresolved'; + + @Component({ + selector: 'tmp', + template: () => {}, + providers: [triggers()] + }) + export class BadComponent { + + } + `; + init(data, [], () => {}, {verboseInvalidExpression: true}); + + const badComponent = reflector.getStaticSymbol(file, 'BadComponent'); + const annotations = reflector.annotations(badComponent); + const annotation = annotations[0]; + expect(annotation.providers).toEqual([]); + }); + describe('inheritance', () => { class ClassDecorator { constructor(public value: any) {} diff --git a/packages/compiler/test/aot/static_symbol_resolver_spec.ts b/packages/compiler/test/aot/static_symbol_resolver_spec.ts index 9adfc0ebdf..944d6c503c 100644 --- a/packages/compiler/test/aot/static_symbol_resolver_spec.ts +++ b/packages/compiler/test/aot/static_symbol_resolver_spec.ts @@ -436,6 +436,9 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost { } return baseName + '.d.ts'; } + if (modulePath == 'unresolved') { + return undefined; + } return '/tmp/' + modulePath + '.d.ts'; } From cb5a7efa9152bc28094788b4dc2676a3f5b93d60 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 14 Apr 2017 09:03:16 -0700 Subject: [PATCH 0021/1039] fix(core): key-value differ changes iteration (#15968) fixes #14997 --- .../differs/default_keyvalue_differ.ts | 38 ++++++++----------- .../differs/default_keyvalue_differ_spec.ts | 13 +++++++ 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/core/src/change_detection/differs/default_keyvalue_differ.ts b/packages/core/src/change_detection/differs/default_keyvalue_differ.ts index fd3165c5dd..ebff2285d8 100644 --- a/packages/core/src/change_detection/differs/default_keyvalue_differ.ts +++ b/packages/core/src/change_detection/differs/default_keyvalue_differ.ts @@ -120,7 +120,6 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal } this._removalsHead = insertBefore; - this._removalsTail = insertBefore; for (let record = insertBefore; record !== null; record = record._nextRemoved) { if (record === this._mapHead) { @@ -135,6 +134,10 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal } } + // Make sure tails have no next records from previous runs + if (this._changesTail) this._changesTail._nextChanged = null; + if (this._additionsTail) this._additionsTail._nextAdded = null; + return this.isDirty; } @@ -222,7 +225,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal this._changesHead = this._changesTail = null; this._additionsHead = this._additionsTail = null; - this._removalsHead = this._removalsTail = null; + this._removalsHead = null; } } @@ -254,28 +257,17 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer, KeyVal } toString(): string { - const items: any[] = []; - const previous: any[] = []; - const changes: any[] = []; - const additions: any[] = []; - const removals: any[] = []; - let record: KeyValueChangeRecord_|null; + const items: string[] = []; + const previous: string[] = []; + const changes: string[] = []; + const additions: string[] = []; + const removals: string[] = []; - for (record = this._mapHead; record !== null; record = record._next) { - items.push(stringify(record)); - } - for (record = this._previousMapHead; record !== null; record = record._nextPrevious) { - previous.push(stringify(record)); - } - for (record = this._changesHead; record !== null; record = record._nextChanged) { - changes.push(stringify(record)); - } - for (record = this._additionsHead; record !== null; record = record._nextAdded) { - additions.push(stringify(record)); - } - for (record = this._removalsHead; record !== null; record = record._nextRemoved) { - removals.push(stringify(record)); - } + this.forEachItem(r => items.push(stringify(r))); + this.forEachPreviousItem(r => previous.push(stringify(r))); + this.forEachChangedItem(r => changes.push(stringify(r))); + this.forEachAddedItem(r => additions.push(stringify(r))); + this.forEachRemovedItem(r => removals.push(stringify(r))); return 'map: ' + items.join(', ') + '\n' + 'previous: ' + previous.join(', ') + '\n' + diff --git a/packages/core/test/change_detection/differs/default_keyvalue_differ_spec.ts b/packages/core/test/change_detection/differs/default_keyvalue_differ_spec.ts index 0100426dee..11bd1b5eb5 100644 --- a/packages/core/test/change_detection/differs/default_keyvalue_differ_spec.ts +++ b/packages/core/test/change_detection/differs/default_keyvalue_differ_spec.ts @@ -194,6 +194,19 @@ export function main() { })); }); + // https://github.com/angular/angular/issues/14997 + it('should work regardless key order', () => { + differ.check({a: 1, b: 2}); + differ.check({b: 3, a: 2}); + differ.check({a: 1, b: 2}); + + expect(differ.toString()).toEqual(kvChangesAsString({ + map: ['a[2->1]', 'b[3->2]'], + previous: ['b[3->2]', 'a[2->1]'], + changes: ['a[2->1]', 'b[3->2]'] + })); + }); + it('should when the first item is moved', () => { differ.check({a: 'a', b: 'b'}); differ.check({c: 'c', a: 'a'}); From 65af9641c229858322ce92c9291fcf0d8a720e29 Mon Sep 17 00:00:00 2001 From: Isaac Brown Date: Fri, 14 Apr 2017 09:03:57 -0700 Subject: [PATCH 0022/1039] fix(packaging): increased buffer size (#15840) --- tools/gulp-tasks/build.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/gulp-tasks/build.js b/tools/gulp-tasks/build.js index e8d94013da..5d724f233d 100644 --- a/tools/gulp-tasks/build.js +++ b/tools/gulp-tasks/build.js @@ -2,5 +2,6 @@ module.exports = (gulp) => (done) => { const path = require('path'); const childProcess = require('child_process'); - childProcess.exec(path.join(__dirname, '../../build.sh'), done); + // increase maxbuffer to address out of memory exception when running certain tasks + childProcess.exec(path.join(__dirname, '../../build.sh'), {maxBuffer: 300 * 1024}, done); }; From 8ad464d90e735ab3f59bdb95191ef9110e63ac84 Mon Sep 17 00:00:00 2001 From: Victor Savkin Date: Fri, 14 Apr 2017 12:04:28 -0400 Subject: [PATCH 0023/1039] feat(upgrade): allow setting the angularjs lib at runtime (#15168) This PR adds an ability to reset the angularjs library, which is often needed when Angular is loaded lazily using RequireJS. --- packages/upgrade/src/common/angular1.ts | 66 ++++++++++++++----- packages/upgrade/static/public_api.ts | 1 + .../test/static/integration/injection_spec.ts | 30 ++++++++- tools/public_api_guard/upgrade/static.d.ts | 6 ++ 4 files changed, 87 insertions(+), 16 deletions(-) diff --git a/packages/upgrade/src/common/angular1.ts b/packages/upgrade/src/common/angular1.ts index 267e8dbac8..20b93c1cd9 100644 --- a/packages/upgrade/src/common/angular1.ts +++ b/packages/upgrade/src/common/angular1.ts @@ -201,6 +201,7 @@ function noNg() { throw new Error('AngularJS v1.x is not loaded!'); } + let angular: { bootstrap: (e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig) => void, @@ -208,27 +209,62 @@ let angular: { element: (e: Element | string) => IAugmentedJQuery, version: {major: number}, resumeBootstrap?: () => void, getTestability: (e: Element) => ITestabilityService -} = { - bootstrap: noNg, - module: noNg, - element: noNg, - version: noNg, - resumeBootstrap: noNg, - getTestability: noNg }; - try { if (window.hasOwnProperty('angular')) { - angular = (window).angular; + setAngularLib((window).angular); } } catch (e) { - // ignore in CJS mode. + setAngularLib({ + bootstrap: noNg, + module: noNg, + element: noNg, + version: noNg, + resumeBootstrap: noNg, + getTestability: noNg + }); +} + +/** + * Resets the AngularJS library. + * + * Used when angularjs is loaded lazily, and not available on `window`. + * + * @stable + */ +export function setAngularLib(ng: any): void { + angular = ng; +} + +/** + * Returns the current version of the AngularJS library. + * + * @stable + */ +export function getAngularLib(): any { + return angular; +} + +export function bootstrap( + e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig): void { + angular.bootstrap(e, modules, config); +} + +export function module(prefix: string, dependencies?: string[]): IModule { + return angular.module(prefix, dependencies); +} + +export function element(e: Element | string): IAugmentedJQuery { + return angular.element(e); +} + +export function resumeBootstrap(): void { + angular.resumeBootstrap(); +} + +export function getTestability(e: Element): ITestabilityService { + return angular.getTestability(e); } -export const bootstrap = angular.bootstrap; -export const module = angular.module; -export const element = angular.element; export const version = angular.version; -export const resumeBootstrap = angular.resumeBootstrap; -export const getTestability = angular.getTestability; diff --git a/packages/upgrade/static/public_api.ts b/packages/upgrade/static/public_api.ts index 57b16cedc6..9d28d1c554 100644 --- a/packages/upgrade/static/public_api.ts +++ b/packages/upgrade/static/public_api.ts @@ -12,6 +12,7 @@ * Entry point for all public APIs of the upgrade/static package, allowing * Angular 1 and Angular 2+ to run side by side in the same application. */ +export {getAngularLib, 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'; diff --git a/packages/upgrade/test/static/integration/injection_spec.ts b/packages/upgrade/test/static/integration/injection_spec.ts index 93b3e3c3a2..8ee142c2f4 100644 --- a/packages/upgrade/test/static/integration/injection_spec.ts +++ b/packages/upgrade/test/static/integration/injection_spec.ts @@ -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} from '@angular/upgrade/static'; +import {UpgradeModule, downgradeInjectable, getAngularLib, setAngularLib} from '@angular/upgrade/static'; import {bootstrap, html} from '../test_helpers'; @@ -99,5 +99,33 @@ export function main() { expect(runBlockTriggered).toBeTruthy(); }); })); + + it('should allow resetting angular at runtime', async(() => { + let wrappedBootstrapepedCalled = false; + + const n: any = getAngularLib(); + + setAngularLib({ + bootstrap: (...args: any[]) => { + wrappedBootstrapepedCalled = true; + n.bootstrap(...args); + }, + module: n.module, + element: n.element, + version: n.version, + resumeBootstrap: n.resumeBootstrap, + getTestability: n.getTestability + }); + + @NgModule({imports: [BrowserModule, UpgradeModule]}) + class Ng2Module { + ngDoBootstrap() {} + } + + const ng1Module = angular.module('ng1Module', []); + + bootstrap(platformBrowserDynamic(), Ng2Module, html('
'), ng1Module) + .then((upgrade) => { expect(wrappedBootstrapepedCalled).toEqual(true); }); + })); }); } diff --git a/tools/public_api_guard/upgrade/static.d.ts b/tools/public_api_guard/upgrade/static.d.ts index 40cee4c38d..2eedf38587 100644 --- a/tools/public_api_guard/upgrade/static.d.ts +++ b/tools/public_api_guard/upgrade/static.d.ts @@ -9,6 +9,12 @@ export declare function downgradeComponent(info: { /** @experimental */ export declare function downgradeInjectable(token: any): Function; +/** @stable */ +export declare function getAngularLib(): any; + +/** @stable */ +export declare function setAngularLib(ng: any): void; + /** @experimental */ export declare class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { constructor(name: string, elementRef: ElementRef, injector: Injector); From 09c4cb254093ba91161d96cebf56a9f1fc953a35 Mon Sep 17 00:00:00 2001 From: "Panuruj Khambanonda (PK)" Date: Fri, 14 Apr 2017 09:05:00 -0700 Subject: [PATCH 0024/1039] feat(compiler): Implement i18n XLIFF 2.0 serializer (#14185) - Ensure that the result passes OASIS XLIFF 2.0 schema validation - Use for self-closing placeholder tags - Use for other placeholder tags - Check for the correct XLIFF file version - Add ICU support fixes #11735 --- .../integrationtest/test/i18n_spec.ts | 33 ++ packages/compiler-cli/src/extractor.ts | 26 +- .../compiler/src/i18n/i18n_html_parser.ts | 4 + packages/compiler/src/i18n/index.ts | 1 + .../compiler/src/i18n/serializers/xliff2.ts | 366 ++++++++++++++++++ .../test/i18n/serializers/xliff2_spec.ts | 336 ++++++++++++++++ scripts/ci/offline_compiler_test.sh | 1 + 7 files changed, 761 insertions(+), 6 deletions(-) create mode 100644 packages/compiler/src/i18n/serializers/xliff2.ts create mode 100644 packages/compiler/test/i18n/serializers/xliff2_spec.ts diff --git a/packages/compiler-cli/integrationtest/test/i18n_spec.ts b/packages/compiler-cli/integrationtest/test/i18n_spec.ts index 019b354ce9..63b914ff97 100644 --- a/packages/compiler-cli/integrationtest/test/i18n_spec.ts +++ b/packages/compiler-cli/integrationtest/test/i18n_spec.ts @@ -63,6 +63,32 @@ const EXPECTED_XLIFF = ` `; +const EXPECTED_XLIFF2 = ` + + + + + desc + meaning + + + translate me + + + + + Welcome + + + + + other-3rdP-component + + + + +`; + describe('template i18n extraction output', () => { const outDir = ''; const genDir = 'out'; @@ -81,6 +107,13 @@ describe('template i18n extraction output', () => { expect(xlf).toEqual(EXPECTED_XLIFF); }); + it('should extract i18n messages as xliff version 2.0', () => { + const xlfOutput = path.join(outDir, 'messages.xliff2.xlf'); + expect(fs.existsSync(xlfOutput)).toBeTruthy(); + const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'}); + expect(xlf).toEqual(EXPECTED_XLIFF2); + }); + it('should not emit js', () => { const genOutput = path.join(genDir, ''); expect(fs.existsSync(genOutput)).toBeFalsy(); diff --git a/packages/compiler-cli/src/extractor.ts b/packages/compiler-cli/src/extractor.ts index 1d929c8e7a..4b340f39dd 100644 --- a/packages/compiler-cli/src/extractor.ts +++ b/packages/compiler-cli/src/extractor.ts @@ -34,7 +34,7 @@ export class Extractor { const promiseBundle = this.extractBundle(); return promiseBundle.then(bundle => { - const content = this.serialize(bundle, ext); + const content = this.serialize(bundle, formatName); const dstFile = outFile || `messages.${ext}`; const dstPath = path.join(this.options.genDir, dstFile); this.host.writeFile(dstPath, content, false); @@ -48,14 +48,20 @@ export class Extractor { return this.ngExtractor.extract(files); } - serialize(bundle: compiler.MessageBundle, ext: string): string { + serialize(bundle: compiler.MessageBundle, formatName: string): string { + const format = formatName.toLowerCase(); let serializer: compiler.Serializer; - switch (ext) { + switch (format) { case 'xmb': serializer = new compiler.Xmb(); break; + case 'xliff2': + case 'xlf2': + serializer = new compiler.Xliff2(); + break; case 'xlf': + case 'xliff': default: serializer = new compiler.Xliff(); } @@ -66,10 +72,18 @@ export class Extractor { getExtension(formatName: string): string { const format = (formatName || 'xlf').toLowerCase(); - if (format === 'xmb') return 'xmb'; - if (format === 'xlf' || format === 'xlif' || format === 'xliff') return 'xlf'; + switch (format) { + case 'xmb': + return 'xmb'; + case 'xlf': + case 'xlif': + case 'xliff': + case 'xlf2': + case 'xliff2': + return 'xlf'; + } - throw new Error('Unsupported format "${formatName}"'); + throw new Error(`Unsupported format "${formatName}"`); } static create( diff --git a/packages/compiler/src/i18n/i18n_html_parser.ts b/packages/compiler/src/i18n/i18n_html_parser.ts index 3cb35db441..7a05ba42ce 100644 --- a/packages/compiler/src/i18n/i18n_html_parser.ts +++ b/packages/compiler/src/i18n/i18n_html_parser.ts @@ -13,6 +13,7 @@ import {ParseTreeResult} from '../ml_parser/parser'; import {mergeTranslations} from './extractor_merger'; import {Serializer} from './serializers/serializer'; import {Xliff} from './serializers/xliff'; +import {Xliff2} from './serializers/xliff2'; import {Xmb} from './serializers/xmb'; import {Xtb} from './serializers/xtb'; import {TranslationBundle} from './translation_bundle'; @@ -62,6 +63,9 @@ function createSerializer(format?: string): Serializer { return new Xmb(); case 'xtb': return new Xtb(); + case 'xliff2': + case 'xlf2': + return new Xliff2(); case 'xliff': case 'xlf': default: diff --git a/packages/compiler/src/i18n/index.ts b/packages/compiler/src/i18n/index.ts index eb05fb1c48..f47e971fd8 100644 --- a/packages/compiler/src/i18n/index.ts +++ b/packages/compiler/src/i18n/index.ts @@ -11,5 +11,6 @@ export {I18NHtmlParser} from './i18n_html_parser'; export {MessageBundle} from './message_bundle'; export {Serializer} from './serializers/serializer'; export {Xliff} from './serializers/xliff'; +export {Xliff2} from './serializers/xliff2'; export {Xmb} from './serializers/xmb'; export {Xtb} from './serializers/xtb'; diff --git a/packages/compiler/src/i18n/serializers/xliff2.ts b/packages/compiler/src/i18n/serializers/xliff2.ts new file mode 100644 index 0000000000..cb7fd960c8 --- /dev/null +++ b/packages/compiler/src/i18n/serializers/xliff2.ts @@ -0,0 +1,366 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as ml from '../../ml_parser/ast'; +import {XmlParser} from '../../ml_parser/xml_parser'; +import {decimalDigest} from '../digest'; +import * as i18n from '../i18n_ast'; +import {I18nError} from '../parse_util'; + +import {Serializer} from './serializer'; +import * as xml from './xml_helper'; + +const _VERSION = '2.0'; +const _XMLNS = 'urn:oasis:names:tc:xliff:document:2.0'; +// TODO(vicb): make this a param (s/_/-/) +const _DEFAULT_SOURCE_LANG = 'en'; +const _PLACEHOLDER_TAG = 'ph'; +const _PLACEHOLDER_SPANNING_TAG = 'pc'; + +const _XLIFF_TAG = 'xliff'; +const _SOURCE_TAG = 'source'; +const _TARGET_TAG = 'target'; +const _UNIT_TAG = 'unit'; + +// http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html +export class Xliff2 extends Serializer { + write(messages: i18n.Message[], locale: string|null): string { + const visitor = new _WriteVisitor(); + const units: xml.Node[] = []; + + messages.forEach(message => { + const unit = new xml.Tag(_UNIT_TAG, {id: message.id}); + + if (message.description || message.meaning) { + const notes = new xml.Tag('notes'); + if (message.description) { + notes.children.push( + new xml.CR(8), + new xml.Tag('note', {category: 'description'}, [new xml.Text(message.description)])); + } + + if (message.meaning) { + notes.children.push( + new xml.CR(8), + new xml.Tag('note', {category: 'meaning'}, [new xml.Text(message.meaning)])); + } + + notes.children.push(new xml.CR(6)); + unit.children.push(new xml.CR(6), notes); + } + + const segment = new xml.Tag('segment'); + + segment.children.push( + new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)), + new xml.CR(6)); + + unit.children.push(new xml.CR(6), segment, new xml.CR(4)); + + units.push(new xml.CR(4), unit); + }); + + const file = + new xml.Tag('file', {'original': 'ng.template', id: 'ngi18n'}, [...units, new xml.CR(2)]); + + const xliff = new xml.Tag( + _XLIFF_TAG, {version: _VERSION, xmlns: _XMLNS, srcLang: locale || _DEFAULT_SOURCE_LANG}, + [new xml.CR(2), file, new xml.CR()]); + + return xml.serialize([ + new xml.Declaration({version: '1.0', encoding: 'UTF-8'}), new xml.CR(), xliff, new xml.CR() + ]); + } + + load(content: string, url: string): + {locale: string, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}} { + // xliff to xml nodes + const xliff2Parser = new Xliff2Parser(); + const {locale, msgIdToHtml, errors} = xliff2Parser.parse(content, url); + + // xml nodes to i18n nodes + const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {}; + const converter = new XmlToI18n(); + + Object.keys(msgIdToHtml).forEach(msgId => { + const {i18nNodes, errors: e} = converter.convert(msgIdToHtml[msgId], url); + errors.push(...e); + i18nNodesByMsgId[msgId] = i18nNodes; + }); + + if (errors.length) { + throw new Error(`xliff2 parse errors:\n${errors.join('\n')}`); + } + + return {locale, i18nNodesByMsgId}; + } + + digest(message: i18n.Message): string { return decimalDigest(message); } +} + +class _WriteVisitor implements i18n.Visitor { + private _nextPlaceholderId: number; + + visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; } + + visitContainer(container: i18n.Container, context?: any): xml.Node[] { + const nodes: xml.Node[] = []; + container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this))); + return nodes; + } + + visitIcu(icu: i18n.Icu, context?: any): xml.Node[] { + const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)]; + + Object.keys(icu.cases).forEach((c: string) => { + nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `)); + }); + + nodes.push(new xml.Text(`}`)); + + return nodes; + } + + visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] { + const type = getTypeForTag(ph.tag); + + if (ph.isVoid) { + const tagPh = new xml.Tag(_PLACEHOLDER_TAG, { + id: (this._nextPlaceholderId++).toString(), + equiv: ph.startName, + type: type, + disp: `<${ph.tag}/>`, + }); + return [tagPh]; + } + + const tagPc = new xml.Tag(_PLACEHOLDER_SPANNING_TAG, { + id: (this._nextPlaceholderId++).toString(), + equivStart: ph.startName, + equivEnd: ph.closeName, + type: type, + dispStart: `<${ph.tag}>`, + dispEnd: ``, + }); + const nodes: xml.Node[] = [].concat(...ph.children.map(node => node.visit(this))); + if (nodes.length) { + nodes.forEach((node: xml.Node) => tagPc.children.push(node)); + } else { + tagPc.children.push(new xml.Text('')); + } + + return [tagPc]; + } + + visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] { + return [new xml.Tag(_PLACEHOLDER_TAG, { + id: (this._nextPlaceholderId++).toString(), + equiv: ph.name, + disp: `{{${ph.value}}}`, + })]; + } + + visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] { + return [new xml.Tag(_PLACEHOLDER_TAG, {id: (this._nextPlaceholderId++).toString()})]; + } + + serialize(nodes: i18n.Node[]): xml.Node[] { + this._nextPlaceholderId = 0; + return [].concat(...nodes.map(node => node.visit(this))); + } +} + +// Extract messages as xml nodes from the xliff file +class Xliff2Parser implements ml.Visitor { + private _unitMlString: string; + private _errors: I18nError[]; + private _msgIdToHtml: {[msgId: string]: string}; + private _locale: string|null = null; + + parse(xliff: string, url: string) { + this._unitMlString = null; + this._msgIdToHtml = {}; + + const xml = new XmlParser().parse(xliff, url, false); + + this._errors = xml.errors; + ml.visitAll(this, xml.rootNodes, null); + + return { + msgIdToHtml: this._msgIdToHtml, + errors: this._errors, + locale: this._locale, + }; + } + + visitElement(element: ml.Element, context: any): any { + switch (element.name) { + case _UNIT_TAG: + this._unitMlString = null; + const idAttr = element.attrs.find((attr) => attr.name === 'id'); + if (!idAttr) { + this._addError(element, `<${_UNIT_TAG}> misses the "id" attribute`); + } else { + const id = idAttr.value; + if (this._msgIdToHtml.hasOwnProperty(id)) { + this._addError(element, `Duplicated translations for msg ${id}`); + } else { + ml.visitAll(this, element.children, null); + if (typeof this._unitMlString === 'string') { + this._msgIdToHtml[id] = this._unitMlString; + } else { + this._addError(element, `Message ${id} misses a translation`); + } + } + } + break; + + case _SOURCE_TAG: + // ignore source message + break; + + case _TARGET_TAG: + const innerTextStart = element.startSourceSpan.end.offset; + const innerTextEnd = element.endSourceSpan.start.offset; + const content = element.startSourceSpan.start.file.content; + const innerText = content.slice(innerTextStart, innerTextEnd); + this._unitMlString = innerText; + break; + + case _XLIFF_TAG: + const localeAttr = element.attrs.find((attr) => attr.name === 'trgLang'); + if (localeAttr) { + this._locale = localeAttr.value; + } + + const versionAttr = element.attrs.find((attr) => attr.name === 'version'); + if (versionAttr) { + const version = versionAttr.value; + if (version !== '2.0') { + this._addError( + element, + `The XLIFF file version ${version} is not compatible with XLIFF 2.0 serializer`); + } else { + ml.visitAll(this, element.children, null); + } + } + break; + default: + ml.visitAll(this, element.children, null); + } + } + + visitAttribute(attribute: ml.Attribute, context: any): any {} + + visitText(text: ml.Text, context: any): any {} + + visitComment(comment: ml.Comment, context: any): any {} + + visitExpansion(expansion: ml.Expansion, context: any): any {} + + visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {} + + private _addError(node: ml.Node, message: string): void { + this._errors.push(new I18nError(node.sourceSpan, message)); + } +} + +// Convert ml nodes (xliff syntax) to i18n nodes +class XmlToI18n implements ml.Visitor { + private _errors: I18nError[]; + + convert(message: string, url: string) { + const xmlIcu = new XmlParser().parse(message, url, true); + this._errors = xmlIcu.errors; + + const i18nNodes = this._errors.length > 0 || xmlIcu.rootNodes.length == 0 ? + [] : + [].concat(...ml.visitAll(this, xmlIcu.rootNodes)); + + return { + i18nNodes, + errors: this._errors, + }; + } + + visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); } + + visitElement(el: ml.Element, context: any): i18n.Node[] { + switch (el.name) { + case _PLACEHOLDER_TAG: + const nameAttr = el.attrs.find((attr) => attr.name === 'equiv'); + if (nameAttr) { + return [new i18n.Placeholder('', nameAttr.value, el.sourceSpan)]; + } + + this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equiv" attribute`); + break; + case _PLACEHOLDER_SPANNING_TAG: + const startAttr = el.attrs.find((attr) => attr.name === 'equivStart'); + const endAttr = el.attrs.find((attr) => attr.name === 'equivEnd'); + + if (!startAttr) { + this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equivStart" attribute`); + } else if (!endAttr) { + this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "equivEnd" attribute`); + } else { + const startId = startAttr.value; + const endId = endAttr.value; + + return [].concat( + new i18n.Placeholder('', startId, el.sourceSpan), + ...el.children.map(node => node.visit(this, null)), + new i18n.Placeholder('', endId, el.sourceSpan)); + } + break; + default: + this._addError(el, `Unexpected tag`); + } + } + + visitExpansion(icu: ml.Expansion, context: any) { + const caseMap: {[value: string]: i18n.Node} = {}; + + ml.visitAll(this, icu.cases).forEach((c: any) => { + caseMap[c.value] = new i18n.Container(c.nodes, icu.sourceSpan); + }); + + return new i18n.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan); + } + + visitExpansionCase(icuCase: ml.ExpansionCase, context: any): any { + return { + value: icuCase.value, + nodes: [].concat(...ml.visitAll(this, icuCase.expression)), + }; + } + + visitComment(comment: ml.Comment, context: any) {} + + visitAttribute(attribute: ml.Attribute, context: any) {} + + private _addError(node: ml.Node, message: string): void { + this._errors.push(new I18nError(node.sourceSpan, message)); + } +} + +function getTypeForTag(tag: string): string { + switch (tag.toLowerCase()) { + case 'br': + case 'b': + case 'i': + case 'u': + return 'fmt'; + case 'img': + return 'image'; + case 'a': + return 'link'; + default: + return 'other'; + } +} \ No newline at end of file diff --git a/packages/compiler/test/i18n/serializers/xliff2_spec.ts b/packages/compiler/test/i18n/serializers/xliff2_spec.ts new file mode 100644 index 0000000000..d20548c48d --- /dev/null +++ b/packages/compiler/test/i18n/serializers/xliff2_spec.ts @@ -0,0 +1,336 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {escapeRegExp} from '@angular/compiler/src/util'; + +import {serializeNodes} from '../../../src/i18n/digest'; +import {MessageBundle} from '../../../src/i18n/message_bundle'; +import {Xliff2} from '../../../src/i18n/serializers/xliff2'; +import {HtmlParser} from '../../../src/ml_parser/html_parser'; +import {DEFAULT_INTERPOLATION_CONFIG} from '../../../src/ml_parser/interpolation_config'; + +const HTML = ` +

not translatable

+

translatable element with placeholders {{ interpolation}}

+{ count, plural, =0 {

test

}} +

foo

+

{{interpolation}} Text

+


+

hello

+

{ count, plural, =0 { { sex, select, other {

deeply nested

}} }}

+

{ count, plural, =0 { { sex, select, other {

deeply nested

}} }}

+`; + +const WRITE_XLIFF = ` + + + + + translatable attribute + + + + + translatable element with placeholders + + + + + {VAR_PLURAL, plural, =0 {test} } + + + + + d + m + + + foo + + + + + nested + + + Text + + + + + ph names + + + + + + + + empty element + + + hello + + + + + {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {deeply nested} } } } + + + + + {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {deeply nested} } } } + + + + +`; + +const LOAD_XLIFF = ` + + + + + translatable attribute + etubirtta elbatalsnart + + + + + translatable element with placeholders + sredlohecalp htiw tnemele elbatalsnart + + + + + {VAR_PLURAL, plural, =0 {test} } + {VAR_PLURAL, plural, =0 {TEST} } + + + + + d + m + + + foo + oof + + + + + nested + + + Text + txeT + + + + + ph names + + + + + + + + + empty element + + + hello + olleh + + + + + {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {deeply nested} } } } + {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {profondément imbriqué} } } } + + + + + {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {deeply nested} } } } + {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {profondément imbriqué} } } } + + + + +`; + +export function main(): void { + const serializer = new Xliff2(); + + function toXliff(html: string, locale: string | null = null): string { + const catalog = new MessageBundle(new HtmlParser, [], {}, locale); + catalog.updateFromTemplate(html, '', DEFAULT_INTERPOLATION_CONFIG); + return catalog.write(serializer); + } + + function loadAsMap(xliff: string): {[id: string]: string} { + const {i18nNodesByMsgId} = serializer.load(xliff, 'url'); + + const msgMap: {[id: string]: string} = {}; + Object.keys(i18nNodesByMsgId) + .forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join('')); + + return msgMap; + } + + describe('XLIFF 2.0 serializer', () => { + describe('write', () => { + it('should write a valid xliff 2.0 file', + () => { expect(toXliff(HTML)).toEqual(WRITE_XLIFF); }); + it('should write a valid xliff 2.0 file with a source language', + () => { expect(toXliff(HTML, 'fr')).toContain('srcLang="fr"'); }); + }); + + describe('load', () => { + it('should load XLIFF files', () => { + expect(loadAsMap(LOAD_XLIFF)).toEqual({ + '1933478729560469763': 'etubirtta elbatalsnart', + '7056919470098446707': + ' sredlohecalp htiw tnemele elbatalsnart', + '2981514368455622387': + '{VAR_PLURAL, plural, =0 {[, TEST, ]}}', + 'i': 'oof', + '6440235004920703622': + 'txeT ', + '8779402634269838862': + '', + '6536355551500405293': ' olleh', + 'baz': + '{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[, profondément imbriqué, ]}}, ]}}', + '2015957479576096115': + '{VAR_PLURAL, plural, =0 {[{VAR_SELECT, select, other {[, profondément imbriqué, ]}}, ]}}' + }); + }); + + it('should return the target locale', + () => { expect(serializer.load(LOAD_XLIFF, 'url').locale).toEqual('fr'); }); + }); + + describe('structure errors', () => { + it('should throw when a wrong xliff version is used', () => { + const XLIFF = ` + + + + + + + + + +`; + + expect(() => { + loadAsMap(XLIFF); + }).toThrowError(/The XLIFF file version 1.2 is not compatible with XLIFF 2.0 serializer/); + }); + + it('should throw when an unit has no translation', () => { + const XLIFF = ` + + + + + + + + +`; + + expect(() => { + loadAsMap(XLIFF); + }).toThrowError(/Message missingtarget misses a translation/); + }); + + + it('should throw when an unit has no id attribute', () => { + const XLIFF = ` + + + + + + + + + +`; + + expect(() => { loadAsMap(XLIFF); }).toThrowError(/ misses the "id" attribute/); + }); + + it('should throw on duplicate unit id', () => { + const XLIFF = ` + + + + + + + + + + + + + + + +`; + + expect(() => { + loadAsMap(XLIFF); + }).toThrowError(/Duplicated translations for msg deadbeef/); + }); + }); + + describe('message errors', () => { + it('should throw on unknown message tags', () => { + const XLIFF = ` + + + + + + msg should contain only ph and pc tags + + + +`; + + expect(() => { loadAsMap(XLIFF); }) + .toThrowError(new RegExp( + escapeRegExp(`[ERROR ->]msg should contain only ph and pc tags`))); + }); + + it('should throw when a placeholder misses an id attribute', () => { + const XLIFF = ` + + + + + + + + + +`; + + expect(() => { + loadAsMap(XLIFF); + }).toThrowError(new RegExp(escapeRegExp(` misses the "equiv" attribute`))); + }); + }); + }); +} \ No newline at end of file diff --git a/scripts/ci/offline_compiler_test.sh b/scripts/ci/offline_compiler_test.sh index 810c3bbdc7..665fa405fc 100755 --- a/scripts/ci/offline_compiler_test.sh +++ b/scripts/ci/offline_compiler_test.sh @@ -58,6 +58,7 @@ cp -v package.json $TMP ./node_modules/.bin/ngc -p tsconfig-build.json --i18nFile=src/messages.fi.xlf --locale=fi --i18nFormat=xlf ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xlf --locale=fr + ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xlf2 --outFile=messages.xliff2.xlf ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xmb --outFile=custom_file.xmb # Removed until #15219 is fixed From 4054055d0d6f028ddbb55a5aa3444bb5ed195bbc Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Fri, 14 Apr 2017 18:06:25 +0200 Subject: [PATCH 0025/1039] feat(compiler): add source files to xmb/xliff translations (#14705) Fixes #14190 --- .../integrationtest/src/entry_components.ts | 6 +- .../integrationtest/test/i18n_spec.ts | 28 ++++- .../integrationtest/test/ng_module_spec.ts | 3 +- .../third_party_src/other_comp.ts | 5 +- packages/compiler-cli/src/extractor.ts | 5 +- packages/compiler/src/i18n/i18n_ast.ts | 27 ++++- packages/compiler/src/i18n/message_bundle.ts | 12 ++- .../compiler/src/i18n/serializers/xliff.ts | 18 +++- packages/compiler/src/i18n/serializers/xmb.ts | 12 ++- packages/compiler/test/i18n/digest_spec.ts | 1 + .../test/i18n/integration_xliff_spec.ts | 102 +++++++++++++++++- .../test/i18n/integration_xmb_xtb_spec.ts | 42 ++++---- .../test/i18n/serializers/xliff_spec.ts | 71 +++++++++++- .../test/i18n/serializers/xmb_spec.ts | 28 ++--- .../test/i18n/translation_bundle_spec.ts | 5 +- 15 files changed, 311 insertions(+), 54 deletions(-) diff --git a/packages/compiler-cli/integrationtest/src/entry_components.ts b/packages/compiler-cli/integrationtest/src/entry_components.ts index f969dd25d2..463ba82554 100644 --- a/packages/compiler-cli/integrationtest/src/entry_components.ts +++ b/packages/compiler-cli/integrationtest/src/entry_components.ts @@ -10,7 +10,11 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Injec import {BasicComp} from './basic'; -@Component({selector: 'cmp-entryComponents', template: '', entryComponents: [BasicComp]}) +@Component({ + selector: 'cmp-entryComponents', + template: '

Welcome

', + entryComponents: [BasicComp] +}) export class CompWithEntryComponents { constructor(public cfr: ComponentFactoryResolver) {} } diff --git a/packages/compiler-cli/integrationtest/test/i18n_spec.ts b/packages/compiler-cli/integrationtest/test/i18n_spec.ts index 63b914ff97..eb80b5f70b 100644 --- a/packages/compiler-cli/integrationtest/test/i18n_spec.ts +++ b/packages/compiler-cli/integrationtest/test/i18n_spec.ts @@ -34,9 +34,10 @@ const EXPECTED_XMB = ` ]> - translate me - Welcome - other-3rdP-component + src/basic.ts:1translate me + src/basic.ts:5src/entry_components.ts:1Welcome + node_modules/third_party/other_comp.d.ts:1,2other-3rdP-component +multi-lines `; @@ -47,16 +48,33 @@ const EXPECTED_XLIFF = ` translate me + + src/basic.ts + 1 + desc meaning Welcome + + src/basic.ts + 5 + + + src/entry_components.ts + 1 + - - other-3rdP-component + + other-3rdP-component +multi-lines + + node_modules/third_party/other_comp.d.ts + 1 + diff --git a/packages/compiler-cli/integrationtest/test/ng_module_spec.ts b/packages/compiler-cli/integrationtest/test/ng_module_spec.ts index b5b76ad41a..4e8a2706df 100644 --- a/packages/compiler-cli/integrationtest/test/ng_module_spec.ts +++ b/packages/compiler-cli/integrationtest/test/ng_module_spec.ts @@ -59,7 +59,8 @@ describe('NgModule', () => { const fixture = createComponent(ComponentUsingThirdParty); const thirdPComps = fixture.nativeElement.children; expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component'); - expect(thirdPComps[1].children[0].children[0].data).toEqual('other-3rdP-component'); + expect(thirdPComps[1].children[0].children[0].data).toEqual(`other-3rdP-component +multi-lines`); }); // https://github.com/angular/angular/issues/12428 diff --git a/packages/compiler-cli/integrationtest/third_party_src/other_comp.ts b/packages/compiler-cli/integrationtest/third_party_src/other_comp.ts index 553e08ba45..130baabd8f 100644 --- a/packages/compiler-cli/integrationtest/third_party_src/other_comp.ts +++ b/packages/compiler-cli/integrationtest/third_party_src/other_comp.ts @@ -10,7 +10,8 @@ import {Component} from '@angular/core'; @Component({ selector: 'another-third-party-comp', - template: '
other-3rdP-component
', + template: `
other-3rdP-component +multi-lines
`, }) export class AnotherThirdpartyComponent { -} \ No newline at end of file +} diff --git a/packages/compiler-cli/src/extractor.ts b/packages/compiler-cli/src/extractor.ts index 4b340f39dd..c8073b89c6 100644 --- a/packages/compiler-cli/src/extractor.ts +++ b/packages/compiler-cli/src/extractor.ts @@ -65,8 +65,9 @@ export class Extractor { default: serializer = new compiler.Xliff(); } - - return bundle.write(serializer); + return bundle.write( + serializer, + (sourcePath: string) => sourcePath.replace(path.join(this.options.basePath, '/'), '')); } getExtension(formatName: string): string { diff --git a/packages/compiler/src/i18n/i18n_ast.ts b/packages/compiler/src/i18n/i18n_ast.ts index 1e94ea416f..c2d1864934 100644 --- a/packages/compiler/src/i18n/i18n_ast.ts +++ b/packages/compiler/src/i18n/i18n_ast.ts @@ -9,6 +9,8 @@ import {ParseSourceSpan} from '../parse_util'; export class Message { + sources: MessageSpan[]; + /** * @param nodes message AST * @param placeholders maps placeholder names to static content @@ -20,7 +22,28 @@ export class Message { constructor( public nodes: Node[], public placeholders: {[phName: string]: string}, public placeholderToMessage: {[phName: string]: Message}, public meaning: string, - public description: string, public id: string) {} + public description: string, public id: string) { + if (nodes.length) { + this.sources = [{ + filePath: nodes[0].sourceSpan.start.file.url, + startLine: nodes[0].sourceSpan.start.line + 1, + startCol: nodes[0].sourceSpan.start.col + 1, + endLine: nodes[nodes.length - 1].sourceSpan.end.line + 1, + endCol: nodes[0].sourceSpan.start.col + 1 + }]; + } else { + this.sources = []; + } + } +} + +// line and columns indexes are 1 based +export interface MessageSpan { + filePath: string; + startLine: number; + startCol: number; + endLine: number; + endCol: number; } export interface Node { @@ -131,4 +154,4 @@ export class RecurseVisitor implements Visitor { visitPlaceholder(ph: Placeholder, context?: any): any{}; visitIcuPlaceholder(ph: IcuPlaceholder, context?: any): any{}; -} \ No newline at end of file +} diff --git a/packages/compiler/src/i18n/message_bundle.ts b/packages/compiler/src/i18n/message_bundle.ts index 8016bc0d3f..54c7245580 100644 --- a/packages/compiler/src/i18n/message_bundle.ts +++ b/packages/compiler/src/i18n/message_bundle.ts @@ -48,7 +48,7 @@ export class MessageBundle { // The public (serialized) format might be different, see the `write` method. getMessages(): i18n.Message[] { return this._messages; } - write(serializer: Serializer): string { + write(serializer: Serializer, filterSources?: (path: string) => string): string { const messages: {[id: string]: i18n.Message} = {}; const mapperVisitor = new MapPlaceholderNames(); @@ -57,6 +57,8 @@ export class MessageBundle { const id = serializer.digest(message); if (!messages.hasOwnProperty(id)) { messages[id] = message; + } else { + messages[id].sources.push(...message.sources); } }); @@ -65,7 +67,13 @@ export class MessageBundle { const mapper = serializer.createNameMapper(messages[id]); const src = messages[id]; const nodes = mapper ? mapperVisitor.convert(src.nodes, mapper) : src.nodes; - return new i18n.Message(nodes, {}, {}, src.meaning, src.description, id); + let transformedMessage = new i18n.Message(nodes, {}, {}, src.meaning, src.description, id); + transformedMessage.sources = src.sources; + if (filterSources) { + transformedMessage.sources.forEach( + (source: i18n.MessageSpan) => source.filePath = filterSources(source.filePath)); + } + return transformedMessage; }); return serializer.write(msgList, this._locale); diff --git a/packages/compiler/src/i18n/serializers/xliff.ts b/packages/compiler/src/i18n/serializers/xliff.ts index dbc9fffb8f..f64e9a58f1 100644 --- a/packages/compiler/src/i18n/serializers/xliff.ts +++ b/packages/compiler/src/i18n/serializers/xliff.ts @@ -25,6 +25,8 @@ const _FILE_TAG = 'file'; const _SOURCE_TAG = 'source'; const _TARGET_TAG = 'target'; const _UNIT_TAG = 'trans-unit'; +const _CONTEXT_GROUP_TAG = 'context-group'; +const _CONTEXT_TAG = 'context'; // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html // http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html @@ -34,10 +36,24 @@ export class Xliff extends Serializer { const transUnits: xml.Node[] = []; messages.forEach(message => { + let contextTags: xml.Node[] = []; + message.sources.forEach((source: i18n.MessageSpan) => { + let contextGroupTag = new xml.Tag(_CONTEXT_GROUP_TAG, {purpose: 'location'}); + contextGroupTag.children.push( + new xml.CR(10), + new xml.Tag( + _CONTEXT_TAG, {'context-type': 'sourcefile'}, [new xml.Text(source.filePath)]), + new xml.CR(10), new xml.Tag( + _CONTEXT_TAG, {'context-type': 'linenumber'}, + [new xml.Text(`${source.startLine}`)]), + new xml.CR(8)); + contextTags.push(new xml.CR(8), contextGroupTag); + }); + const transUnit = new xml.Tag(_UNIT_TAG, {id: message.id, datatype: 'html'}); transUnit.children.push( new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)), - new xml.CR(8), new xml.Tag(_TARGET_TAG)); + new xml.CR(8), new xml.Tag(_TARGET_TAG), ...contextTags); if (message.description) { transUnit.children.push( diff --git a/packages/compiler/src/i18n/serializers/xmb.ts b/packages/compiler/src/i18n/serializers/xmb.ts index 9110b27221..c9ee3eb52b 100644 --- a/packages/compiler/src/i18n/serializers/xmb.ts +++ b/packages/compiler/src/i18n/serializers/xmb.ts @@ -16,6 +16,7 @@ const _MESSAGES_TAG = 'messagebundle'; const _MESSAGE_TAG = 'msg'; const _PLACEHOLDER_TAG = 'ph'; const _EXEMPLE_TAG = 'ex'; +const _SOURCE_TAG = 'source'; const _DOCTYPE = ` @@ -54,8 +55,17 @@ export class Xmb extends Serializer { attrs['meaning'] = message.meaning; } + let sourceTags: xml.Tag[] = []; + message.sources.forEach((source: i18n.MessageSpan) => { + sourceTags.push(new xml.Tag(_SOURCE_TAG, {}, [ + new xml.Text( + `${source.filePath}:${source.startLine}${source.endLine !== source.startLine ? ',' + source.endLine : ''}`) + ])); + }); + rootNode.children.push( - new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes))); + new xml.CR(2), + new xml.Tag(_MESSAGE_TAG, attrs, [...sourceTags, ...visitor.serialize(message.nodes)])); }); rootNode.children.push(new xml.CR()); diff --git a/packages/compiler/test/i18n/digest_spec.ts b/packages/compiler/test/i18n/digest_spec.ts index 7297f6cc76..532430d64f 100644 --- a/packages/compiler/test/i18n/digest_spec.ts +++ b/packages/compiler/test/i18n/digest_spec.ts @@ -19,6 +19,7 @@ export function main(): void { placeholderToMessage: {}, meaning: '', description: '', + sources: [], })).toEqual('i'); }); }); diff --git a/packages/compiler/test/i18n/integration_xliff_spec.ts b/packages/compiler/test/i18n/integration_xliff_spec.ts index d082c283dd..a71c95be2e 100644 --- a/packages/compiler/test/i18n/integration_xliff_spec.ts +++ b/packages/compiler/test/i18n/integration_xliff_spec.ts @@ -39,7 +39,7 @@ export function main() { it('should extract from templates', () => { const catalog = new MessageBundle(new HtmlParser, [], {}); const serializer = new Xliff(); - catalog.updateFromTemplate(HTML, '', DEFAULT_INTERPOLATION_CONFIG); + catalog.updateFromTemplate(HTML, 'file.ts', DEFAULT_INTERPOLATION_CONFIG); expect(catalog.write(serializer)).toContain(XLIFF_EXTRACTED); }); @@ -163,67 +163,139 @@ const XLIFF_EXTRACTED = ` i18n attribute on tags + + file.ts + 3 + nested + + file.ts + 5 + nested + + file.ts + 7 + different meaning with placeholders + + file.ts + 9 + + + file.ts + 10 + on not translatable node + + file.ts + 13 + on translatable node + + file.ts + 14 + {VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {many} } + + file.ts + 19 + + + file.ts + 36 + + + file.ts + 21 + {VAR_SELECT, select, m {male} f {female} } + + file.ts + 22 + + + file.ts + 24 + {VAR_SELECT, select, m {male} f {female} } + + file.ts + 25 + + + file.ts + 28 + sex = + + file.ts + 29 + + + file.ts + 30 + in a translatable section + + file.ts + 35 + + + file.ts + 53 + @@ -232,29 +304,57 @@ const XLIFF_EXTRACTED = ` + + file.ts + 33 + it should work + + file.ts + 39 + with an explicit ID + + file.ts + 41 + {VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {many} } + + file.ts + 42 + {VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found results} } + + file.ts + 45 + desc foobar + + file.ts + 53 + + + file.ts + 55 + `; diff --git a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts index d4e78b2958..6432a72034 100644 --- a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts +++ b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts @@ -39,7 +39,7 @@ export function main() { it('should extract from templates', () => { const catalog = new MessageBundle(new HtmlParser, [], {}); const serializer = new Xmb(); - catalog.updateFromTemplate(HTML, '', DEFAULT_INTERPOLATION_CONFIG); + catalog.updateFromTemplate(HTML, 'file.ts', DEFAULT_INTERPOLATION_CONFIG); expect(catalog.write(serializer)).toContain(XMB); }); @@ -84,29 +84,29 @@ const XTB = ` MAP_NAME `; -const XMB = ` i18n attribute on tags - nested - nested - <i>with placeholders</i> - on not translatable node - on translatable node - {VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} } - +const XMB = ` file.ts:3i18n attribute on tags + file.ts:5nested + file.ts:7nested + file.ts:9file.ts:10<i>with placeholders</i> + file.ts:13on not translatable node + file.ts:14on translatable node + file.ts:19file.ts:36{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} } + file.ts:21,23file.ts:24,26 ICU - {VAR_SELECT, select, m {male} f {female} } - INTERPOLATION - sex = INTERPOLATION - CUSTOM_NAME - in a translatable section - + file.ts:22file.ts:25{VAR_SELECT, select, m {male} f {female} } + file.ts:28INTERPOLATION + file.ts:29sex = INTERPOLATION + file.ts:30CUSTOM_NAME + file.ts:35file.ts:53in a translatable section + file.ts:33,37 <h1>Markers in html comments</h1> <div></div> <div>ICU</div> - it <b>should</b> work - with an explicit ID - {VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} } - {VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found INTERPOLATION results} } - foo<a>bar</a> - MAP_NAME`; + file.ts:39it <b>should</b> work + file.ts:41with an explicit ID + file.ts:42{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} } + file.ts:45,51{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found INTERPOLATION results} } + file.ts:53foo<a>bar</a> + file.ts:55MAP_NAME`; diff --git a/packages/compiler/test/i18n/serializers/xliff_spec.ts b/packages/compiler/test/i18n/serializers/xliff_spec.ts index f16dbc57ad..c6d01231f4 100644 --- a/packages/compiler/test/i18n/serializers/xliff_spec.ts +++ b/packages/compiler/test/i18n/serializers/xliff_spec.ts @@ -19,6 +19,7 @@ const HTML = `

translatable element with placeholders {{ interpolation}}

{ count, plural, =0 {

test

}}

foo

+

foo

foo

foo


@@ -33,43 +34,83 @@ const WRITE_XLIFF = ` translatable attribute + + file.ts + 2 + translatable element with placeholders + + file.ts + 3 + {VAR_PLURAL, plural, =0 {test} } + + file.ts + 4 + foo + + file.ts + 5 + + + file.ts + 6 + d m foo + + file.ts + 7 + d m foo + + file.ts + 8 + + + file.ts + 9 + ph names {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {deeply nested} } } } + + file.ts + 10 + {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {deeply nested} } } } + + file.ts + 11 + @@ -83,10 +124,18 @@ const LOAD_XLIFF = ` translatable attribute etubirtta elbatalsnart + + file.ts + 1 + translatable element with placeholders footnemele elbatalsnart sredlohecalp htiw + + file.ts + 2 + {VAR_PLURAL, plural, =0 {test} } @@ -95,27 +144,47 @@ const LOAD_XLIFF = ` foo oof + + file.ts + 3 + d m foo toto + + file.ts + 4 + d m foo tata + + file.ts + 5 + + + file.ts + 6 + ph names + + file.ts + 6 + ph names @@ -136,7 +205,7 @@ export function main(): void { function toXliff(html: string, locale: string | null = null): string { const catalog = new MessageBundle(new HtmlParser, [], {}, locale); - catalog.updateFromTemplate(html, '', DEFAULT_INTERPOLATION_CONFIG); + catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG); return catalog.write(serializer); } diff --git a/packages/compiler/test/i18n/serializers/xmb_spec.ts b/packages/compiler/test/i18n/serializers/xmb_spec.ts index cb8c535fd5..58084111b4 100644 --- a/packages/compiler/test/i18n/serializers/xmb_spec.ts +++ b/packages/compiler/test/i18n/serializers/xmb_spec.ts @@ -21,7 +21,9 @@ export function main(): void {

foo

foo

{ count, plural, =0 { { sex, select, other {

deeply nested

}} }}

-

{ count, plural, =0 { { sex, select, other {

deeply nested

}} }}

`; +

{ count, plural, =0 { { sex, select, other {

deeply nested

}} }}

+

multi +lines

`; const XMB = ` ]> - translatable element <b>with placeholders</b> INTERPOLATION - {VAR_PLURAL, plural, =0 {<p>test</p>} } - foo - foo - foo - {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p>deeply nested</p>} } } } - {VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p>deeply nested</p>} } } } + file.ts:3translatable element <b>with placeholders</b> INTERPOLATION + file.ts:4{VAR_PLURAL, plural, =0 {<p>test</p>} } + file.ts:5foo + file.ts:6foo + file.ts:7foo + file.ts:8{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p>deeply nested</p>} } } } + file.ts:9{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<p>deeply nested</p>} } } } + file.ts:10,11multi +lines `; it('should write a valid xmb file', () => { - expect(toXmb(HTML)).toEqual(XMB); + expect(toXmb(HTML, 'file.ts')).toEqual(XMB); // the locale is not specified in the xmb file - expect(toXmb(HTML, 'fr')).toEqual(XMB); + expect(toXmb(HTML, 'file.ts', 'fr')).toEqual(XMB); }); it('should throw when trying to load an xmb file', () => { @@ -71,11 +75,11 @@ export function main(): void { }); } -function toXmb(html: string, locale: string | null = null): string { +function toXmb(html: string, url: string, locale: string | null = null): string { const catalog = new MessageBundle(new HtmlParser, [], {}, locale); const serializer = new Xmb(); - catalog.updateFromTemplate(html, '', DEFAULT_INTERPOLATION_CONFIG); + catalog.updateFromTemplate(html, url, DEFAULT_INTERPOLATION_CONFIG); return catalog.write(serializer); } diff --git a/packages/compiler/test/i18n/translation_bundle_spec.ts b/packages/compiler/test/i18n/translation_bundle_spec.ts index d0dff34e65..1a78e89d2b 100644 --- a/packages/compiler/test/i18n/translation_bundle_spec.ts +++ b/packages/compiler/test/i18n/translation_bundle_spec.ts @@ -17,8 +17,9 @@ import {_extractMessages} from './i18n_parser_spec'; export function main(): void { describe('TranslationBundle', () => { const file = new ParseSourceFile('content', 'url'); - const location = new ParseLocation(file, 0, 0, 0); - const span = new ParseSourceSpan(location, null !); + const startLocation = new ParseLocation(file, 0, 0, 0); + const endLocation = new ParseLocation(file, 0, 0, 7); + const span = new ParseSourceSpan(startLocation, endLocation); const srcNode = new i18n.Text('src', span); it('should translate a plain message', () => { From 886cca028f31ed9dbaed97e7116a15f78b76cb6f Mon Sep 17 00:00:00 2001 From: Dzmitry Shylovich Date: Wed, 5 Apr 2017 02:00:40 +0300 Subject: [PATCH 0026/1039] refactor(router): misc refactoring --- packages/router/src/apply_redirects.ts | 4 ++-- packages/router/src/config.ts | 12 ++++++++---- packages/router/src/router.ts | 4 ++-- packages/router/src/router_config_loader.ts | 6 +----- packages/router/src/router_preloader.ts | 16 ++++++++-------- packages/router/test/apply_redirects.spec.ts | 3 +-- tools/public_api_guard/router/router.d.ts | 2 +- 7 files changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/router/src/apply_redirects.ts b/packages/router/src/apply_redirects.ts index 3a801227ca..cee485c375 100644 --- a/packages/router/src/apply_redirects.ts +++ b/packages/router/src/apply_redirects.ts @@ -18,8 +18,8 @@ import {map} from 'rxjs/operator/map'; import {mergeMap} from 'rxjs/operator/mergeMap'; import {EmptyError} from 'rxjs/util/EmptyError'; -import {InternalRoute, Route, Routes} from './config'; -import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader'; +import {InternalRoute, LoadedRouterConfig, Route, Routes} from './config'; +import {RouterConfigLoader} from './router_config_loader'; import {PRIMARY_OUTLET, Params, defaultUrlMatcher, navigationCancelingError} from './shared'; import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree'; import {andObservables, forEach, waitForMap, wrapIntoObservable} from './utils/collection'; diff --git a/packages/router/src/config.ts b/packages/router/src/config.ts index 6e0207d952..b80bf3eb85 100644 --- a/packages/router/src/config.ts +++ b/packages/router/src/config.ts @@ -6,13 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {NgModuleFactory, Type} from '@angular/core'; +import {NgModuleFactory, NgModuleRef, Type} from '@angular/core'; import {Observable} from 'rxjs/Observable'; - import {PRIMARY_OUTLET} from './shared'; import {UrlSegment, UrlSegmentGroup} from './url_tree'; - /** * @whatItDoes Represents router configuration. * @@ -359,11 +357,17 @@ export interface Route { children?: Routes; loadChildren?: LoadChildren; runGuardsAndResolvers?: RunGuardsAndResolvers; + /** @internal */ + _loadedConfig?: LoadedRouterConfig; +} + +export class LoadedRouterConfig { + constructor(public routes: Route[], public module: NgModuleRef) {} } export interface InternalRoute extends Route { // `LoadedRouterConfig` loaded via a Route `loadChildren` - _loadedConfig?: any; + _loadedConfig?: LoadedRouterConfig; } export function validateConfig(config: Routes, parentPath: string = ''): void { diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index d4ad6aa330..f059b39e67 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -22,14 +22,14 @@ import {mergeMap} from 'rxjs/operator/mergeMap'; import {reduce} from 'rxjs/operator/reduce'; import {applyRedirects} from './apply_redirects'; -import {InternalRoute, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config'; +import {InternalRoute, LoadedRouterConfig, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config'; import {createRouterState} from './create_router_state'; import {createUrlTree} from './create_url_tree'; import {RouterOutlet} from './directives/router_outlet'; import {Event, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, RouteConfigLoadEnd, RouteConfigLoadStart, RoutesRecognized} from './events'; import {recognize} from './recognize'; import {DetachedRouteHandle, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy'; -import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader'; +import {RouterConfigLoader} from './router_config_loader'; import {RouterOutletMap} from './router_outlet_map'; import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state'; import {PRIMARY_OUTLET, Params, isNavigationCancelingError} from './shared'; diff --git a/packages/router/src/router_config_loader.ts b/packages/router/src/router_config_loader.ts index 943ea62111..b91bd28b5c 100644 --- a/packages/router/src/router_config_loader.ts +++ b/packages/router/src/router_config_loader.ts @@ -12,7 +12,7 @@ import {fromPromise} from 'rxjs/observable/fromPromise'; import {of } from 'rxjs/observable/of'; import {map} from 'rxjs/operator/map'; import {mergeMap} from 'rxjs/operator/mergeMap'; -import {LoadChildren, Route} from './config'; +import {LoadChildren, LoadedRouterConfig, Route} from './config'; import {flatten, wrapIntoObservable} from './utils/collection'; /** @@ -21,10 +21,6 @@ import {flatten, wrapIntoObservable} from './utils/collection'; */ export const ROUTES = new InjectionToken('ROUTES'); -export class LoadedRouterConfig { - constructor(public routes: Route[], public module: NgModuleRef) {} -} - export class RouterConfigLoader { constructor( private loader: NgModuleFactoryLoader, private compiler: Compiler, diff --git a/packages/router/src/router_preloader.ts b/packages/router/src/router_preloader.ts index c8ed0a2f0f..7c897c87f8 100644 --- a/packages/router/src/router_preloader.ts +++ b/packages/router/src/router_preloader.ts @@ -6,7 +6,7 @@ *found in the LICENSE file at https://angular.io/license */ -import {Compiler, Injectable, Injector, NgModuleFactoryLoader, NgModuleRef} from '@angular/core'; +import {Compiler, Injectable, Injector, NgModuleFactoryLoader, NgModuleRef, OnDestroy} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import {Subscription} from 'rxjs/Subscription'; import {from} from 'rxjs/observable/from'; @@ -16,8 +16,8 @@ import {concatMap} from 'rxjs/operator/concatMap'; import {filter} from 'rxjs/operator/filter'; import {mergeAll} from 'rxjs/operator/mergeAll'; import {mergeMap} from 'rxjs/operator/mergeMap'; -import {InternalRoute, Route, Routes} from './config'; -import {NavigationEnd, RouteConfigLoadEnd, RouteConfigLoadStart} from './events'; +import {InternalRoute, LoadedRouterConfig, Route, Routes} from './config'; +import {Event, NavigationEnd, RouteConfigLoadEnd, RouteConfigLoadStart} from './events'; import {Router} from './router'; import {RouterConfigLoader} from './router_config_loader'; @@ -73,7 +73,7 @@ export class NoPreloading implements PreloadingStrategy { * @stable */ @Injectable() -export class RouterPreloader { +export class RouterPreloader implements OnDestroy { private loader: RouterConfigLoader; private subscription: Subscription; @@ -87,8 +87,8 @@ export class RouterPreloader { }; setUpPreloading(): void { - const navigations = filter.call(this.router.events, (e: any) => e instanceof NavigationEnd); - this.subscription = concatMap.call(navigations, () => this.preload()).subscribe(() => {}); + const navigations$ = filter.call(this.router.events, (e: Event) => e instanceof NavigationEnd); + this.subscription = concatMap.call(navigations$, () => this.preload()).subscribe(() => {}); } preload(): Observable { @@ -121,8 +121,8 @@ export class RouterPreloader { private preloadConfig(ngModule: NgModuleRef, route: InternalRoute): Observable { return this.preloadingStrategy.preload(route, () => { - const loaded = this.loader.load(ngModule.injector, route); - return mergeMap.call(loaded, (config: any): any => { + const loaded$ = this.loader.load(ngModule.injector, route); + return mergeMap.call(loaded$, (config: LoadedRouterConfig) => { route._loadedConfig = config; return this.processRoutes(config.module, config.routes); }); diff --git a/packages/router/test/apply_redirects.spec.ts b/packages/router/test/apply_redirects.spec.ts index 4212f203f8..fb06c2d0cd 100644 --- a/packages/router/test/apply_redirects.spec.ts +++ b/packages/router/test/apply_redirects.spec.ts @@ -12,8 +12,7 @@ import {Observable} from 'rxjs/Observable'; import {of } from 'rxjs/observable/of'; import {applyRedirects} from '../src/apply_redirects'; -import {Routes} from '../src/config'; -import {LoadedRouterConfig} from '../src/router_config_loader'; +import {LoadedRouterConfig, Routes} from '../src/config'; import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree'; describe('applyRedirects', () => { diff --git a/tools/public_api_guard/router/router.d.ts b/tools/public_api_guard/router/router.d.ts index 80b574e659..0387427f58 100644 --- a/tools/public_api_guard/router/router.d.ts +++ b/tools/public_api_guard/router/router.d.ts @@ -354,7 +354,7 @@ export declare class RouterOutletMap { } /** @stable */ -export declare class RouterPreloader { +export declare class RouterPreloader implements OnDestroy { constructor(router: Router, moduleLoader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, preloadingStrategy: PreloadingStrategy); ngOnDestroy(): void; preload(): Observable; From ea4afebeb930a720f32cfa689216c12e6eff9c70 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 11 Apr 2017 08:34:58 -0700 Subject: [PATCH 0027/1039] refactor(router): drop the `InternalRoute` interface --- packages/router/src/apply_redirects.ts | 7 +++--- packages/router/src/config.ts | 10 ++++---- packages/router/src/recognize.ts | 4 ++-- packages/router/src/router.ts | 6 ++--- packages/router/src/router_preloader.ts | 7 +++--- packages/router/test/apply_redirects.spec.ts | 24 +++++++++---------- packages/router/test/integration.spec.ts | 2 +- packages/router/test/router_preloader.spec.ts | 18 +++++++------- 8 files changed, 37 insertions(+), 41 deletions(-) diff --git a/packages/router/src/apply_redirects.ts b/packages/router/src/apply_redirects.ts index cee485c375..0805761d1b 100644 --- a/packages/router/src/apply_redirects.ts +++ b/packages/router/src/apply_redirects.ts @@ -18,7 +18,7 @@ import {map} from 'rxjs/operator/map'; import {mergeMap} from 'rxjs/operator/mergeMap'; import {EmptyError} from 'rxjs/util/EmptyError'; -import {InternalRoute, LoadedRouterConfig, Route, Routes} from './config'; +import {LoadedRouterConfig, Route, Routes} from './config'; import {RouterConfigLoader} from './router_config_loader'; import {PRIMARY_OUTLET, Params, defaultUrlMatcher, navigationCancelingError} from './shared'; import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree'; @@ -247,7 +247,7 @@ class ApplyRedirects { } private matchSegmentAgainstRoute( - ngModule: NgModuleRef, rawSegmentGroup: UrlSegmentGroup, route: InternalRoute, + ngModule: NgModuleRef, rawSegmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[]): Observable { if (route.path === '**') { if (route.loadChildren) { @@ -292,8 +292,7 @@ class ApplyRedirects { }); } - private getChildConfig(ngModule: NgModuleRef, route: InternalRoute): - Observable { + private getChildConfig(ngModule: NgModuleRef, route: Route): Observable { if (route.children) { // The children belong to the same module return of (new LoadedRouterConfig(route.children, ngModule)); diff --git a/packages/router/src/config.ts b/packages/router/src/config.ts index b80bf3eb85..de5ef3dbb0 100644 --- a/packages/router/src/config.ts +++ b/packages/router/src/config.ts @@ -357,7 +357,10 @@ export interface Route { children?: Routes; loadChildren?: LoadChildren; runGuardsAndResolvers?: RunGuardsAndResolvers; - /** @internal */ + /** + * Filled for routes with `loadChildren` once the module has been loaded + * @internal + */ _loadedConfig?: LoadedRouterConfig; } @@ -365,11 +368,6 @@ export class LoadedRouterConfig { constructor(public routes: Route[], public module: NgModuleRef) {} } -export interface InternalRoute extends Route { - // `LoadedRouterConfig` loaded via a Route `loadChildren` - _loadedConfig?: LoadedRouterConfig; -} - export function validateConfig(config: Routes, parentPath: string = ''): void { // forEach doesn't iterate undefined values for (let i = 0; i < config.length; i++) { diff --git a/packages/router/src/recognize.ts b/packages/router/src/recognize.ts index 169bd84484..684e2c6a1b 100644 --- a/packages/router/src/recognize.ts +++ b/packages/router/src/recognize.ts @@ -11,7 +11,7 @@ import {Observable} from 'rxjs/Observable'; import {Observer} from 'rxjs/Observer'; import {of } from 'rxjs/observable/of'; -import {Data, InternalRoute, ResolveData, Route, Routes} from './config'; +import {Data, ResolveData, Route, Routes} from './config'; import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state'; import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared'; import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree'; @@ -154,7 +154,7 @@ function sortActivatedRouteSnapshots(nodes: TreeNode[]): }); } -function getChildConfig(route: InternalRoute): Route[] { +function getChildConfig(route: Route): Route[] { if (route.children) { return route.children; } diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index f059b39e67..92a815e854 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -22,7 +22,7 @@ import {mergeMap} from 'rxjs/operator/mergeMap'; import {reduce} from 'rxjs/operator/reduce'; import {applyRedirects} from './apply_redirects'; -import {InternalRoute, LoadedRouterConfig, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config'; +import {LoadedRouterConfig, QueryParamsHandling, ResolveData, Route, Routes, RunGuardsAndResolvers, validateConfig} from './config'; import {createRouterState} from './create_router_state'; import {createUrlTree} from './create_url_tree'; import {RouterOutlet} from './directives/router_outlet'; @@ -1168,7 +1168,7 @@ function advanceActivatedRouteNodeAndItsChildren(node: TreeNode) function parentLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig { for (let s = snapshot.parent; s; s = s.parent) { - const route: InternalRoute = s._routeConfig; + const route = s._routeConfig; if (route && route._loadedConfig) return route._loadedConfig; if (route && route.component) return null; } @@ -1180,7 +1180,7 @@ function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConf if (!snapshot) return null; for (let s = snapshot.parent; s; s = s.parent) { - const route: InternalRoute = s._routeConfig; + const route = s._routeConfig; if (route && route._loadedConfig) return route._loadedConfig; } diff --git a/packages/router/src/router_preloader.ts b/packages/router/src/router_preloader.ts index 7c897c87f8..a4a9177064 100644 --- a/packages/router/src/router_preloader.ts +++ b/packages/router/src/router_preloader.ts @@ -16,7 +16,7 @@ import {concatMap} from 'rxjs/operator/concatMap'; import {filter} from 'rxjs/operator/filter'; import {mergeAll} from 'rxjs/operator/mergeAll'; import {mergeMap} from 'rxjs/operator/mergeMap'; -import {InternalRoute, LoadedRouterConfig, Route, Routes} from './config'; +import {LoadedRouterConfig, Route, Routes} from './config'; import {Event, NavigationEnd, RouteConfigLoadEnd, RouteConfigLoadStart} from './events'; import {Router} from './router'; import {RouterConfigLoader} from './router_config_loader'; @@ -100,8 +100,7 @@ export class RouterPreloader implements OnDestroy { private processRoutes(ngModule: NgModuleRef, routes: Routes): Observable { const res: Observable[] = []; - for (const r of routes) { - const route: InternalRoute = r; + for (const route of routes) { // we already have the config loaded, just recurse if (route.loadChildren && !route.canLoad && route._loadedConfig) { const childConfig = route._loadedConfig; @@ -119,7 +118,7 @@ export class RouterPreloader implements OnDestroy { return mergeAll.call(from(res)); } - private preloadConfig(ngModule: NgModuleRef, route: InternalRoute): Observable { + private preloadConfig(ngModule: NgModuleRef, route: Route): Observable { return this.preloadingStrategy.preload(route, () => { const loaded$ = this.loader.load(ngModule.injector, route); return mergeMap.call(loaded$, (config: LoadedRouterConfig) => { diff --git a/packages/router/test/apply_redirects.spec.ts b/packages/router/test/apply_redirects.spec.ts index fb06c2d0cd..5e1e044b1e 100644 --- a/packages/router/test/apply_redirects.spec.ts +++ b/packages/router/test/apply_redirects.spec.ts @@ -162,12 +162,12 @@ describe('applyRedirects', () => { return of (loadedConfig); } }; - const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}]; + const config: Routes = [{path: 'a', component: ComponentA, loadChildren: 'children'}]; applyRedirects(testModule.injector, loader, serializer, tree('a/b'), config) .forEach(r => { compareTrees(r, tree('/a/b')); - expect((config[0])._loadedConfig).toBe(loadedConfig); + expect(config[0]._loadedConfig).toBe(loadedConfig); }); }); @@ -289,12 +289,12 @@ describe('applyRedirects', () => { const loader = {load: (injector: any, p: any) => of (loadedConfig)}; - const config = + const config: Routes = [{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}]; applyRedirects(testModule.injector, loader, serializer, tree(''), config).forEach(r => { compareTrees(r, tree('a')); - expect((config[1])._loadedConfig).toBe(loadedConfig); + expect(config[1]._loadedConfig).toBe(loadedConfig); }); }); @@ -310,7 +310,7 @@ describe('applyRedirects', () => { } }; - const config = [{path: 'a', loadChildren: 'children'}]; + const config: Routes = [{path: 'a', loadChildren: 'children'}]; applyRedirects(testModule.injector, loader, serializer, tree('a?k1'), config) .subscribe(r => {}); @@ -319,7 +319,7 @@ describe('applyRedirects', () => { .subscribe( r => { compareTrees(r, tree('a?k2')); - expect((config[0])._loadedConfig).toBe(loadedConfig); + expect(config[0]._loadedConfig).toBe(loadedConfig); }, (e) => { throw 'Should not reach'; }); }); @@ -329,10 +329,10 @@ describe('applyRedirects', () => { const loader = {load: (injector: any, p: any) => of (loadedConfig)}; - const config = [{path: '**', loadChildren: 'children'}]; + const config: Routes = [{path: '**', loadChildren: 'children'}]; applyRedirects(testModule.injector, loader, serializer, tree('xyz'), config) - .forEach(r => { expect((config[0])._loadedConfig).toBe(loadedConfig); }); + .forEach(r => { expect(config[0]._loadedConfig).toBe(loadedConfig); }); }); it('should load the configuration after a local redirect from a wildcard route', () => { @@ -340,11 +340,11 @@ describe('applyRedirects', () => { const loader = {load: (injector: any, p: any) => of (loadedConfig)}; - const config = + const config: Routes = [{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: 'not-found'}]; applyRedirects(testModule.injector, loader, serializer, tree('xyz'), config) - .forEach(r => { expect((config[0])._loadedConfig).toBe(loadedConfig); }); + .forEach(r => { expect(config[0]._loadedConfig).toBe(loadedConfig); }); }); it('should load the configuration after an absolute redirect from a wildcard route', () => { @@ -352,11 +352,11 @@ describe('applyRedirects', () => { const loader = {load: (injector: any, p: any) => of (loadedConfig)}; - const config = + const config: Routes = [{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: '/not-found'}]; applyRedirects(testModule.injector, loader, serializer, tree('xyz'), config) - .forEach(r => { expect((config[0])._loadedConfig).toBe(loadedConfig); }); + .forEach(r => { expect(config[0]._loadedConfig).toBe(loadedConfig); }); }); }); diff --git a/packages/router/test/integration.spec.ts b/packages/router/test/integration.spec.ts index c62a56ef55..68612befd8 100644 --- a/packages/router/test/integration.spec.ts +++ b/packages/router/test/integration.spec.ts @@ -3049,7 +3049,7 @@ describe('Integration', () => { router.navigateByUrl('/blank'); advance(fixture); - const config: any = router.config; + const config = router.config; const firstConfig = config[1]._loadedConfig; expect(firstConfig).toBeDefined(); diff --git a/packages/router/test/router_preloader.spec.ts b/packages/router/test/router_preloader.spec.ts index be48638ffe..c1b8d2afd4 100644 --- a/packages/router/test/router_preloader.spec.ts +++ b/packages/router/test/router_preloader.spec.ts @@ -10,7 +10,7 @@ import {Compiler, Component, NgModule, NgModuleFactoryLoader, NgModuleRef} from import {TestBed, fakeAsync, inject, tick} from '@angular/core/testing'; import {Route, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouterModule} from '../index'; -import {LoadedRouterConfig} from '../src/router_config_loader'; +import {LoadedRouterConfig} from '../src/config'; import {PreloadAllModules, PreloadingStrategy, RouterPreloader} from '../src/router_preloader'; import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing'; @@ -46,7 +46,7 @@ describe('RouterPreloader', () => { tick(); const c = router.config; - expect(!!((c[0])._loadedConfig)).toBe(false); + expect(c[0]._loadedConfig).not.toBeDefined(); }))); }); @@ -97,12 +97,12 @@ describe('RouterPreloader', () => { const c = router.config; expect(c[0].loadChildren).toEqual('expected'); - const loadedConfig: LoadedRouterConfig = (c[0])._loadedConfig; + const loadedConfig: LoadedRouterConfig = c[0]._loadedConfig; const module: any = loadedConfig.module; expect(loadedConfig.routes[0].path).toEqual('LoadedModule1'); expect(module.parent).toBe(testModule); - const loadedConfig2: LoadedRouterConfig = (loadedConfig.routes[0])._loadedConfig; + const loadedConfig2: LoadedRouterConfig = loadedConfig.routes[0]._loadedConfig; const module2: any = loadedConfig2.module; expect(loadedConfig2.routes[0].path).toEqual('LoadedModule2'); expect(module2.parent).toBe(module); @@ -165,12 +165,12 @@ describe('RouterPreloader', () => { const c = router.config; - const loadedConfig: LoadedRouterConfig = (c[0])._loadedConfig; + const loadedConfig: LoadedRouterConfig = c[0]._loadedConfig; const module: any = loadedConfig.module; expect(module.parent).toBe(testModule); - const loadedConfig2: LoadedRouterConfig = (loadedConfig.routes[0])._loadedConfig; - const loadedConfig3: LoadedRouterConfig = (loadedConfig2.routes[0])._loadedConfig; + const loadedConfig2: LoadedRouterConfig = loadedConfig.routes[0]._loadedConfig; + const loadedConfig3: LoadedRouterConfig = loadedConfig2.routes[0]._loadedConfig; const module3: any = loadedConfig3.module; expect(module3.parent).toBe(module2); }))); @@ -204,8 +204,8 @@ describe('RouterPreloader', () => { tick(); const c = router.config; - expect(!!((c[0])._loadedConfig)).toBe(false); - expect(!!((c[1])._loadedConfig)).toBe(true); + expect(c[0]._loadedConfig).not.toBeDefined(); + expect(c[1]._loadedConfig).toBeDefined(); }))); }); }); From bd704c90dd205f278769467262f383722d6e6a9f Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 14 Apr 2017 09:41:41 -0700 Subject: [PATCH 0028/1039] fix(compiler): fix build error in xliff2 --- .../integrationtest/test/i18n_spec.ts | 5 +++-- .../compiler/src/i18n/serializers/xliff2.ts | 18 +++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/compiler-cli/integrationtest/test/i18n_spec.ts b/packages/compiler-cli/integrationtest/test/i18n_spec.ts index eb80b5f70b..787def3142 100644 --- a/packages/compiler-cli/integrationtest/test/i18n_spec.ts +++ b/packages/compiler-cli/integrationtest/test/i18n_spec.ts @@ -98,9 +98,10 @@ const EXPECTED_XLIFF2 = ` Welcome
- + - other-3rdP-component + other-3rdP-component +multi-lines diff --git a/packages/compiler/src/i18n/serializers/xliff2.ts b/packages/compiler/src/i18n/serializers/xliff2.ts index cb7fd960c8..e2940491ef 100644 --- a/packages/compiler/src/i18n/serializers/xliff2.ts +++ b/packages/compiler/src/i18n/serializers/xliff2.ts @@ -97,7 +97,7 @@ export class Xliff2 extends Serializer { throw new Error(`xliff2 parse errors:\n${errors.join('\n')}`); } - return {locale, i18nNodesByMsgId}; + return {locale: locale !, i18nNodesByMsgId}; } digest(message: i18n.Message): string { return decimalDigest(message); } @@ -177,7 +177,7 @@ class _WriteVisitor implements i18n.Visitor { // Extract messages as xml nodes from the xliff file class Xliff2Parser implements ml.Visitor { - private _unitMlString: string; + private _unitMlString: string|null; private _errors: I18nError[]; private _msgIdToHtml: {[msgId: string]: string}; private _locale: string|null = null; @@ -225,9 +225,9 @@ class Xliff2Parser implements ml.Visitor { break; case _TARGET_TAG: - const innerTextStart = element.startSourceSpan.end.offset; - const innerTextEnd = element.endSourceSpan.start.offset; - const content = element.startSourceSpan.start.file.content; + const innerTextStart = element.startSourceSpan !.end.offset; + const innerTextEnd = element.endSourceSpan !.start.offset; + const content = element.startSourceSpan !.start.file.content; const innerText = content.slice(innerTextStart, innerTextEnd); this._unitMlString = innerText; break; @@ -290,7 +290,7 @@ class XmlToI18n implements ml.Visitor { visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); } - visitElement(el: ml.Element, context: any): i18n.Node[] { + visitElement(el: ml.Element, context: any): i18n.Node[]|null { switch (el.name) { case _PLACEHOLDER_TAG: const nameAttr = el.attrs.find((attr) => attr.name === 'equiv'); @@ -312,7 +312,9 @@ class XmlToI18n implements ml.Visitor { const startId = startAttr.value; const endId = endAttr.value; - return [].concat( + const nodes: i18n.Node[] = []; + + return nodes.concat( new i18n.Placeholder('', startId, el.sourceSpan), ...el.children.map(node => node.visit(this, null)), new i18n.Placeholder('', endId, el.sourceSpan)); @@ -321,6 +323,8 @@ class XmlToI18n implements ml.Visitor { default: this._addError(el, `Unexpected tag`); } + + return null; } visitExpansion(icu: ml.Expansion, context: any) { From 590e68c2512d39bdf5397050213fb05934e650ac Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 14 Apr 2017 10:56:14 -0700 Subject: [PATCH 0029/1039] Revert "feat(upgrade): allow setting the angularjs lib at runtime (#15168)" This reverts commit 8ad464d90e735ab3f59bdb95191ef9110e63ac84. Breaks G3. --- packages/upgrade/src/common/angular1.ts | 66 +++++-------------- packages/upgrade/static/public_api.ts | 1 - .../test/static/integration/injection_spec.ts | 30 +-------- tools/public_api_guard/upgrade/static.d.ts | 6 -- 4 files changed, 16 insertions(+), 87 deletions(-) diff --git a/packages/upgrade/src/common/angular1.ts b/packages/upgrade/src/common/angular1.ts index 20b93c1cd9..267e8dbac8 100644 --- a/packages/upgrade/src/common/angular1.ts +++ b/packages/upgrade/src/common/angular1.ts @@ -201,7 +201,6 @@ function noNg() { throw new Error('AngularJS v1.x is not loaded!'); } - let angular: { bootstrap: (e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig) => void, @@ -209,62 +208,27 @@ let angular: { element: (e: Element | string) => IAugmentedJQuery, version: {major: number}, resumeBootstrap?: () => void, getTestability: (e: Element) => ITestabilityService +} = { + bootstrap: noNg, + module: noNg, + element: noNg, + version: noNg, + resumeBootstrap: noNg, + getTestability: noNg }; + try { if (window.hasOwnProperty('angular')) { - setAngularLib((window).angular); + angular = (window).angular; } } catch (e) { - setAngularLib({ - bootstrap: noNg, - module: noNg, - element: noNg, - version: noNg, - resumeBootstrap: noNg, - getTestability: noNg - }); -} - -/** - * Resets the AngularJS library. - * - * Used when angularjs is loaded lazily, and not available on `window`. - * - * @stable - */ -export function setAngularLib(ng: any): void { - angular = ng; -} - -/** - * Returns the current version of the AngularJS library. - * - * @stable - */ -export function getAngularLib(): any { - return angular; -} - -export function bootstrap( - e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig): void { - angular.bootstrap(e, modules, config); -} - -export function module(prefix: string, dependencies?: string[]): IModule { - return angular.module(prefix, dependencies); -} - -export function element(e: Element | string): IAugmentedJQuery { - return angular.element(e); -} - -export function resumeBootstrap(): void { - angular.resumeBootstrap(); -} - -export function getTestability(e: Element): ITestabilityService { - return angular.getTestability(e); + // ignore in CJS mode. } +export const bootstrap = angular.bootstrap; +export const module = angular.module; +export const element = angular.element; export const version = angular.version; +export const resumeBootstrap = angular.resumeBootstrap; +export const getTestability = angular.getTestability; diff --git a/packages/upgrade/static/public_api.ts b/packages/upgrade/static/public_api.ts index 9d28d1c554..57b16cedc6 100644 --- a/packages/upgrade/static/public_api.ts +++ b/packages/upgrade/static/public_api.ts @@ -12,7 +12,6 @@ * Entry point for all public APIs of the upgrade/static package, allowing * Angular 1 and Angular 2+ to run side by side in the same application. */ -export {getAngularLib, 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'; diff --git a/packages/upgrade/test/static/integration/injection_spec.ts b/packages/upgrade/test/static/integration/injection_spec.ts index 8ee142c2f4..93b3e3c3a2 100644 --- a/packages/upgrade/test/static/integration/injection_spec.ts +++ b/packages/upgrade/test/static/integration/injection_spec.ts @@ -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} from '@angular/upgrade/static'; import {bootstrap, html} from '../test_helpers'; @@ -99,33 +99,5 @@ export function main() { expect(runBlockTriggered).toBeTruthy(); }); })); - - it('should allow resetting angular at runtime', async(() => { - let wrappedBootstrapepedCalled = false; - - const n: any = getAngularLib(); - - setAngularLib({ - bootstrap: (...args: any[]) => { - wrappedBootstrapepedCalled = true; - n.bootstrap(...args); - }, - module: n.module, - element: n.element, - version: n.version, - resumeBootstrap: n.resumeBootstrap, - getTestability: n.getTestability - }); - - @NgModule({imports: [BrowserModule, UpgradeModule]}) - class Ng2Module { - ngDoBootstrap() {} - } - - const ng1Module = angular.module('ng1Module', []); - - bootstrap(platformBrowserDynamic(), Ng2Module, html('
'), ng1Module) - .then((upgrade) => { expect(wrappedBootstrapepedCalled).toEqual(true); }); - })); }); } diff --git a/tools/public_api_guard/upgrade/static.d.ts b/tools/public_api_guard/upgrade/static.d.ts index 2eedf38587..40cee4c38d 100644 --- a/tools/public_api_guard/upgrade/static.d.ts +++ b/tools/public_api_guard/upgrade/static.d.ts @@ -9,12 +9,6 @@ export declare function downgradeComponent(info: { /** @experimental */ export declare function downgradeInjectable(token: any): Function; -/** @stable */ -export declare function getAngularLib(): any; - -/** @stable */ -export declare function setAngularLib(ng: any): void; - /** @experimental */ export declare class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { constructor(name: string, elementRef: ElementRef, injector: Injector); From 86396a43e9a8bea335f77af1301772b72d0444a2 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 14 Apr 2017 11:19:08 -0700 Subject: [PATCH 0030/1039] Revert "fix(compiler): ignore calls to unresolved symbols in metadata (#15970)" This reverts commit ce47d33cd94a242f23b47f287e975c80506c9a05. --- packages/compiler/src/aot/static_reflector.ts | 2 +- .../test/aot/static_reflector_spec.ts | 24 ------------------- .../test/aot/static_symbol_resolver_spec.ts | 3 --- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/packages/compiler/src/aot/static_reflector.ts b/packages/compiler/src/aot/static_reflector.ts index 6af817f97b..bb97bd1676 100644 --- a/packages/compiler/src/aot/static_reflector.ts +++ b/packages/compiler/src/aot/static_reflector.ts @@ -601,7 +601,7 @@ export class StaticReflector implements ɵReflectorReader { case 'ignore': return expression; } - return IGNORE; + return null; } return mapStringMap(expression, (value, name) => simplify(value)); } diff --git a/packages/compiler/test/aot/static_reflector_spec.ts b/packages/compiler/test/aot/static_reflector_spec.ts index 9ea0ffda15..dd6fc683b3 100644 --- a/packages/compiler/test/aot/static_reflector_spec.ts +++ b/packages/compiler/test/aot/static_reflector_spec.ts @@ -546,30 +546,6 @@ describe('StaticReflector', () => { expect(annotation.providers).toEqual([1, 2, 3, 4, 5, 6, 7]); }); - it('should ignore unresolved calls', () => { - const data = Object.create(DEFAULT_TEST_DATA); - const file = '/tmp/src/invalid-component.ts'; - data[file] = ` - import {Component} from '@angular/core'; - import {unknown} from 'unresolved'; - - @Component({ - selector: 'tmp', - template: () => {}, - providers: [triggers()] - }) - export class BadComponent { - - } - `; - init(data, [], () => {}, {verboseInvalidExpression: true}); - - const badComponent = reflector.getStaticSymbol(file, 'BadComponent'); - const annotations = reflector.annotations(badComponent); - const annotation = annotations[0]; - expect(annotation.providers).toEqual([]); - }); - describe('inheritance', () => { class ClassDecorator { constructor(public value: any) {} diff --git a/packages/compiler/test/aot/static_symbol_resolver_spec.ts b/packages/compiler/test/aot/static_symbol_resolver_spec.ts index 944d6c503c..9adfc0ebdf 100644 --- a/packages/compiler/test/aot/static_symbol_resolver_spec.ts +++ b/packages/compiler/test/aot/static_symbol_resolver_spec.ts @@ -436,9 +436,6 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost { } return baseName + '.d.ts'; } - if (modulePath == 'unresolved') { - return undefined; - } return '/tmp/' + modulePath + '.d.ts'; } From c36ec9bf6043cb25bbe8a705ae7342fa77b9ffdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mis=CC=8Cko=20Hevery?= Date: Fri, 24 Mar 2017 09:55:16 -0700 Subject: [PATCH 0031/1039] fix(http): Update types for TypeScript nullability support --- packages/http/src/backends/browser_jsonp.ts | 2 +- packages/http/src/backends/xhr_backend.ts | 2 +- packages/http/src/base_request_options.ts | 14 ++--- packages/http/src/base_response_options.ts | 12 ++-- packages/http/src/headers.ts | 13 +++-- packages/http/src/http.ts | 11 ++-- packages/http/src/http_utils.ts | 4 +- packages/http/src/interfaces.ts | 28 ++++----- packages/http/src/static_request.ts | 8 +-- packages/http/src/static_response.ts | 10 ++-- packages/http/src/url_search_params.ts | 2 +- .../http/test/backends/jsonp_backend_spec.ts | 9 +-- .../http/test/backends/mock_backend_spec.ts | 12 ++-- .../http/test/backends/xhr_backend_spec.ts | 50 ++++++++-------- packages/http/test/http_spec.ts | 4 +- packages/http/test/static_request_spec.ts | 19 +++--- packages/http/test/url_search_params_spec.ts | 8 +-- tools/public_api_guard/http/http.d.ts | 58 +++++++++---------- 18 files changed, 138 insertions(+), 128 deletions(-) diff --git a/packages/http/src/backends/browser_jsonp.ts b/packages/http/src/backends/browser_jsonp.ts index 2734836afc..d5112c7a88 100644 --- a/packages/http/src/backends/browser_jsonp.ts +++ b/packages/http/src/backends/browser_jsonp.ts @@ -10,7 +10,7 @@ import {Injectable} from '@angular/core'; let _nextRequestId = 0; export const JSONP_HOME = '__ng_jsonp__'; -let _jsonpConnections: {[key: string]: any} = null; +let _jsonpConnections: {[key: string]: any}|null = null; function _getJsonpConnections(): {[key: string]: any} { const w: {[key: string]: any} = typeof window == 'object' ? window : {}; diff --git a/packages/http/src/backends/xhr_backend.ts b/packages/http/src/backends/xhr_backend.ts index 923f805bad..b5262d1fb8 100644 --- a/packages/http/src/backends/xhr_backend.ts +++ b/packages/http/src/backends/xhr_backend.ts @@ -115,7 +115,7 @@ export class XHRConnection implements Connection { if (!req.headers.has('Accept')) { req.headers.append('Accept', 'application/json, text/plain, */*'); } - req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(','))); + req.headers.forEach((values, name) => _xhr.setRequestHeader(name !, values.join(','))); // Select the correct buffer type to store the response if (req.responseType != null && _xhr.responseType != null) { diff --git a/packages/http/src/base_request_options.ts b/packages/http/src/base_request_options.ts index 1d0942b95d..76e873c25b 100644 --- a/packages/http/src/base_request_options.ts +++ b/packages/http/src/base_request_options.ts @@ -44,11 +44,11 @@ export class RequestOptions { * Http method with which to execute a {@link Request}. * Acceptable methods are defined in the {@link RequestMethod} enum. */ - method: RequestMethod|string; + method: RequestMethod|string|null; /** * {@link Headers} to be attached to a {@link Request}. */ - headers: Headers; + headers: Headers|null; /** * Body to be used when creating a {@link Request}. */ @@ -56,7 +56,7 @@ export class RequestOptions { /** * Url with which to perform a {@link Request}. */ - url: string; + url: string|null; /** * Search parameters to be included in a {@link Request}. */ @@ -72,11 +72,11 @@ export class RequestOptions { /** * Enable use credentials for a {@link Request}. */ - withCredentials: boolean; + withCredentials: boolean|null; /* * Select a buffer to store the response, such as ArrayBuffer, Blob, Json (or Document) */ - responseType: ResponseContentType; + responseType: ResponseContentType|null; // TODO(Dzmitry): remove search when this.search is removed constructor( @@ -128,8 +128,8 @@ export class RequestOptions { }); } - private _mergeSearchParams(params: string|URLSearchParams| - {[key: string]: any | any[]}): URLSearchParams { + private _mergeSearchParams(params?: string|URLSearchParams|{[key: string]: any | any[]}| + null): URLSearchParams { if (!params) return this.params; if (params instanceof URLSearchParams) { diff --git a/packages/http/src/base_response_options.ts b/packages/http/src/base_response_options.ts index a9e56aebe3..1e62aea15c 100644 --- a/packages/http/src/base_response_options.ts +++ b/packages/http/src/base_response_options.ts @@ -46,25 +46,25 @@ export class ResponseOptions { /** * String, Object, ArrayBuffer or Blob representing the body of the {@link Response}. */ - body: string|Object|ArrayBuffer|Blob; + body: string|Object|ArrayBuffer|Blob|null; /** * Http {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html status code} * associated with the response. */ - status: number; + status: number|null; /** * Response {@link Headers headers} */ - headers: Headers; + headers: Headers|null; /** * @internal */ - statusText: string; + statusText: string|null; /** * @internal */ - type: ResponseType; - url: string; + type: ResponseType|null; + url: string|null; constructor({body, status, headers, statusText, type, url}: ResponseOptionsArgs = {}) { this.body = body != null ? body : null; this.status = status != null ? status : null; diff --git a/packages/http/src/headers.ts b/packages/http/src/headers.ts index dc1be57ce8..029362c9aa 100644 --- a/packages/http/src/headers.ts +++ b/packages/http/src/headers.ts @@ -41,7 +41,7 @@ export class Headers { _normalizedNames: Map = new Map(); // TODO(vicb): any -> string|string[] - constructor(headers?: Headers|{[name: string]: any}) { + constructor(headers?: Headers|{[name: string]: any}|null) { if (!headers) { return; } @@ -100,7 +100,8 @@ export class Headers { this._headers.delete(lcName); } - forEach(fn: (values: string[], name: string, headers: Map) => void): void { + forEach(fn: (values: string[], name: string|undefined, headers: Map) => void): + void { this._headers.forEach( (values, lcName) => fn(values, this._normalizedNames.get(lcName), this._headers)); } @@ -108,7 +109,7 @@ export class Headers { /** * Returns first header that matches given name. */ - get(name: string): string { + get(name: string): string|null { const values = this.getAll(name); if (values === null) { @@ -157,7 +158,7 @@ export class Headers { this._headers.forEach((values: string[], name: string) => { const split: string[] = []; values.forEach(v => split.push(...v.split(','))); - serialized[this._normalizedNames.get(name)] = split; + serialized[this._normalizedNames.get(name) !] = split; }); return serialized; @@ -166,8 +167,8 @@ export class Headers { /** * Returns list of header values for a given name. */ - getAll(name: string): string[] { - return this.has(name) ? this._headers.get(name.toLowerCase()) : null; + getAll(name: string): string[]|null { + return this.has(name) ? this._headers.get(name.toLowerCase()) || null : null; } /** diff --git a/packages/http/src/http.ts b/packages/http/src/http.ts index 4487eb5121..9f453832d5 100644 --- a/packages/http/src/http.ts +++ b/packages/http/src/http.ts @@ -8,9 +8,10 @@ import {Injectable} from '@angular/core'; import {Observable} from 'rxjs/Observable'; + import {BaseRequestOptions, RequestOptions} from './base_request_options'; import {RequestMethod} from './enums'; -import {ConnectionBackend, RequestOptionsArgs} from './interfaces'; +import {ConnectionBackend, RequestArgs, RequestOptionsArgs} from './interfaces'; import {Request} from './static_request'; import {Response} from './static_response'; @@ -19,8 +20,8 @@ function httpRequest(backend: ConnectionBackend, request: Request): Observable (status >= 200 && status < 300); -export function getResponseURL(xhr: any): string { +export function getResponseURL(xhr: any): string|null { if ('responseURL' in xhr) { return xhr.responseURL; } if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { return xhr.getResponseHeader('X-Request-URL'); } - return; + return null; } export function stringToArrayBuffer(input: String): ArrayBuffer { diff --git a/packages/http/src/interfaces.ts b/packages/http/src/interfaces.ts index 112e71d14d..8637d27869 100644 --- a/packages/http/src/interfaces.ts +++ b/packages/http/src/interfaces.ts @@ -46,21 +46,21 @@ export abstract class XSRFStrategy { abstract configureRequest(req: Request): vo * @experimental */ export interface RequestOptionsArgs { - url?: string; - method?: string|RequestMethod; + url?: string|null; + method?: string|RequestMethod|null; /** @deprecated from 4.0.0. Use params instead. */ - search?: string|URLSearchParams|{[key: string]: any | any[]}; - params?: string|URLSearchParams|{[key: string]: any | any[]}; - headers?: Headers; + search?: string|URLSearchParams|{[key: string]: any | any[]}|null; + params?: string|URLSearchParams|{[key: string]: any | any[]}|null; + headers?: Headers|null; body?: any; - withCredentials?: boolean; - responseType?: ResponseContentType; + withCredentials?: boolean|null; + responseType?: ResponseContentType|null; } /** * Required structure when constructing new Request(); */ -export interface RequestArgs extends RequestOptionsArgs { url: string; } +export interface RequestArgs extends RequestOptionsArgs { url: string|null; } /** * Interface for options to construct a Response, based on @@ -69,10 +69,10 @@ export interface RequestArgs extends RequestOptionsArgs { url: string; } * @experimental */ export interface ResponseOptionsArgs { - body?: string|Object|FormData|ArrayBuffer|Blob; - status?: number; - statusText?: string; - headers?: Headers; - type?: ResponseType; - url?: string; + body?: string|Object|FormData|ArrayBuffer|Blob|null; + status?: number|null; + statusText?: string|null; + headers?: Headers|null; + type?: ResponseType|null; + url?: string|null; } diff --git a/packages/http/src/static_request.ts b/packages/http/src/static_request.ts index 05947426f6..2e635913a3 100644 --- a/packages/http/src/static_request.ts +++ b/packages/http/src/static_request.ts @@ -75,7 +75,7 @@ export class Request extends Body { super(); // TODO: assert that url is present const url = requestOptions.url; - this.url = requestOptions.url; + this.url = requestOptions.url !; if (requestOptions.params) { const params = requestOptions.params.toString(); if (params.length > 0) { @@ -88,13 +88,13 @@ export class Request extends Body { } } this._body = requestOptions.body; - this.method = normalizeMethodName(requestOptions.method); + this.method = normalizeMethodName(requestOptions.method !); // TODO(jeffbcross): implement behavior // Defaults to 'omit', consistent with browser this.headers = new Headers(requestOptions.headers); this.contentType = this.detectContentType(); - this.withCredentials = requestOptions.withCredentials; - this.responseType = requestOptions.responseType; + this.withCredentials = requestOptions.withCredentials !; + this.responseType = requestOptions.responseType !; } /** diff --git a/packages/http/src/static_response.ts b/packages/http/src/static_response.ts index 199fd08af5..f46d4e0491 100644 --- a/packages/http/src/static_response.ts +++ b/packages/http/src/static_response.ts @@ -63,7 +63,7 @@ export class Response extends Body { * * Defaults to "OK" */ - statusText: string; + statusText: string|null; /** * Non-standard property * @@ -81,17 +81,17 @@ export class Response extends Body { * Headers object based on the `Headers` class in the [Fetch * Spec](https://fetch.spec.whatwg.org/#headers-class). */ - headers: Headers; + headers: Headers|null; constructor(responseOptions: ResponseOptions) { super(); this._body = responseOptions.body; - this.status = responseOptions.status; + this.status = responseOptions.status !; this.ok = (this.status >= 200 && this.status <= 299); this.statusText = responseOptions.statusText; this.headers = responseOptions.headers; - this.type = responseOptions.type; - this.url = responseOptions.url; + this.type = responseOptions.type !; + this.url = responseOptions.url !; } toString(): string { diff --git a/packages/http/src/url_search_params.ts b/packages/http/src/url_search_params.ts index 3effcad374..ffb1086da7 100644 --- a/packages/http/src/url_search_params.ts +++ b/packages/http/src/url_search_params.ts @@ -93,7 +93,7 @@ export class URLSearchParams { has(param: string): boolean { return this.paramsMap.has(param); } - get(param: string): string { + get(param: string): string|null { const storedParam = this.paramsMap.get(param); return Array.isArray(storedParam) ? storedParam[0] : null; diff --git a/packages/http/test/backends/jsonp_backend_spec.ts b/packages/http/test/backends/jsonp_backend_spec.ts index 5f0e4d501f..ecef2c529e 100644 --- a/packages/http/test/backends/jsonp_backend_spec.ts +++ b/packages/http/test/backends/jsonp_backend_spec.ts @@ -59,13 +59,14 @@ export function main() { ]); backend = injector.get(JSONPBackend); const base = new BaseRequestOptions(); - sampleRequest = new Request(base.merge(new RequestOptions({url: 'https://google.com'}))); + sampleRequest = + new Request(base.merge(new RequestOptions({url: 'https://google.com'})) as any); }); afterEach(() => { existingScripts = []; }); it('should create a connection', () => { - let instance: JSONPConnection; + let instance: JSONPConnection = undefined !; expect(() => instance = backend.createConnection(sampleRequest)).not.toThrow(); expect(instance).toBeAnInstanceOf(JSONPConnection); }); @@ -146,8 +147,8 @@ export function main() { RequestMethod.Head, RequestMethod.Patch] .forEach(method => { const base = new BaseRequestOptions(); - const req = new Request( - base.merge(new RequestOptions({url: 'https://google.com', method: method}))); + const req = new Request(base.merge( + new RequestOptions({url: 'https://google.com', method: method})) as any); expect(() => new JSONPConnection_(req, new MockBrowserJsonp()).response.subscribe()) .toThrowError(); }); diff --git a/packages/http/test/backends/mock_backend_spec.ts b/packages/http/test/backends/mock_backend_spec.ts index ee81874791..3b4640decc 100644 --- a/packages/http/test/backends/mock_backend_spec.ts +++ b/packages/http/test/backends/mock_backend_spec.ts @@ -31,9 +31,11 @@ export function main() { [{provide: ResponseOptions, useClass: BaseResponseOptions}, MockBackend]); backend = injector.get(MockBackend); const base = new BaseRequestOptions(); - sampleRequest1 = new Request(base.merge(new RequestOptions({url: 'https://google.com'}))); + sampleRequest1 = + new Request(base.merge(new RequestOptions({url: 'https://google.com'})) as any); sampleResponse1 = new Response(new ResponseOptions({body: 'response1'})); - sampleRequest2 = new Request(base.merge(new RequestOptions({url: 'https://google.com'}))); + sampleRequest2 = + new Request(base.merge(new RequestOptions({url: 'https://google.com'})) as any); sampleResponse2 = new Response(new ResponseOptions({body: 'response2'})); }); @@ -65,7 +67,7 @@ export function main() { it('should allow responding after subscription with an error', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { const connection: MockConnection = backend.createConnection(sampleRequest1); - connection.response.subscribe(null, () => { async.done(); }); + connection.response.subscribe(null !, () => { async.done(); }); connection.mockError(new Error('nope')); })); @@ -98,12 +100,12 @@ export function main() { xit('should allow double subscribing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { const responses: Response[] = [sampleResponse1, sampleResponse2]; - backend.connections.subscribe((c: MockConnection) => c.mockRespond(responses.shift())); + backend.connections.subscribe((c: MockConnection) => c.mockRespond(responses.shift() !)); const responseObservable: ReplaySubject = backend.createConnection(sampleRequest1).response; responseObservable.subscribe(res => expect(res.text()).toBe('response1')); responseObservable.subscribe( - res => expect(res.text()).toBe('response2'), null, async.done); + res => expect(res.text()).toBe('response2'), null !, async.done); })); // TODO(robwormald): readyStates are leaving? diff --git a/packages/http/test/backends/xhr_backend_spec.ts b/packages/http/test/backends/xhr_backend_spec.ts index f62840474c..8be7db3b00 100644 --- a/packages/http/test/backends/xhr_backend_spec.ts +++ b/packages/http/test/backends/xhr_backend_spec.ts @@ -75,7 +75,7 @@ class MockBrowserXHR extends BrowserXhr { removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); } - dispatchEvent(type: string) { this.callbacks.get(type)({}); } + dispatchEvent(type: string) { this.callbacks.get(type) !({}); } build() { const xhr = new MockBrowserXHR(); @@ -99,7 +99,8 @@ export function main() { beforeEach(inject([XHRBackend], (be: XHRBackend) => { backend = be; const base = new BaseRequestOptions(); - sampleRequest = new Request(base.merge(new RequestOptions({url: 'https://google.com'}))); + sampleRequest = + new Request(base.merge(new RequestOptions({url: 'https://google.com'})) as any); })); afterEach(() => { existingXHRs = []; }); @@ -163,7 +164,7 @@ export function main() { sampleRequest, new MockBrowserXHR(), new ResponseOptions({type: ResponseType.Error})); connection.response.subscribe( - (res: Response) => { expect(res.type).toBe(ResponseType.Error); }, null, + (res: Response) => { expect(res.type).toBe(ResponseType.Error); }, null !, () => { async.done(); }); existingXHRs[0].setStatusCode(200); existingXHRs[0].dispatchEvent('load'); @@ -181,7 +182,7 @@ export function main() { const connection = new XHRConnection( sampleRequest, new MockBrowserXHR(), new ResponseOptions({type: ResponseType.Error})); - connection.response.subscribe(null, (res: Response) => { + connection.response.subscribe(null !, (res: Response) => { expect(res.type).toBe(ResponseType.Error); async.done(); }); @@ -193,7 +194,7 @@ export function main() { const connection = new XHRConnection( sampleRequest, new MockBrowserXHR(), new ResponseOptions({type: ResponseType.Error})); - connection.response.subscribe(null, (res: Response) => { + connection.response.subscribe(null !, (res: Response) => { expect(res.type).toBe(ResponseType.Error); expect(res.status).toEqual(0); expect(res.statusText).toEqual(''); @@ -217,7 +218,7 @@ export function main() { const body = 'Some body to love'; const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({body: body})) as any), new MockBrowserXHR()); expect(sendSpy).not.toHaveBeenCalled(); connection.response.subscribe(); expect(sendSpy).toHaveBeenCalledWith(body); @@ -229,7 +230,8 @@ export function main() { const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({headers: headers}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({headers: headers})) as any), + new MockBrowserXHR()); connection.response.subscribe(); expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/xml'); expect(setRequestHeaderSpy).toHaveBeenCalledWith('Breaking-Bad', '<3'); @@ -240,7 +242,7 @@ export function main() { const headers = new Headers(); const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({headers}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({headers})) as any), new MockBrowserXHR()); connection.response.subscribe(); expect(setRequestHeaderSpy) .toHaveBeenCalledWith('Accept', 'application/json, text/plain, */*'); @@ -250,7 +252,7 @@ export function main() { const headers = new Headers({'Accept': 'text/xml'}); const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({headers}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({headers})) as any), new MockBrowserXHR()); connection.response.subscribe(); expect(setRequestHeaderSpy).toHaveBeenCalledWith('Accept', 'text/xml'); }); @@ -260,7 +262,7 @@ export function main() { const body = {test: 'val'}; const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({body: body, headers: headers}))), + new Request(base.merge(new RequestOptions({body: body, headers: headers})) as any), new MockBrowserXHR()); connection.response.subscribe(); expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/plain'); @@ -272,7 +274,7 @@ export function main() { const body = {test: 'val'}; const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({body: body})) as any), new MockBrowserXHR()); connection.response.subscribe(); expect(sendSpy).toHaveBeenCalledWith(JSON.stringify(body, null, 2)); expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'application/json'); @@ -282,7 +284,7 @@ export function main() { const body = 23; const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({body: body})) as any), new MockBrowserXHR()); connection.response.subscribe(); expect(sendSpy).toHaveBeenCalledWith('23'); expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/plain'); @@ -292,7 +294,7 @@ export function main() { const body = 'some string'; const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({body: body})) as any), new MockBrowserXHR()); connection.response.subscribe(); expect(sendSpy).toHaveBeenCalledWith(body); expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/plain'); @@ -304,7 +306,7 @@ export function main() { body.set('test2', 'val2'); const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({body: body})) as any), new MockBrowserXHR()); connection.response.subscribe(); expect(sendSpy).toHaveBeenCalledWith('test1=val1&test2=val2'); expect(setRequestHeaderSpy) @@ -337,7 +339,8 @@ export function main() { body.append('userfile', blob); const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({body: body})) as any), + new MockBrowserXHR()); connection.response.subscribe(); expect(sendSpy).toHaveBeenCalledWith(body); expect(setRequestHeaderSpy).not.toHaveBeenCalledWith(); @@ -347,14 +350,15 @@ export function main() { const body = createBlob(['body { color: red; }'], 'text/css'); const base = new BaseRequestOptions(); const connection = new XHRConnection( - new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR()); + new Request(base.merge(new RequestOptions({body: body})) as any), + new MockBrowserXHR()); connection.response.subscribe(); expect(sendSpy).toHaveBeenCalledWith(body); expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/css'); }); it('should use blob body without type to the request', () => { - const body = createBlob(['body { color: red; }'], null); + const body = createBlob(['body { color: red; }'], null !); const base = new BaseRequestOptions(); const connection = new XHRConnection( new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR()); @@ -366,7 +370,7 @@ export function main() { it('should use blob body without type with custom content type header to the request', () => { const headers = new Headers({'Content-Type': 'text/css'}); - const body = createBlob(['body { color: red; }'], null); + const body = createBlob(['body { color: red; }'], null !); const base = new BaseRequestOptions(); const connection = new XHRConnection( new Request(base.merge(new RequestOptions({body: body, headers: headers}))), @@ -590,7 +594,7 @@ export function main() { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { const conn = new XHRConnection(sampleRequest, new MockBrowserXHR(), new ResponseOptions()); - conn.response.subscribe(null, (res: Response) => { + conn.response.subscribe(null !, (res: Response) => { expect(res.text()).toBe('{json: "object"}'); async.done(); }); @@ -611,10 +615,10 @@ Transfer-Encoding: chunked Connection: keep-alive`; connection.response.subscribe((res: Response) => { - expect(res.headers.get('Date')).toEqual('Fri, 20 Nov 2015 01:45:26 GMT'); - expect(res.headers.get('Content-Type')).toEqual('application/json; charset=utf-8'); - expect(res.headers.get('Transfer-Encoding')).toEqual('chunked'); - expect(res.headers.get('Connection')).toEqual('keep-alive'); + expect(res.headers !.get('Date')).toEqual('Fri, 20 Nov 2015 01:45:26 GMT'); + expect(res.headers !.get('Content-Type')).toEqual('application/json; charset=utf-8'); + expect(res.headers !.get('Transfer-Encoding')).toEqual('chunked'); + expect(res.headers !.get('Connection')).toEqual('keep-alive'); async.done(); }); diff --git a/packages/http/test/http_spec.ts b/packages/http/test/http_spec.ts index 544d58745c..3f0fb7b5c0 100644 --- a/packages/http/test/http_spec.ts +++ b/packages/http/test/http_spec.ts @@ -175,7 +175,7 @@ export function main() { backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse)); http.request('http://basic.connection') .subscribe( - (res: Response) => { expect(res.text()).toBe('base response'); }, null, + (res: Response) => { expect(res.text()).toBe('base response'); }, null !, () => { async.done(); }); })); @@ -188,7 +188,7 @@ export function main() { }); http.request('http://basic.connection') .subscribe( - (res: Response) => { expect(res.text()).toBe('base response'); }, null, + (res: Response) => { expect(res.text()).toBe('base response'); }, null !, () => { async.done(); }); })); diff --git a/packages/http/test/static_request_spec.ts b/packages/http/test/static_request_spec.ts index 41c9d2b586..bdf05d213a 100644 --- a/packages/http/test/static_request_spec.ts +++ b/packages/http/test/static_request_spec.ts @@ -17,7 +17,8 @@ export function main() { describe('Request', () => { describe('detectContentType', () => { it('should return ContentType.NONE', () => { - const req = new Request(new RequestOptions({url: 'test', method: 'GET', body: null})); + const req = + new Request(new RequestOptions({url: 'test', method: 'GET', body: null}) as any); expect(req.detectContentType()).toEqual(ContentType.NONE); }); @@ -28,7 +29,7 @@ export function main() { method: 'GET', body: null, headers: new Headers({'content-type': 'application/json'}) - })); + }) as any); expect(req.detectContentType()).toEqual(ContentType.JSON); }); @@ -39,7 +40,7 @@ export function main() { method: 'GET', body: null, headers: new Headers({'content-type': 'application/x-www-form-urlencoded'}) - })); + }) as any); expect(req.detectContentType()).toEqual(ContentType.FORM); }); @@ -50,7 +51,7 @@ export function main() { method: 'GET', body: null, headers: new Headers({'content-type': 'multipart/form-data'}) - })); + }) as any); expect(req.detectContentType()).toEqual(ContentType.FORM_DATA); }); @@ -61,7 +62,7 @@ export function main() { method: 'GET', body: null, headers: new Headers({'content-type': 'text/plain'}) - })); + }) as any); expect(req.detectContentType()).toEqual(ContentType.TEXT); }); @@ -72,7 +73,7 @@ export function main() { method: 'GET', body: null, headers: new Headers({'content-type': 'application/octet-stream'}) - })); + }) as any); expect(req.detectContentType()).toEqual(ContentType.BLOB); }); @@ -83,7 +84,7 @@ export function main() { method: 'GET', body: new ArrayBuffer(1), headers: new Headers({'content-type': 'application/octet-stream'}) - })); + }) as any); expect(req.detectContentType()).toEqual(ContentType.ARRAY_BUFFER); }); @@ -95,7 +96,7 @@ export function main() { method: 'GET', body: null, headers: new Headers({'content-type': 'application/json'}) - })); + }) as any); expect(req.text()).toEqual(''); }); @@ -104,7 +105,7 @@ export function main() { const reqOptions = new RequestOptions( {url: 'test', method: 'GET', headers: new Headers({'content-type': 'application/json'})}); delete reqOptions.body; - const req = new Request(reqOptions); + const req = new Request(reqOptions as any); expect(req.text()).toEqual(''); }); diff --git a/packages/http/test/url_search_params_spec.ts b/packages/http/test/url_search_params_spec.ts index 1cb725f147..e95620ed46 100644 --- a/packages/http/test/url_search_params_spec.ts +++ b/packages/http/test/url_search_params_spec.ts @@ -151,19 +151,19 @@ export function main() { it('should remove the parameter when set to undefined or null', () => { const params = new URLSearchParams('q=Q'); - params.set('q', undefined); + params.set('q', undefined !); expect(params.has('q')).toBe(false); expect(params.toString()).toEqual(''); - params.set('q', null); + params.set('q', null !); expect(params.has('q')).toBe(false); expect(params.toString()).toEqual(''); }); it('should ignore the value when append undefined or null', () => { const params = new URLSearchParams('q=Q'); - params.append('q', undefined); + params.append('q', undefined !); expect(params.toString()).toEqual('q=Q'); - params.append('q', null); + params.append('q', null !); expect(params.toString()).toEqual('q=Q'); }); diff --git a/tools/public_api_guard/http/http.d.ts b/tools/public_api_guard/http/http.d.ts index 2b0196aa37..e1bd37fd8f 100644 --- a/tools/public_api_guard/http/http.d.ts +++ b/tools/public_api_guard/http/http.d.ts @@ -36,13 +36,13 @@ export declare class CookieXSRFStrategy implements XSRFStrategy { export declare class Headers { constructor(headers?: Headers | { [name: string]: any; - }); + } | null); append(name: string, value: string): void; delete(name: string): void; entries(): void; - forEach(fn: (values: string[], name: string, headers: Map) => void): void; - get(name: string): string; - getAll(name: string): string[]; + forEach(fn: (values: string[], name: string | undefined, headers: Map) => void): void; + get(name: string): string | null; + getAll(name: string): string[] | null; has(name: string): boolean; keys(): string[]; set(name: string, value: string | string[]): void; @@ -137,13 +137,13 @@ export declare enum RequestMethod { /** @experimental */ export declare class RequestOptions { body: any; - headers: Headers; - method: RequestMethod | string; + headers: Headers | null; + method: RequestMethod | string | null; params: URLSearchParams; - responseType: ResponseContentType; + responseType: ResponseContentType | null; /** @deprecated */ search: URLSearchParams; - url: string; - withCredentials: boolean; + url: string | null; + withCredentials: boolean | null; constructor({method, headers, body, url, search, params, withCredentials, responseType}?: RequestOptionsArgs); merge(options?: RequestOptionsArgs): RequestOptions; } @@ -151,26 +151,26 @@ export declare class RequestOptions { /** @experimental */ export interface RequestOptionsArgs { body?: any; - headers?: Headers; - method?: string | RequestMethod; + headers?: Headers | null; + method?: string | RequestMethod | null; params?: string | URLSearchParams | { [key: string]: any | any[]; - }; - responseType?: ResponseContentType; + } | null; + responseType?: ResponseContentType | null; /** @deprecated */ search?: string | URLSearchParams | { [key: string]: any | any[]; - }; - url?: string; - withCredentials?: boolean; + } | null; + url?: string | null; + withCredentials?: boolean | null; } /** @experimental */ export declare class Response extends Body { bytesLoaded: number; - headers: Headers; + headers: Headers | null; ok: boolean; status: number; - statusText: string; + statusText: string | null; totalBytes: number; type: ResponseType; url: string; @@ -188,22 +188,22 @@ export declare enum ResponseContentType { /** @experimental */ export declare class ResponseOptions { - body: string | Object | ArrayBuffer | Blob; - headers: Headers; - status: number; - url: string; + body: string | Object | ArrayBuffer | Blob | null; + headers: Headers | null; + status: number | null; + url: string | null; constructor({body, status, headers, statusText, type, url}?: ResponseOptionsArgs); merge(options?: ResponseOptionsArgs): ResponseOptions; } /** @experimental */ export interface ResponseOptionsArgs { - body?: string | Object | FormData | ArrayBuffer | Blob; - headers?: Headers; - status?: number; - statusText?: string; - type?: ResponseType; - url?: string; + body?: string | Object | FormData | ArrayBuffer | Blob | null; + headers?: Headers | null; + status?: number | null; + statusText?: string | null; + type?: ResponseType | null; + url?: string | null; } /** @experimental */ @@ -224,7 +224,7 @@ export declare class URLSearchParams { appendAll(searchParams: URLSearchParams): void; clone(): URLSearchParams; delete(param: string): void; - get(param: string): string; + get(param: string): string | null; getAll(param: string): string[]; has(param: string): boolean; replaceAll(searchParams: URLSearchParams): void; From 56c46d70f7b3cb308d658224682d739d4f9b5a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mis=CC=8Cko=20Hevery?= Date: Fri, 24 Mar 2017 09:55:52 -0700 Subject: [PATCH 0032/1039] fix(router): Update types for TypeScript nullability support --- packages/router/src/apply_redirects.ts | 24 ++-- packages/router/src/create_url_tree.ts | 2 +- .../router/src/directives/router_outlet.ts | 16 +-- packages/router/src/recognize.ts | 20 +-- packages/router/src/route_reuse_strategy.ts | 4 +- packages/router/src/router.ts | 61 ++++----- packages/router/src/router_config_loader.ts | 2 +- packages/router/src/router_module.ts | 6 +- packages/router/src/router_outlet_map.ts | 2 +- packages/router/src/router_state.ts | 26 ++-- packages/router/src/shared.ts | 2 +- packages/router/src/url_tree.ts | 12 +- packages/router/src/utils/collection.ts | 2 +- packages/router/src/utils/tree.ts | 6 +- packages/router/test/apply_redirects.spec.ts | 14 +- packages/router/test/bootstrap.spec.ts | 4 +- packages/router/test/config.spec.ts | 7 +- .../router/test/create_router_state.spec.ts | 8 +- packages/router/test/create_url_tree.spec.ts | 12 +- packages/router/test/integration.spec.ts | 42 +++--- packages/router/test/recognize.spec.ts | 122 +++++++++--------- packages/router/test/router.spec.ts | 12 +- packages/router/test/router_state.spec.ts | 18 +-- .../testing/src/router_testing_module.ts | 2 +- packages/router/tsconfig-build.json | 1 + tools/public_api_guard/router/router.d.ts | 26 ++-- 26 files changed, 230 insertions(+), 223 deletions(-) diff --git a/packages/router/src/apply_redirects.ts b/packages/router/src/apply_redirects.ts index 0805761d1b..8af1df37c9 100644 --- a/packages/router/src/apply_redirects.ts +++ b/packages/router/src/apply_redirects.ts @@ -25,7 +25,9 @@ import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree'; import {andObservables, forEach, waitForMap, wrapIntoObservable} from './utils/collection'; class NoMatch { - constructor(public segmentGroup: UrlSegmentGroup = null) {} + public segmentGroup: UrlSegmentGroup|null; + + constructor(segmentGroup?: UrlSegmentGroup) { this.segmentGroup = segmentGroup || null; } } class AbsoluteRedirect { @@ -80,7 +82,7 @@ class ApplyRedirects { this.expandSegmentGroup(this.ngModule, this.config, this.urlTree.root, PRIMARY_OUTLET); const urlTrees$ = map.call( expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree( - rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment)); + rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment !)); return _catch.call(urlTrees$, (e: any) => { if (e instanceof AbsoluteRedirect) { // after an absolute redirect we do not apply any more redirects! @@ -102,7 +104,7 @@ class ApplyRedirects { this.expandSegmentGroup(this.ngModule, this.config, tree.root, PRIMARY_OUTLET); const mapped$ = map.call( expanded$, (rootSegmentGroup: UrlSegmentGroup) => - this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment)); + this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment !)); return _catch.call(mapped$, (e: any): Observable => { if (e instanceof NoMatch) { throw this.noMatchError(e); @@ -215,8 +217,8 @@ class ApplyRedirects { private expandWildCardWithParamsAgainstRouteUsingRedirect( ngModule: NgModuleRef, routes: Route[], route: Route, outlet: string): Observable { - const newTree = this.applyRedirectCommands([], route.redirectTo, {}); - if (route.redirectTo.startsWith('/')) { + const newTree = this.applyRedirectCommands([], route.redirectTo !, {}); + if (route.redirectTo !.startsWith('/')) { return absoluteRedirect(newTree); } @@ -234,8 +236,8 @@ class ApplyRedirects { if (!matched) return noMatch(segmentGroup); const newTree = this.applyRedirectCommands( - consumedSegments, route.redirectTo, positionalParamSegments); - if (route.redirectTo.startsWith('/')) { + consumedSegments, route.redirectTo !, positionalParamSegments); + if (route.redirectTo !.startsWith('/')) { return absoluteRedirect(newTree); } @@ -331,7 +333,7 @@ class ApplyRedirects { } if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) { - return namedOutletsRedirect(route.redirectTo); + return namedOutletsRedirect(route.redirectTo !); } c = c.children[PRIMARY_OUTLET]; @@ -442,9 +444,9 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment return { matched: true, - consumedSegments: res.consumed, - lastChild: res.consumed.length, - positionalParamSegments: res.posParams, + consumedSegments: res.consumed !, + lastChild: res.consumed.length !, + positionalParamSegments: res.posParams !, }; } diff --git a/packages/router/src/create_url_tree.ts b/packages/router/src/create_url_tree.ts index ab0d3e6bb5..c454af5099 100644 --- a/packages/router/src/create_url_tree.ts +++ b/packages/router/src/create_url_tree.ts @@ -164,7 +164,7 @@ function createPositionApplyingDoubleDots( let dd = numberOfDoubleDots; while (dd > ci) { dd -= ci; - g = g.parent; + g = g.parent !; if (!g) { throw new Error('Invalid number of \'../\''); } diff --git a/packages/router/src/directives/router_outlet.ts b/packages/router/src/directives/router_outlet.ts index 435590b2e9..a6938e55e3 100644 --- a/packages/router/src/directives/router_outlet.ts +++ b/packages/router/src/directives/router_outlet.ts @@ -71,8 +71,8 @@ export class RouterOutlet implements OnDestroy { if (!this.activated) throw new Error('Outlet is not activated'); this.location.detach(); const r = this.activated; - this.activated = null; - this._activatedRoute = null; + this.activated = null !; + this._activatedRoute = null !; return r; } @@ -86,8 +86,8 @@ export class RouterOutlet implements OnDestroy { if (this.activated) { const c = this.component; this.activated.destroy(); - this.activated = null; - this._activatedRoute = null; + this.activated = null !; + this._activatedRoute = null !; this.deactivateEvents.emit(c); } } @@ -104,8 +104,8 @@ export class RouterOutlet implements OnDestroy { this._activatedRoute = activatedRoute; const snapshot = activatedRoute._futureSnapshot; - const component: any = snapshot._routeConfig.component; - const factory = resolver.resolveComponentFactory(component); + const component: any = snapshot._routeConfig !.component; + const factory = resolver.resolveComponentFactory(component) !; const inj = ReflectiveInjector.fromResolvedProviders(providers, injector); @@ -126,10 +126,10 @@ export class RouterOutlet implements OnDestroy { this._activatedRoute = activatedRoute; const snapshot = activatedRoute._futureSnapshot; - const component = snapshot._routeConfig.component; + const component = snapshot._routeConfig !.component; resolver = resolver || this.resolver; - const factory = resolver.resolveComponentFactory(component); + const factory = resolver.resolveComponentFactory(component) !; const injector = new OutletInjector(activatedRoute, outletMap, this.location.injector); diff --git a/packages/router/src/recognize.ts b/packages/router/src/recognize.ts index 684e2c6a1b..09ed8453b1 100644 --- a/packages/router/src/recognize.ts +++ b/packages/router/src/recognize.ts @@ -21,14 +21,14 @@ import {TreeNode} from './utils/tree'; class NoMatch {} export function recognize( - rootComponentType: Type, config: Routes, urlTree: UrlTree, + rootComponentType: Type| null, config: Routes, urlTree: UrlTree, url: string): Observable { return new Recognizer(rootComponentType, config, urlTree, url).recognize(); } class Recognizer { constructor( - private rootComponentType: Type, private config: Routes, private urlTree: UrlTree, + private rootComponentType: Type|null, private config: Routes, private urlTree: UrlTree, private url: string) {} recognize(): Observable { @@ -38,8 +38,8 @@ class Recognizer { const children = this.processSegmentGroup(this.config, rootSegmentGroup, PRIMARY_OUTLET); const root = new ActivatedRouteSnapshot( - [], Object.freeze({}), Object.freeze(this.urlTree.queryParams), this.urlTree.fragment, {}, - PRIMARY_OUTLET, this.rootComponentType, null, this.urlTree.root, -1, {}); + [], Object.freeze({}), Object.freeze(this.urlTree.queryParams), this.urlTree.fragment !, + {}, PRIMARY_OUTLET, this.rootComponentType, null, this.urlTree.root, -1, {}); const rootNode = new TreeNode(root, children); const routeState = new RouterStateSnapshot(this.url, rootNode); @@ -110,10 +110,10 @@ class Recognizer { if ((route.outlet || PRIMARY_OUTLET) !== outlet) throw new NoMatch(); if (route.path === '**') { - const params = segments.length > 0 ? last(segments).parameters : {}; + const params = segments.length > 0 ? last(segments) !.parameters : {}; const snapshot = new ActivatedRouteSnapshot( - segments, params, Object.freeze(this.urlTree.queryParams), this.urlTree.fragment, - getData(route), outlet, route.component, route, getSourceSegmentGroup(rawSegment), + 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(snapshot, [])]; } @@ -127,7 +127,7 @@ class Recognizer { const snapshot = new ActivatedRouteSnapshot( consumedSegments, parameters, Object.freeze(this.urlTree.queryParams), - this.urlTree.fragment, getData(route), outlet, route.component, route, + this.urlTree.fragment !, getData(route), outlet, route.component !, route, getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + consumedSegments.length, getResolve(route)); @@ -160,7 +160,7 @@ function getChildConfig(route: Route): Route[] { } if (route.loadChildren) { - return route._loadedConfig.routes; + return route._loadedConfig !.routes; } return []; @@ -180,7 +180,7 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment if (!res) throw new NoMatch(); const posParams: {[n: string]: string} = {}; - forEach(res.posParams, (v: UrlSegment, k: string) => { posParams[k] = v.path; }); + forEach(res.posParams !, (v: UrlSegment, k: string) => { posParams[k] = v.path; }); const parameters = {...posParams, ...res.consumed[res.consumed.length - 1].parameters}; return {consumedSegments: res.consumed, lastChild: res.consumed.length, parameters}; diff --git a/packages/router/src/route_reuse_strategy.ts b/packages/router/src/route_reuse_strategy.ts index f733b06dc6..3f96768bef 100644 --- a/packages/router/src/route_reuse_strategy.ts +++ b/packages/router/src/route_reuse_strategy.ts @@ -37,13 +37,13 @@ export abstract class RouteReuseStrategy { abstract shouldDetach(route: ActivatedRouteSnapshot): boolean; /** Stores the detached route */ - abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void; + abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle|null): void; /** Determines if this route (and its subtree) should be reattached */ abstract shouldAttach(route: ActivatedRouteSnapshot): boolean; /** Retrieves the previously stored route */ - abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle; + abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle|null; /** Determines if a route should be reused */ abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean; diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index 92a815e854..4414600ab6 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -189,7 +189,7 @@ export type RouterHook = (snapshot: RouterStateSnapshot) => Observable; * @internal */ function defaultRouterHook(snapshot: RouterStateSnapshot): Observable { - return of (null); + return of (null) as any; } /** @@ -199,7 +199,7 @@ export class DefaultRouteReuseStrategy implements RouteReuseStrategy { shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; } store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {} shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; } - retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { return null; } + retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle|null { return null; } shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { return future.routeConfig === curr.routeConfig; } @@ -218,7 +218,7 @@ export class Router { private currentUrlTree: UrlTree; private rawUrlTree: UrlTree; - private navigations = new BehaviorSubject(null); + private navigations = new BehaviorSubject(null !); private routerEvents = new Subject(); private currentRouterState: RouterState; @@ -263,7 +263,7 @@ export class Router { */ // TODO: vsavkin make internal after the final is out. constructor( - private rootComponentType: Type, private urlSerializer: UrlSerializer, + private rootComponentType: Type|null, private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap, private location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, public config: Routes) { const onLoadStart = (r: Route) => this.triggerEvent(new RouteConfigLoadStart(r)); @@ -354,7 +354,7 @@ export class Router { dispose(): void { if (this.locationSubscription) { this.locationSubscription.unsubscribe(); - this.locationSubscription = null; + this.locationSubscription = null !; } } @@ -407,7 +407,7 @@ export class Router { } const a = relativeTo || this.routerState.root; const f = preserveFragment ? this.currentUrlTree.fragment : fragment; - let q: Params = null; + let q: Params|null = null; if (queryParamsHandling) { switch (queryParamsHandling) { case 'merge': @@ -417,12 +417,12 @@ export class Router { q = this.currentUrlTree.queryParams; break; default: - q = queryParams; + q = queryParams || null; } } else { - q = preserveQueryParams ? this.currentUrlTree.queryParams : queryParams; + q = preserveQueryParams ? this.currentUrlTree.queryParams : queryParams || null; } - return createUrlTree(a, this.currentUrlTree, commands, q, f); + return createUrlTree(a, this.currentUrlTree, commands, q !, f !); } /** @@ -535,7 +535,7 @@ export class Router { // we should skip those. if (lastNavigation && source !== 'imperative' && lastNavigation.source === 'imperative' && lastNavigation.rawUrl.toString() === rawUrl.toString()) { - return null; // return value is not used + return Promise.resolve(true); // return value is not used } // Because of a bug in IE and Edge, the location class fires two events (popstate and @@ -543,7 +543,7 @@ export class Router { // flicker. if (lastNavigation && source == 'hashchange' && lastNavigation.source === 'popstate' && lastNavigation.rawUrl.toString() === rawUrl.toString()) { - return null; // return value is not used + return Promise.resolve(true); // return value is not used } let resolve: any = null; @@ -572,7 +572,7 @@ export class Router { Promise.resolve() .then( (_) => this.runNavigate( - url, rawUrl, extras.skipLocationChange, extras.replaceUrl, id, null)) + url, rawUrl, !!extras.skipLocationChange, !!extras.replaceUrl, id, null)) .then(resolve, reject); // we cannot process the current URL, but we could process the previous one => @@ -596,7 +596,7 @@ export class Router { private runNavigate( url: UrlTree, rawUrl: UrlTree, shouldPreventPushState: boolean, shouldReplaceUrl: boolean, - id: number, precreatedState: RouterStateSnapshot): Promise { + id: number, precreatedState: RouterStateSnapshot|null): Promise { if (id !== this.navigationId) { this.location.go(this.urlSerializer.serialize(this.currentUrlTree)); this.routerEvents.next(new NavigationCancel( @@ -768,7 +768,7 @@ class CanActivate { } class CanDeactivate { - constructor(public component: Object, public route: ActivatedRouteSnapshot) {} + constructor(public component: Object|null, public route: ActivatedRouteSnapshot) {} } @@ -804,8 +804,8 @@ export class PreActivation { } private traverseChildRoutes( - futureNode: TreeNode, currNode: TreeNode, - outletMap: RouterOutletMap, futurePath: ActivatedRouteSnapshot[]): void { + futureNode: TreeNode, currNode: TreeNode|null, + outletMap: RouterOutletMap|null, futurePath: ActivatedRouteSnapshot[]): void { const prevChildren = nodeChildrenAsMap(currNode); futureNode.children.forEach(c => { @@ -814,12 +814,12 @@ export class PreActivation { }); forEach( prevChildren, (v: TreeNode, k: string) => - this.deactiveRouteAndItsChildren(v, outletMap._outlets[k])); + this.deactiveRouteAndItsChildren(v, outletMap !._outlets[k])); } private traverseRoutes( futureNode: TreeNode, currNode: TreeNode, - parentOutletMap: RouterOutletMap, futurePath: ActivatedRouteSnapshot[]): void { + parentOutletMap: RouterOutletMap|null, futurePath: ActivatedRouteSnapshot[]): void { const future = futureNode.value; const curr = currNode ? currNode.value : null; const outlet = parentOutletMap ? parentOutletMap._outlets[futureNode.value.outlet] : null; @@ -827,9 +827,9 @@ export class PreActivation { // reusing the node if (curr && future._routeConfig === curr._routeConfig) { if (this.shouldRunGuardsAndResolvers( - curr, future, future._routeConfig.runGuardsAndResolvers)) { + curr, future, future._routeConfig !.runGuardsAndResolvers)) { this.canActivateChecks.push(new CanActivate(futurePath)); - this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr)); + this.canDeactivateChecks.push(new CanDeactivate(outlet !.component, curr)); } else { // we need to set the data future.data = curr.data; @@ -864,7 +864,7 @@ export class PreActivation { private shouldRunGuardsAndResolvers( curr: ActivatedRouteSnapshot, future: ActivatedRouteSnapshot, - mode: RunGuardsAndResolvers): boolean { + mode: RunGuardsAndResolvers|undefined): boolean { switch (mode) { case 'always': return true; @@ -880,7 +880,7 @@ export class PreActivation { } private deactiveRouteAndItsChildren( - route: TreeNode, outlet: RouterOutlet): void { + route: TreeNode, outlet: RouterOutlet|null): void { const prevChildren = nodeChildrenAsMap(route); const r = route.value; @@ -958,13 +958,14 @@ export class PreActivation { } private extractCanActivateChild(p: ActivatedRouteSnapshot): - {node: ActivatedRouteSnapshot, guards: any[]} { + {node: ActivatedRouteSnapshot, guards: any[]}|null { const canActivateChild = p._routeConfig ? p._routeConfig.canActivateChild : null; if (!canActivateChild || canActivateChild.length === 0) return null; return {node: p, guards: canActivateChild}; } - private runCanDeactivate(component: Object, curr: ActivatedRouteSnapshot): Observable { + private runCanDeactivate(component: Object|null, curr: ActivatedRouteSnapshot): + Observable { const canDeactivate = curr && curr._routeConfig ? curr._routeConfig.canDeactivate : null; if (!canDeactivate || canDeactivate.length === 0) return of (true); const canDeactivate$ = mergeMap.call(from(canDeactivate), (c: any) => { @@ -1020,7 +1021,7 @@ class ActivateRoutes { } private deactivateChildRoutes( - futureNode: TreeNode, currNode: TreeNode, + futureNode: TreeNode, currNode: TreeNode|null, outletMap: RouterOutletMap): void { const prevChildren: {[key: string]: any} = nodeChildrenAsMap(currNode); futureNode.children.forEach(c => { @@ -1031,7 +1032,7 @@ class ActivateRoutes { } private activateChildRoutes( - futureNode: TreeNode, currNode: TreeNode, + futureNode: TreeNode, currNode: TreeNode|null, outletMap: RouterOutletMap): void { const prevChildren: {[key: string]: any} = nodeChildrenAsMap(currNode); futureNode.children.forEach( @@ -1136,7 +1137,7 @@ class ActivateRoutes { private deactiveRouteAndOutlet(route: TreeNode, parentOutletMap: RouterOutletMap): void { const prevChildren: {[key: string]: any} = nodeChildrenAsMap(route); - let outlet: RouterOutlet = null; + let outlet: RouterOutlet|null = null; // getOutlet throws when cannot find the right outlet, // which can happen if an outlet was in an NgIf and was removed @@ -1166,7 +1167,7 @@ function advanceActivatedRouteNodeAndItsChildren(node: TreeNode) node.children.forEach(advanceActivatedRouteNodeAndItsChildren); } -function parentLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig { +function parentLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig|null { for (let s = snapshot.parent; s; s = s.parent) { const route = s._routeConfig; if (route && route._loadedConfig) return route._loadedConfig; @@ -1176,7 +1177,7 @@ function parentLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfi return null; } -function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig { +function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConfig|null { if (!snapshot) return null; for (let s = snapshot.parent; s; s = s.parent) { @@ -1187,7 +1188,7 @@ function closestLoadedConfig(snapshot: ActivatedRouteSnapshot): LoadedRouterConf return null; } -function nodeChildrenAsMap(node: TreeNode) { +function nodeChildrenAsMap(node: TreeNode| null) { const map: {[key: string]: TreeNode} = {}; if (node) { diff --git a/packages/router/src/router_config_loader.ts b/packages/router/src/router_config_loader.ts index b91bd28b5c..08d28c777a 100644 --- a/packages/router/src/router_config_loader.ts +++ b/packages/router/src/router_config_loader.ts @@ -32,7 +32,7 @@ export class RouterConfigLoader { this.onLoadStartListener(route); } - const moduleFactory$ = this.loadModuleFactory(route.loadChildren); + const moduleFactory$ = this.loadModuleFactory(route.loadChildren !); return map.call(moduleFactory$, (factory: NgModuleFactory) => { if (this.onLoadEndListener) { diff --git a/packages/router/src/router_module.ts b/packages/router/src/router_module.ts index 4451ac013f..a7c1c076ec 100644 --- a/packages/router/src/router_module.ts +++ b/packages/router/src/router_module.ts @@ -327,7 +327,7 @@ export class RouterInitializer { appInitializer(): Promise { const p: Promise = this.injector.get(LOCATION_INITIALIZED, Promise.resolve(null)); return p.then(() => { - let resolve: Function = null; + let resolve: Function = null !; const res = new Promise(r => resolve = r); const router = this.injector.get(Router); const opts = this.injector.get(ROUTER_CONFIGURATION); @@ -349,7 +349,7 @@ export class RouterInitializer { // subsequent navigations should not be delayed } else { - return of (null); + return of (null) as any; } }; router.initialNavigation(); @@ -380,7 +380,7 @@ export class RouterInitializer { preloader.setUpPreloading(); router.resetRootComponentType(ref.componentTypes[0]); - this.resultOfPreactivationDone.next(null); + this.resultOfPreactivationDone.next(null !); this.resultOfPreactivationDone.complete(); } diff --git a/packages/router/src/router_outlet_map.ts b/packages/router/src/router_outlet_map.ts index fb8e21c854..3fb1ad7a12 100644 --- a/packages/router/src/router_outlet_map.ts +++ b/packages/router/src/router_outlet_map.ts @@ -25,5 +25,5 @@ export class RouterOutletMap { /** * Removes an outlet from this map. */ - removeOutlet(name: string): void { this._outlets[name] = undefined; } + removeOutlet(name: string): void { this._outlets[name] = undefined !; } } diff --git a/packages/router/src/router_state.ts b/packages/router/src/router_state.ts index d37a321111..7b2f33be80 100644 --- a/packages/router/src/router_state.ts +++ b/packages/router/src/router_state.ts @@ -57,7 +57,7 @@ export class RouterState extends Tree { toString(): string { return this.snapshot.toString(); } } -export function createEmptyState(urlTree: UrlTree, rootComponent: Type): RouterState { +export function createEmptyState(urlTree: UrlTree, rootComponent: Type| null): RouterState { const snapshot = createEmptyStateSnapshot(urlTree, rootComponent); const emptyUrl = new BehaviorSubject([new UrlSegment('', {})]); const emptyParams = new BehaviorSubject({}); @@ -72,7 +72,7 @@ export function createEmptyState(urlTree: UrlTree, rootComponent: Type): Ro } export function createEmptyStateSnapshot( - urlTree: UrlTree, rootComponent: Type): RouterStateSnapshot { + urlTree: UrlTree, rootComponent: Type| null): RouterStateSnapshot { const emptyParams = {}; const emptyData = {}; const emptyQueryParams = {}; @@ -132,21 +132,21 @@ export class ActivatedRoute { public outlet: string, /** The component of the route. It's a constant */ // TODO(vsavkin): remove |string - public component: Type|string, futureSnapshot: ActivatedRouteSnapshot) { + public component: Type|string|null, futureSnapshot: ActivatedRouteSnapshot) { this._futureSnapshot = futureSnapshot; } /** The configuration used to match this route */ - get routeConfig(): Route { return this._futureSnapshot.routeConfig; } + get routeConfig(): Route|null { return this._futureSnapshot.routeConfig; } /** The root of the router state */ get root(): ActivatedRoute { return this._routerState.root; } /** The parent of this route in the router state tree */ - get parent(): ActivatedRoute { return this._routerState.parent(this); } + get parent(): ActivatedRoute|null { return this._routerState.parent(this); } /** The first child of this route in the router state tree */ - get firstChild(): ActivatedRoute { return this._routerState.firstChild(this); } + get firstChild(): ActivatedRoute|null { return this._routerState.firstChild(this); } /** The children of this route in the router state tree */ get children(): ActivatedRoute[] { return this._routerState.children(this); } @@ -234,7 +234,7 @@ export function inheritedParamsDataResolve(route: ActivatedRouteSnapshot): Inher */ export class ActivatedRouteSnapshot { /** @internal **/ - _routeConfig: Route; + _routeConfig: Route|null; /** @internal **/ _urlSegment: UrlSegmentGroup; /** @internal */ @@ -265,7 +265,7 @@ export class ActivatedRouteSnapshot { /** The outlet name of the route */ public outlet: string, /** The component of the route */ - public component: Type|string, routeConfig: Route, urlSegment: UrlSegmentGroup, + public component: Type|string|null, routeConfig: Route|null, urlSegment: UrlSegmentGroup, lastPathIndex: number, resolve: ResolveData) { this._routeConfig = routeConfig; this._urlSegment = urlSegment; @@ -274,16 +274,16 @@ export class ActivatedRouteSnapshot { } /** The configuration used to match this route */ - get routeConfig(): Route { return this._routeConfig; } + get routeConfig(): Route|null { return this._routeConfig; } /** The root of the router state */ get root(): ActivatedRouteSnapshot { return this._routerState.root; } /** The parent of this route in the router state tree */ - get parent(): ActivatedRouteSnapshot { return this._routerState.parent(this); } + get parent(): ActivatedRouteSnapshot|null { return this._routerState.parent(this); } /** The first child of this route in the router state tree */ - get firstChild(): ActivatedRouteSnapshot { return this._routerState.firstChild(this); } + get firstChild(): ActivatedRouteSnapshot|null { return this._routerState.firstChild(this); } /** The children of this route in the router state tree */ get children(): ActivatedRouteSnapshot[] { return this._routerState.children(this); } @@ -398,5 +398,5 @@ export function equalParamsAndUrlSegments( const parentsMismatch = !a.parent !== !b.parent; return equalUrlParams && !parentsMismatch && - (!a.parent || equalParamsAndUrlSegments(a.parent, b.parent)); -} + (!a.parent || equalParamsAndUrlSegments(a.parent, b.parent !)); +} \ No newline at end of file diff --git a/packages/router/src/shared.ts b/packages/router/src/shared.ts index d9897c9f6f..56814d5edd 100644 --- a/packages/router/src/shared.ts +++ b/packages/router/src/shared.ts @@ -111,7 +111,7 @@ export function isNavigationCancelingError(error: Error) { // Matches the route configuration (`route`) against the actual URL (`segments`). export function defaultUrlMatcher( segments: UrlSegment[], segmentGroup: UrlSegmentGroup, route: Route): UrlMatchResult|null { - const parts = route.path.split('/'); + const parts = route.path !.split('/'); if (parts.length > segments.length) { // The actual URL is shorter than the config, no match diff --git a/packages/router/src/url_tree.ts b/packages/router/src/url_tree.ts index 600f6bbb30..48a792d892 100644 --- a/packages/router/src/url_tree.ts +++ b/packages/router/src/url_tree.ts @@ -113,7 +113,7 @@ export class UrlTree { /** The query params of the URL */ public queryParams: {[key: string]: string}, /** The fragment of the URL */ - public fragment: string) {} + public fragment: string|null) {} get queryParamMap(): ParamMap { if (!this._queryParamMap) { @@ -139,7 +139,7 @@ export class UrlSegmentGroup { /** @internal */ _segmentIndexShift: number; /** The parent node in the url tree */ - parent: UrlSegmentGroup = null; + parent: UrlSegmentGroup|null = null; constructor( /** The URL segments of this group. See {@link UrlSegment} for more information */ @@ -280,7 +280,7 @@ export class DefaultUrlSerializer implements UrlSerializer { serialize(tree: UrlTree): string { const segment = `/${serializeSegment(tree.root, true)}`; const query = serializeQueryParams(tree.queryParams); - const fragment = typeof tree.fragment === `string` ? `#${encodeURI(tree.fragment)}` : ''; + const fragment = typeof tree.fragment === `string` ? `#${encodeURI(tree.fragment !)}` : ''; return `${segment}${query}${fragment}`; } @@ -397,7 +397,9 @@ class UrlParser { return params; } - parseFragment(): string { return this.consumeOptional('#') ? decodeURI(this.remaining) : null; } + parseFragment(): string|null { + return this.consumeOptional('#') ? decodeURI(this.remaining) : null; + } private parseChildren(): {[outlet: string]: UrlSegmentGroup} { if (this.remaining === '') { @@ -521,7 +523,7 @@ class UrlParser { throw new Error(`Cannot parse url '${this.url}'`); } - let outletName: string; + let outletName: string = undefined !; if (path.indexOf(':') > -1) { outletName = path.substr(0, path.indexOf(':')); this.capture(outletName); diff --git a/packages/router/src/utils/collection.ts b/packages/router/src/utils/collection.ts index fcc3fb58d0..c49a2daf8f 100644 --- a/packages/router/src/utils/collection.ts +++ b/packages/router/src/utils/collection.ts @@ -45,7 +45,7 @@ export function flatten(arr: T[][]): T[] { return Array.prototype.concat.apply([], arr); } -export function last(a: T[]): T { +export function last(a: T[]): T|null { return a.length > 0 ? a[a.length - 1] : null; } diff --git a/packages/router/src/utils/tree.ts b/packages/router/src/utils/tree.ts index 76a511a4ce..a21fee00ed 100644 --- a/packages/router/src/utils/tree.ts +++ b/packages/router/src/utils/tree.ts @@ -17,7 +17,7 @@ export class Tree { /** * @internal */ - parent(t: T): T { + parent(t: T): T|null { const p = this.pathFromRoot(t); return p.length > 1 ? p[p.length - 2] : null; } @@ -33,7 +33,7 @@ export class Tree { /** * @internal */ - firstChild(t: T): T { + firstChild(t: T): T|null { const n = findNode(t, this._root); return n && n.children.length > 0 ? n.children[0].value : null; } @@ -55,7 +55,7 @@ export class Tree { pathFromRoot(t: T): T[] { return findPath(t, this._root, []).map(s => s.value); } } -function findNode(expected: T, c: TreeNode): TreeNode { +function findNode(expected: T, c: TreeNode): TreeNode|null { if (expected === c.value) return c; for (const cc of c.children) { const r = findNode(expected, cc); diff --git a/packages/router/test/apply_redirects.spec.ts b/packages/router/test/apply_redirects.spec.ts index 5e1e044b1e..6583c1c5e0 100644 --- a/packages/router/test/apply_redirects.spec.ts +++ b/packages/router/test/apply_redirects.spec.ts @@ -51,7 +51,7 @@ describe('applyRedirects', () => { }); it('should throw when cannot handle a positional parameter', () => { - applyRedirects(testModule.injector, null, serializer, tree('/a/1'), [ + applyRedirects(testModule.injector, null !, serializer, tree('/a/1'), [ {path: 'a/:id', redirectTo: 'a/:other'} ]).subscribe(() => {}, (e) => { expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.'); @@ -403,7 +403,7 @@ describe('applyRedirects', () => { {path: '', redirectTo: 'a', pathMatch: 'full'} ]; - applyRedirects(testModule.injector, null, serializer, tree('b'), config) + applyRedirects(testModule.injector, null !, serializer, tree('b'), config) .subscribe( (_) => { throw 'Should not be reached'; }, e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'b\''); }); @@ -533,7 +533,7 @@ describe('applyRedirects', () => { ] }]; - applyRedirects(testModule.injector, null, serializer, tree('a/(d//aux:e)'), config) + applyRedirects(testModule.injector, null !, serializer, tree('a/(d//aux:e)'), config) .subscribe( (_) => { throw 'Should not be reached'; }, e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a\''); }); @@ -564,7 +564,7 @@ describe('applyRedirects', () => { it('should error when no children matching and some url is left', () => { applyRedirects( - testModule.injector, null, serializer, tree('/a/c'), + testModule.injector, null !, serializer, tree('/a/c'), [{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}]) .subscribe( (_) => { throw 'Should not be reached'; }, @@ -587,7 +587,7 @@ describe('applyRedirects', () => { matcher: matcher, component: ComponentA, children: [{path: 'b', component: ComponentB}] - }], + }] as any, '/a/1/b', (t: UrlTree) => { compareTrees(t, tree('a/1/b')); }); }); }); @@ -614,7 +614,7 @@ describe('applyRedirects', () => { it('should throw when using non-absolute redirects', () => { applyRedirects( - testModule.injector, null, serializer, tree('a'), + testModule.injector, null !, serializer, tree('a'), [ {path: 'a', redirectTo: 'b(aux:c)'}, ]) @@ -629,7 +629,7 @@ describe('applyRedirects', () => { }); function checkRedirect(config: Routes, url: string, callback: any): void { - applyRedirects(TestBed, null, new DefaultUrlSerializer(), tree(url), config) + applyRedirects(TestBed, null !, new DefaultUrlSerializer(), tree(url), config) .subscribe(callback, e => { throw e; }); } diff --git a/packages/router/test/bootstrap.spec.ts b/packages/router/test/bootstrap.spec.ts index 1c6dabfde9..d1f43c346f 100644 --- a/packages/router/test/bootstrap.spec.ts +++ b/packages/router/test/bootstrap.spec.ts @@ -16,7 +16,7 @@ import {NavigationEnd, Resolve, Router, RouterModule} from '@angular/router'; describe('bootstrap', () => { let log: any[] = []; - let testProviders: any[] = null; + let testProviders: any[] = null !; @Component({selector: 'test-app', template: 'root '}) class RootCmp { @@ -80,7 +80,7 @@ describe('bootstrap', () => { platformBrowserDynamic([]).bootstrapModule(TestModule).then(res => { const router = res.injector.get(Router); - const data = router.routerState.snapshot.root.firstChild.data; + const data = router.routerState.snapshot.root.firstChild !.data; expect(data['test']).toEqual('test-data'); expect(log).toEqual( ['TestModule', 'NavigationStart', 'RoutesRecognized', 'RootCmp', 'NavigationEnd']); diff --git a/packages/router/test/config.spec.ts b/packages/router/test/config.spec.ts index 5897da2539..ad643a8fae 100644 --- a/packages/router/test/config.spec.ts +++ b/packages/router/test/config.spec.ts @@ -24,7 +24,8 @@ describe('config', () => { it('should throw for undefined route', () => { expect(() => { - validateConfig([{path: 'a', component: ComponentA}, , {path: 'b', component: ComponentB}]); + validateConfig( + [{path: 'a', component: ComponentA}, , {path: 'b', component: ComponentB}] as any); }).toThrowError(/Invalid configuration of route ''/); }); @@ -36,7 +37,7 @@ describe('config', () => { {path: 'b', component: ComponentB}, , ] - }]); + }] as any); }).toThrowError(/Invalid configuration of route 'a'/); }); @@ -98,7 +99,7 @@ describe('config', () => { }); it('should throw when path and matcher are missing', () => { - expect(() => { validateConfig([{component: null, redirectTo: 'b'}]); }) + expect(() => { validateConfig([{component: null, redirectTo: 'b'}] as any); }) .toThrowError( `Invalid configuration of route '': routes must have either a path or a matcher specified`); }); diff --git a/packages/router/test/create_router_state.spec.ts b/packages/router/test/create_router_state.spec.ts index b31247b4fc..49ed70f028 100644 --- a/packages/router/test/create_router_state.spec.ts +++ b/packages/router/test/create_router_state.spec.ts @@ -19,7 +19,7 @@ describe('create router state', () => { const reuseStrategy = new DefaultRouteReuseStrategy(); const emptyState = () => - createEmptyState(new UrlTree(new UrlSegmentGroup([], {}), {}, null), RootComponent); + createEmptyState(new UrlTree(new UrlSegmentGroup([], {}), {}, null !), RootComponent); it('should work create new state', () => { const state = createRouterState( @@ -76,8 +76,8 @@ describe('create router state', () => { createRouterState(reuseStrategy, createState(config, 'a/2;p=22/(b//right:c)'), prevState); expect(prevState.root).toBe(state.root); - const prevP = prevState.firstChild(prevState.root); - const currP = state.firstChild(state.root); + const prevP = prevState.firstChild(prevState.root) !; + const currP = state.firstChild(state.root) !; expect(prevP).toBe(currP); const prevC = prevState.children(prevP); @@ -101,7 +101,7 @@ function advanceNode(node: TreeNode): void { } function createState(config: Routes, url: string): RouterStateSnapshot { - let res: RouterStateSnapshot; + let res: RouterStateSnapshot = undefined !; recognize(RootComponent, config, tree(url), url).forEach(s => res = s); return res; } diff --git a/packages/router/test/create_url_tree.spec.ts b/packages/router/test/create_url_tree.spec.ts index bb7a2b3b00..726faff2bd 100644 --- a/packages/router/test/create_url_tree.spec.ts +++ b/packages/router/test/create_url_tree.spec.ts @@ -247,10 +247,10 @@ function createRoot(tree: UrlTree, commands: any[], queryParams?: Params, fragme [], {}, {}, '', {}, PRIMARY_OUTLET, 'someComponent', null, tree.root, -1, null); const a = new ActivatedRoute( - new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null), - new BehaviorSubject(null), new BehaviorSubject(null), PRIMARY_OUTLET, 'someComponent', s); + new BehaviorSubject(null !), new BehaviorSubject(null !), new BehaviorSubject(null !), + new BehaviorSubject(null !), new BehaviorSubject(null !), PRIMARY_OUTLET, 'someComponent', s); advanceActivatedRoute(a); - return createUrlTree(a, tree, commands, queryParams, fragment); + return createUrlTree(a, tree, commands, queryParams !, fragment !); } function create( @@ -263,8 +263,8 @@ function create( [], {}, {}, '', {}, PRIMARY_OUTLET, 'someComponent', null, segment, startIndex, null); const a = new ActivatedRoute( - new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null), - new BehaviorSubject(null), new BehaviorSubject(null), PRIMARY_OUTLET, 'someComponent', s); + new BehaviorSubject(null !), new BehaviorSubject(null !), new BehaviorSubject(null !), + new BehaviorSubject(null !), new BehaviorSubject(null !), PRIMARY_OUTLET, 'someComponent', s); advanceActivatedRoute(a); - return createUrlTree(a, tree, commands, queryParams, fragment); + return createUrlTree(a, tree, commands, queryParams !, fragment !); } \ No newline at end of file diff --git a/packages/router/test/integration.spec.ts b/packages/router/test/integration.spec.ts index 68612befd8..3deefc54e9 100644 --- a/packages/router/test/integration.spec.ts +++ b/packages/router/test/integration.spec.ts @@ -240,7 +240,7 @@ describe('Integration', () => { advance(fixture); let recordedError: any = null; - router.navigateByUrl('/blank').catch(e => recordedError = e); + router.navigateByUrl('/blank') !.catch(e => recordedError = e); advance(fixture); expect(recordedError.message).toEqual('Cannot find primary outlet to load \'BlankCmp\''); @@ -620,8 +620,8 @@ describe('Integration', () => { const user = fixture.debugElement.children[1].componentInstance; let r1: any, r2: any; - router.navigateByUrl('/user/victor').then(_ => r1 = _); - router.navigateByUrl('/user/fedor').then(_ => r2 = _); + router.navigateByUrl('/user/victor') !.then(_ => r1 = _); + router.navigateByUrl('/user/fedor') !.then(_ => r2 = _); advance(fixture); expect(r1).toEqual(false); // returns false because it was canceled @@ -650,7 +650,7 @@ describe('Integration', () => { router.events.forEach(e => recordedEvents.push(e)); let e: any; - router.navigateByUrl('/invalid').catch(_ => e = _); + router.navigateByUrl('/invalid') !.catch(_ => e = _); advance(fixture); expect(e.message).toContain('Cannot match any routes'); @@ -677,7 +677,7 @@ describe('Integration', () => { router.events.forEach(e => recordedEvents.push(e)); let e: any; - router.navigateByUrl('/invalid').then(_ => e = _); + router.navigateByUrl('/invalid') !.then(_ => e = _); advance(fixture); expect(e).toEqual('resolvedValue'); @@ -911,7 +911,7 @@ describe('Integration', () => { router.events.subscribe(e => recordedEvents.push(e)); let e: any = null; - router.navigateByUrl('/simple').catch(error => e = error); + router.navigateByUrl('/simple') !.catch(error => e = error); advance(fixture); expectEvents(recordedEvents, [ @@ -1688,7 +1688,7 @@ describe('Integration', () => { { provide: 'RecordingDeactivate', useValue: (c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => { - log.push({path: a.routeConfig.path, component: c}); + log.push({path: a.routeConfig !.path, component: c}); return true; } }, @@ -1736,14 +1736,14 @@ describe('Integration', () => { advance(fixture); expect(location.path()).toEqual('/team/22'); - let successStatus: boolean; - router.navigateByUrl('/team/33').then(res => successStatus = res); + let successStatus: boolean = false; + router.navigateByUrl('/team/33') !.then(res => successStatus = res); advance(fixture); expect(location.path()).toEqual('/team/33'); expect(successStatus).toEqual(true); - let canceledStatus: boolean; - router.navigateByUrl('/team/44').then(res => canceledStatus = res); + let canceledStatus: boolean = false; + router.navigateByUrl('/team/44') !.then(res => canceledStatus = res); advance(fixture); expect(location.path()).toEqual('/team/33'); expect(canceledStatus).toEqual(false); @@ -2073,7 +2073,7 @@ describe('Integration', () => { expect(location.path()).toEqual('/team/22'); - router.navigateByUrl('/team/33').catch(() => {}); + router.navigateByUrl('/team/33') !.catch(() => {}); advance(fixture); expect(location.path()).toEqual('/team/22'); @@ -2582,8 +2582,8 @@ describe('Integration', () => { expect(location.path()).toEqual('/lazy/parent/child'); expect(fixture.nativeElement).toHaveText('parent[child]'); - const pInj = fixture.debugElement.query(By.directive(Parent)).injector; - const cInj = fixture.debugElement.query(By.directive(Child)).injector; + const pInj = fixture.debugElement.query(By.directive(Parent)).injector !; + const cInj = fixture.debugElement.query(By.directive(Child)).injector !; expect(pInj.get('moduleName')).toEqual('parent'); expect(pInj.get('fromParent')).toEqual('from parent'); @@ -2769,7 +2769,7 @@ describe('Integration', () => { router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]); let recordedError: any = null; - router.navigateByUrl('/lazy/loaded').catch(err => recordedError = err); + router.navigateByUrl('/lazy/loaded') !.catch(err => recordedError = err); advance(fixture); expect(recordedError.message) .toEqual( @@ -2942,7 +2942,7 @@ describe('Integration', () => { const recordedEvents: any[] = []; router.events.forEach(e => recordedEvents.push(e)); - router.navigateByUrl('/lazy/loaded').catch(s => {}); + router.navigateByUrl('/lazy/loaded') !.catch(s => {}); advance(fixture); expect(location.path()).toEqual('/'); @@ -3206,19 +3206,19 @@ describe('Integration', () => { stored: {[k: string]: DetachedRouteHandle} = {}; shouldDetach(route: ActivatedRouteSnapshot): boolean { - return route.routeConfig.path === 'a'; + return route.routeConfig !.path === 'a'; } store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void { - this.stored[route.routeConfig.path] = detachedTree; + this.stored[route.routeConfig !.path !] = detachedTree; } shouldAttach(route: ActivatedRouteSnapshot): boolean { - return !!this.stored[route.routeConfig.path]; + return !!this.stored[route.routeConfig !.path !]; } retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { - return this.stored[route.routeConfig.path]; + return this.stored[route.routeConfig !.path !]; } shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { @@ -3230,7 +3230,7 @@ describe('Integration', () => { shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; } store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {} shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; } - retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { return null; } + retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { return null !; } shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { if (future.routeConfig !== curr.routeConfig) { return false; diff --git a/packages/router/test/recognize.spec.ts b/packages/router/test/recognize.spec.ts index cf63d18b53..a90f99ecca 100644 --- a/packages/router/test/recognize.spec.ts +++ b/packages/router/test/recognize.spec.ts @@ -16,14 +16,14 @@ describe('recognize', () => { it('should work', () => { checkRecognize([{path: 'a', component: ComponentA}], 'a', (s: RouterStateSnapshot) => { checkActivatedRoute(s.root, '', {}, RootComponent); - checkActivatedRoute(s.firstChild(s.root), 'a', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA); }); }); it('should freeze params object', () => { checkRecognize([{path: 'a/:id', component: ComponentA}], 'a/10', (s: RouterStateSnapshot) => { checkActivatedRoute(s.root, '', {}, RootComponent); - const child = s.firstChild(s.root); + const child = s.firstChild(s.root) !; expect(Object.isFrozen(child.params)).toBeTruthy(); }); }); @@ -79,11 +79,11 @@ describe('recognize', () => { expect(s.root._urlSegment).toBe(url.root); expect(s.root._lastPathIndex).toBe(-1); - const compA = s.firstChild(s.root); + const compA = s.firstChild(s.root) !; expect(compA._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(compA._lastPathIndex).toBe(1); - const compC = s.firstChild(compA); + const compC = s.firstChild(compA) !; expect(compC._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(compC._lastPathIndex).toBe(2); }); @@ -101,11 +101,11 @@ describe('recognize', () => { expect(s.root._urlSegment).toBe(url.root); expect(s.root._lastPathIndex).toBe(-1); - const compA = s.firstChild(s.root); + const compA = s.firstChild(s.root) !; expect(compA._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(compA._lastPathIndex).toBe(0); - const compC = s.firstChild(compA); + const compC = s.firstChild(compA) !; expect(compC._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(compC._lastPathIndex).toBe(2); }); @@ -119,16 +119,16 @@ describe('recognize', () => { ], 'a/paramA', (s: RouterStateSnapshot) => { checkActivatedRoute(s.root, '', {}, RootComponent); - checkActivatedRoute(s.firstChild(s.root), 'a', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA); checkActivatedRoute( - s.firstChild(s.firstChild(s.root)), 'paramA', {id: 'paramA'}, ComponentB); + s.firstChild(s.firstChild(s.root)) !, 'paramA', {id: 'paramA'}, ComponentB); }); checkRecognize( [{path: 'a', component: ComponentA}, {path: 'a/:id', component: ComponentC}], 'a/paramA', (s: RouterStateSnapshot) => { checkActivatedRoute(s.root, '', {}, RootComponent); - checkActivatedRoute(s.firstChild(s.root), 'a/paramA', {id: 'paramA'}, ComponentC); + checkActivatedRoute(s.firstChild(s.root) !, 'a/paramA', {id: 'paramA'}, ComponentC); }); }); @@ -187,7 +187,7 @@ describe('recognize', () => { 'a;a1=11;a2=22/b;b1=111;b2=222(left:c;c1=1111;c2=2222)', (s: RouterStateSnapshot) => { const c = s.children(s.root); checkActivatedRoute(c[0], 'a', {a1: '11', a2: '22'}, ComponentA); - checkActivatedRoute(s.firstChild(c[0]), 'b', {b1: '111', b2: '222'}, ComponentB); + checkActivatedRoute(s.firstChild(c[0]) !, 'b', {b1: '111', b2: '222'}, ComponentB); checkActivatedRoute(c[1], 'c', {c1: '1111', c2: '2222'}, ComponentC, 'left'); }); }); @@ -196,7 +196,7 @@ describe('recognize', () => { it('should set static data', () => { checkRecognize( [{path: 'a', data: {one: 1}, component: ComponentA}], 'a', (s: RouterStateSnapshot) => { - const r: ActivatedRouteSnapshot = s.firstChild(s.root); + const r: ActivatedRouteSnapshot = s.firstChild(s.root) !; expect(r.data).toEqual({one: 1}); }); }); @@ -209,7 +209,7 @@ describe('recognize', () => { children: [{path: 'b', data: {two: 2}, component: ComponentB}] }], 'a/b', (s: RouterStateSnapshot) => { - const r: ActivatedRouteSnapshot = s.firstChild(s.firstChild(s.root)); + const r: ActivatedRouteSnapshot = s.firstChild(s.firstChild(s.root)) !; expect(r.data).toEqual({one: 1, two: 2}); }); }); @@ -218,7 +218,7 @@ describe('recognize', () => { checkRecognize( [{path: 'a', resolve: {one: 'some-token'}, component: ComponentA}], 'a', (s: RouterStateSnapshot) => { - const r: ActivatedRouteSnapshot = s.firstChild(s.root); + const r: ActivatedRouteSnapshot = s.firstChild(s.root) !; expect(r._resolve).toEqual({one: 'some-token'}); }); }); @@ -228,7 +228,7 @@ describe('recognize', () => { describe('root', () => { it('should work', () => { checkRecognize([{path: '', component: ComponentA}], '', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), '', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, '', {}, ComponentA); }); }); @@ -236,7 +236,7 @@ describe('recognize', () => { checkRecognize( [{path: '', pathMatch: 'full', component: ComponentA}], '', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), '', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, '', {}, ComponentA); }); }); @@ -244,8 +244,8 @@ describe('recognize', () => { checkRecognize( [{path: '', component: ComponentA, children: [{path: '', component: ComponentB}]}], '', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), '', {}, ComponentA); - checkActivatedRoute(s.firstChild(s.firstChild(s.root)), '', {}, ComponentB); + checkActivatedRoute(s.firstChild(s.root) !, '', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.firstChild(s.root)) !, '', {}, ComponentB); }); }); @@ -259,11 +259,11 @@ describe('recognize', () => { expect(s.root._urlSegment).toBe(url.root); expect(s.root._lastPathIndex).toBe(-1); - const c = s.firstChild(s.root); + const c = s.firstChild(s.root) !; expect(c._urlSegment).toBe(url.root); expect(c._lastPathIndex).toBe(-1); - const c2 = s.firstChild(s.firstChild(s.root)); + const c2 = s.firstChild(s.firstChild(s.root)) !; expect(c2._urlSegment).toBe(url.root); expect(c2._lastPathIndex).toBe(-1); }); @@ -279,10 +279,10 @@ describe('recognize', () => { ] }], '/a;p=1', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), 'a', {p: '1'}, ComponentA); - checkActivatedRoute(s.firstChild(s.firstChild(s.root)), '', {p: '1'}, ComponentB); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {p: '1'}, ComponentA); + checkActivatedRoute(s.firstChild(s.firstChild(s.root) !) !, '', {p: '1'}, ComponentB); checkActivatedRoute( - s.firstChild(s.firstChild(s.firstChild(s.root))), '', {p: '1'}, ComponentC); + s.firstChild(s.firstChild(s.firstChild(s.root) !) !) !, '', {p: '1'}, ComponentC); }); }); }); @@ -299,9 +299,9 @@ describe('recognize', () => { ] }], 'a/b', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), 'a', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA); - const c = s.children(s.firstChild(s.root)); + const c = s.children(s.firstChild(s.root) !); checkActivatedRoute(c[0], 'b', {}, ComponentB); checkActivatedRoute(c[1], '', {}, ComponentC, 'aux'); }); @@ -330,13 +330,13 @@ describe('recognize', () => { checkRecognize(config, 'parent/b', (s: RouterStateSnapshot) => { checkActivatedRoute(s.root, '', {}, RootComponent); - checkActivatedRoute(s.firstChild(s.root), 'parent', {}, undefined); + checkActivatedRoute(s.firstChild(s.root) !, 'parent', {}, undefined !); - const cc = s.children(s.firstChild(s.root)); + const cc = s.children(s.firstChild(s.root) !); checkActivatedRoute(cc[0], '', {}, ComponentA); checkActivatedRoute(cc[1], '', {}, ComponentD, 'secondary'); - checkActivatedRoute(s.firstChild(cc[0]), 'b', {}, ComponentB); + checkActivatedRoute(s.firstChild(cc[0]) !, 'b', {}, ComponentB); }); }); @@ -351,9 +351,9 @@ describe('recognize', () => { ] }], 'a/b', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), 'a', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA); - const c = s.children(s.firstChild(s.root)); + const c = s.children(s.firstChild(s.root) !); expect(c.length).toEqual(1); checkActivatedRoute(c[0], 'b', {}, ComponentB); }); @@ -375,11 +375,11 @@ describe('recognize', () => { expect(s.root._urlSegment).toBe(url.root); expect(s.root._lastPathIndex).toBe(-1); - const a = s.firstChild(s.root); + const a = s.firstChild(s.root) !; expect(a._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(a._lastPathIndex).toBe(0); - const b = s.firstChild(a); + const b = s.firstChild(a) !; expect(b._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(b._lastPathIndex).toBe(1); @@ -403,15 +403,15 @@ describe('recognize', () => { expect(s.root._urlSegment).toBe(url.root); expect(s.root._lastPathIndex).toBe(-1); - const a = s.firstChild(s.root); + const a = s.firstChild(s.root) !; expect(a._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(a._lastPathIndex).toBe(0); - const b = s.firstChild(a); + const b = s.firstChild(a) !; expect(b._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(b._lastPathIndex).toBe(0); - const c = s.firstChild(b); + const c = s.firstChild(b) !; expect(c._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); expect(c._lastPathIndex).toBe(0); }); @@ -431,15 +431,15 @@ describe('recognize', () => { expect(s.root._urlSegment).toBe(url.root); expect(s.root._lastPathIndex).toBe(-1); - const a = s.firstChild(s.root); + const a = s.firstChild(s.root) !; expect(a._urlSegment).toBe(url.root); expect(a._lastPathIndex).toBe(-1); - const b = s.firstChild(a); + const b = s.firstChild(a) !; expect(b._urlSegment).toBe(url.root); expect(b._lastPathIndex).toBe(-1); - const c = s.firstChild(b); + const c = s.firstChild(b) !; expect(c._urlSegment).toBe(url.root); expect(c._lastPathIndex).toBe(-1); }); @@ -458,9 +458,9 @@ describe('recognize', () => { ] }], 'a', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), 'a', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA); - const c = s.children(s.firstChild(s.root)); + const c = s.children(s.firstChild(s.root) !); checkActivatedRoute(c[0], '', {}, ComponentB); checkActivatedRoute(c[1], '', {}, ComponentC, 'aux'); }); @@ -477,9 +477,9 @@ describe('recognize', () => { ] }], 'a', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), 'a', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA); - const c = s.children(s.firstChild(s.root)); + const c = s.children(s.firstChild(s.root) !); checkActivatedRoute(c[0], '', {}, ComponentB); checkActivatedRoute(c[1], '', {}, ComponentC, 'aux'); }); @@ -496,9 +496,9 @@ describe('recognize', () => { ] }], 'a/(aux:c)', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), 'a', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA); - const c = s.children(s.firstChild(s.root)); + const c = s.children(s.firstChild(s.root) !); checkActivatedRoute(c[0], '', {}, ComponentB); checkActivatedRoute(c[1], 'c', {}, ComponentC, 'aux'); }); @@ -538,13 +538,13 @@ describe('recognize', () => { ] }], 'a/(d//aux:e)', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), 'a', {}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a', {}, ComponentA); - const c = s.children(s.firstChild(s.root)); + const c = s.children(s.firstChild(s.root) !); checkActivatedRoute(c[0], '', {}, ComponentB); - checkActivatedRoute(s.firstChild(c[0]), 'd', {}, ComponentD); + checkActivatedRoute(s.firstChild(c[0]) !, 'd', {}, ComponentD); checkActivatedRoute(c[1], '', {}, ComponentC, 'aux'); - checkActivatedRoute(s.firstChild(c[1]), 'e', {}, ComponentE); + checkActivatedRoute(s.firstChild(c[1]) !, 'e', {}, ComponentE); }); }); }); @@ -554,7 +554,7 @@ describe('recognize', () => { it('should support simple wildcards', () => { checkRecognize( [{path: '**', component: ComponentA}], 'a/b/c/d;a1=11', (s: RouterStateSnapshot) => { - checkActivatedRoute(s.firstChild(s.root), 'a/b/c/d', {a1: '11'}, ComponentA); + checkActivatedRoute(s.firstChild(s.root) !, 'a/b/c/d', {a1: '11'}, ComponentA); }); }); }); @@ -570,8 +570,8 @@ describe('recognize', () => { ] }], 'p/11;pp=22/(a;pa=33//aux:b;pb=44)', (s: RouterStateSnapshot) => { - const p = s.firstChild(s.root); - checkActivatedRoute(p, 'p/11', {id: '11', pp: '22'}, undefined); + const p = s.firstChild(s.root) !; + checkActivatedRoute(p, 'p/11', {id: '11', pp: '22'}, undefined !); const c = s.children(p); checkActivatedRoute(c[0], 'a', {id: '11', pp: '22', pa: '33'}, ComponentA); @@ -593,16 +593,16 @@ describe('recognize', () => { }] }], 'p/11/a/victor/b/c', (s: RouterStateSnapshot) => { - const p = s.firstChild(s.root); - checkActivatedRoute(p, 'p/11', {id: '11'}, undefined); + const p = s.firstChild(s.root) !; + checkActivatedRoute(p, 'p/11', {id: '11'}, undefined !); - const a = s.firstChild(p); - checkActivatedRoute(a, 'a/victor', {id: '11', name: 'victor'}, undefined); + const a = s.firstChild(p) !; + checkActivatedRoute(a, 'a/victor', {id: '11', name: 'victor'}, undefined !); - const b = s.firstChild(a); + const b = s.firstChild(a) !; checkActivatedRoute(b, 'b', {id: '11', name: 'victor'}, ComponentB); - const c = s.firstChild(b); + const c = s.firstChild(b) !; checkActivatedRoute(c, 'c', {}, ComponentC); }); }); @@ -614,7 +614,7 @@ describe('recognize', () => { [{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}], '/a', (s: RouterStateSnapshot) => { const a = s.firstChild(s.root); - checkActivatedRoute(a, 'a', {}, ComponentA); + checkActivatedRoute(a !, 'a', {}, ComponentA); }); }); @@ -629,7 +629,7 @@ describe('recognize', () => { ] }], '/a', (s: RouterStateSnapshot) => { - const a = s.firstChild(s.root); + const a = s.firstChild(s.root) !; checkActivatedRoute(a, 'a', {}, ComponentA); checkActivatedRoute(a.children[0], '', {}, ComponentC, 'aux'); }); @@ -651,11 +651,11 @@ describe('recognize', () => { matcher: matcher, component: ComponentA, children: [{path: 'b', component: ComponentB}] - }], + }] as any, '/a/1;p=99/b', (s: RouterStateSnapshot) => { - const a = s.root.firstChild; + const a = s.root.firstChild !; checkActivatedRoute(a, 'a/1', {id: '1', p: '99'}, ComponentA); - checkActivatedRoute(a.firstChild, 'b', {}, ComponentB); + checkActivatedRoute(a.firstChild !, 'b', {}, ComponentB); }); }); }); diff --git a/packages/router/test/router.spec.ts b/packages/router/test/router.spec.ts index 6f293266ad..77b8984ee7 100644 --- a/packages/router/test/router.spec.ts +++ b/packages/router/test/router.spec.ts @@ -57,7 +57,7 @@ describe('Router', () => { const inj = {get: (token: any) => () => `${token}_value`}; let empty: RouterStateSnapshot; - beforeEach(() => { empty = createEmptyStateSnapshot(serializer.parse('/'), null); }); + beforeEach(() => { empty = createEmptyStateSnapshot(serializer.parse('/'), null !); }); it('should resolve data', () => { const r = {data: 'resolver'}; @@ -65,7 +65,7 @@ describe('Router', () => { const s = new RouterStateSnapshot('url', new TreeNode(empty.root, [new TreeNode(n, [])])); checkResolveData(s, empty, inj, () => { - expect(s.root.firstChild.data).toEqual({data: 'resolver_value'}); + expect(s.root.firstChild !.data).toEqual({data: 'resolver_value'}); }); }); @@ -73,7 +73,7 @@ describe('Router', () => { const parentResolve = {data: 'resolver'}; const childResolve = {}; - const parent = createActivatedRouteSnapshot(null, {resolve: parentResolve}); + const parent = createActivatedRouteSnapshot(null !, {resolve: parentResolve}); const child = createActivatedRouteSnapshot('b', {resolve: childResolve}); const s = new RouterStateSnapshot( @@ -82,7 +82,7 @@ describe('Router', () => { const inj = {get: (token: any) => () => Promise.resolve(`${token}_value`)}; checkResolveData(s, empty, inj, () => { - expect(s.root.firstChild.firstChild.data).toEqual({data: 'resolver_value'}); + expect(s.root.firstChild !.firstChild !.data).toEqual({data: 'resolver_value'}); }); }); @@ -99,8 +99,8 @@ describe('Router', () => { const s2 = new RouterStateSnapshot( 'url', new TreeNode(empty.root, [new TreeNode(n21, [new TreeNode(n22, [])])])); checkResolveData(s2, s1, inj, () => { - expect(s2.root.firstChild.data).toEqual({data: 'resolver1_value'}); - expect(s2.root.firstChild.firstChild.data).toEqual({data: 'resolver2_value'}); + expect(s2.root.firstChild !.data).toEqual({data: 'resolver1_value'}); + expect(s2.root.firstChild !.firstChild !.data).toEqual({data: 'resolver2_value'}); }); }); }); diff --git a/packages/router/test/router_state.spec.ts b/packages/router/test/router_state.spec.ts index 6417c7d9d7..f813d6c050 100644 --- a/packages/router/test/router_state.spec.ts +++ b/packages/router/test/router_state.spec.ts @@ -39,17 +39,17 @@ describe('RouterState & Snapshot', () => { }); it('should return root', () => { - const b = state.root.firstChild; + const b = state.root.firstChild !; expect(b.root).toBe(state.root); }); it('should return parent', () => { - const b = state.root.firstChild; + const b = state.root.firstChild !; expect(b.parent).toBe(state.root); }); it('should return path from root', () => { - const b = state.root.firstChild; + const b = state.root.firstChild !; const p = b.pathFromRoot; expect(p[0]).toBe(state.root); expect(p[1]).toBe(b); @@ -81,17 +81,17 @@ describe('RouterState & Snapshot', () => { }); it('should return root', () => { - const b = state.root.firstChild; + const b = state.root.firstChild !; expect(b.root).toBe(state.root); }); it('should return parent', () => { - const b = state.root.firstChild; + const b = state.root.firstChild !; expect(b.parent).toBe(state.root); }); it('should return path from root', () => { - const b = state.root.firstChild; + const b = state.root.firstChild !; const p = b.pathFromRoot; expect(p[0]).toBe(state.root); expect(p[1]).toBe(b); @@ -102,7 +102,7 @@ describe('RouterState & Snapshot', () => { function createSnapshot(params: Params, url: UrlSegment[]): ActivatedRouteSnapshot { const snapshot = new ActivatedRouteSnapshot( url, params, null, null, null, null, null, null, null, - -1, null); + -1, null !); snapshot._routerState = new RouterStateSnapshot('', new TreeNode(snapshot, [])); return snapshot; } @@ -177,7 +177,7 @@ describe('RouterState & Snapshot', () => { const data = {}; const snapshot = new ActivatedRouteSnapshot( url, params, queryParams, fragment, data, null, null, null, null, -1, - null); + null !); const state = new RouterStateSnapshot('', new TreeNode(snapshot, [])); snapshot._routerState = state; return snapshot; @@ -200,7 +200,7 @@ describe('RouterState & Snapshot', () => { function createActivatedRouteSnapshot(cmp: string) { return new ActivatedRouteSnapshot( null, null, null, null, null, null, cmp, null, - null, -1, null); + null, -1, null !); } function createActivatedRoute(cmp: string) { diff --git a/packages/router/testing/src/router_testing_module.ts b/packages/router/testing/src/router_testing_module.ts index 379abe7186..11fb8bb5fa 100644 --- a/packages/router/testing/src/router_testing_module.ts +++ b/packages/router/testing/src/router_testing_module.ts @@ -86,7 +86,7 @@ export function setupTestingRouter( loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], urlHandlingStrategy?: UrlHandlingStrategy) { const router = new Router( - null, urlSerializer, outletMap, location, injector, loader, compiler, flatten(routes)); + null !, urlSerializer, outletMap, location, injector, loader, compiler, flatten(routes)); if (urlHandlingStrategy) { router.urlHandlingStrategy = urlHandlingStrategy; } diff --git a/packages/router/tsconfig-build.json b/packages/router/tsconfig-build.json index 5dd5e957d4..5cca1d6c13 100644 --- a/packages/router/tsconfig-build.json +++ b/packages/router/tsconfig-build.json @@ -10,6 +10,7 @@ "noEmitOnError": false, "noImplicitAny": true, "noImplicitReturns": true, + "strictNullChecks": true, "outDir": "../../dist/packages/router", "paths": { "@angular/core": ["../../dist/packages/core"], diff --git a/tools/public_api_guard/router/router.d.ts b/tools/public_api_guard/router/router.d.ts index 0387427f58..6dc3d5d2fa 100644 --- a/tools/public_api_guard/router/router.d.ts +++ b/tools/public_api_guard/router/router.d.ts @@ -1,19 +1,19 @@ /** @stable */ export declare class ActivatedRoute { readonly children: ActivatedRoute[]; - component: Type | string; + component: Type | string | null; data: Observable; - readonly firstChild: ActivatedRoute; + readonly firstChild: ActivatedRoute | null; fragment: Observable; outlet: string; readonly paramMap: Observable; params: Observable; - readonly parent: ActivatedRoute; + readonly parent: ActivatedRoute | null; readonly pathFromRoot: ActivatedRoute[]; readonly queryParamMap: Observable; queryParams: Observable; readonly root: ActivatedRoute; - readonly routeConfig: Route; + readonly routeConfig: Route | null; snapshot: ActivatedRouteSnapshot; url: Observable; toString(): string; @@ -22,19 +22,19 @@ export declare class ActivatedRoute { /** @stable */ export declare class ActivatedRouteSnapshot { readonly children: ActivatedRouteSnapshot[]; - component: Type | string; + component: Type | string | null; data: Data; - readonly firstChild: ActivatedRouteSnapshot; + readonly firstChild: ActivatedRouteSnapshot | null; fragment: string; outlet: string; readonly paramMap: ParamMap; params: Params; - readonly parent: ActivatedRouteSnapshot; + readonly parent: ActivatedRouteSnapshot | null; readonly pathFromRoot: ActivatedRouteSnapshot[]; readonly queryParamMap: ParamMap; queryParams: Params; readonly root: ActivatedRouteSnapshot; - readonly routeConfig: Route; + readonly routeConfig: Route | null; url: UrlSegment[]; toString(): string; } @@ -239,7 +239,7 @@ export declare class Router { readonly routerState: RouterState; readonly url: string; urlHandlingStrategy: UrlHandlingStrategy; - constructor(rootComponentType: Type, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes); + constructor(rootComponentType: Type | null, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes); createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, queryParamsHandling, preserveFragment}?: NavigationExtras): UrlTree; dispose(): void; initialNavigation(): void; @@ -261,11 +261,11 @@ export declare const ROUTER_INITIALIZER: InjectionToken<(compRef: ComponentRef Date: Fri, 14 Apr 2017 13:59:08 -0700 Subject: [PATCH 0033/1039] fix(forms): Update types for TypeScript nullability support This reverts commit 2e47a0d19fa1d1527cf998d87cd8a49bc968606a. --- .../directives/abstract_control_directive.ts | 34 +++---- .../abstract_form_group_directive.ts | 12 +-- .../forms/src/directives/control_container.ts | 6 +- packages/forms/src/directives/ng_control.ts | 10 +-- packages/forms/src/directives/ng_form.ts | 2 +- packages/forms/src/directives/ng_model.ts | 6 +- .../src/directives/number_value_accessor.ts | 2 +- .../src/directives/range_value_accessor.ts | 2 +- .../form_control_directive.ts | 8 +- .../reactive_directives/form_control_name.ts | 10 +-- .../form_group_directive.ts | 6 +- .../reactive_directives/form_group_name.ts | 12 +-- .../select_control_value_accessor.ts | 6 +- .../select_multiple_control_value_accessor.ts | 6 +- packages/forms/src/directives/shared.ts | 45 +++++----- packages/forms/src/form_builder.ts | 10 +-- packages/forms/src/model.ts | 47 +++++----- packages/forms/src/validators.ts | 12 +-- packages/forms/test/directives_spec.ts | 38 ++++---- packages/forms/test/form_array_spec.ts | 48 +++++----- packages/forms/test/form_control_spec.ts | 29 +++--- packages/forms/test/form_group_spec.ts | 56 ++++++------ .../forms/test/reactive_integration_spec.ts | 52 +++++------ .../forms/test/template_integration_spec.ts | 66 +++++++------- packages/forms/test/validators_spec.ts | 58 ++++++------ packages/forms/tsconfig-build.json | 1 + tools/public_api_guard/forms/forms.d.ts | 89 ++++++++++--------- 27 files changed, 343 insertions(+), 330 deletions(-) diff --git a/packages/forms/src/directives/abstract_control_directive.ts b/packages/forms/src/directives/abstract_control_directive.ts index f2cef2e96f..1bf7524dae 100644 --- a/packages/forms/src/directives/abstract_control_directive.ts +++ b/packages/forms/src/directives/abstract_control_directive.ts @@ -18,45 +18,49 @@ import {ValidationErrors} from './validators'; * @stable */ export abstract class AbstractControlDirective { - get control(): AbstractControl { throw new Error('unimplemented'); } + abstract get control(): AbstractControl|null; get value(): any { return this.control ? this.control.value : null; } - get valid(): boolean { return this.control ? this.control.valid : null; } + get valid(): boolean|null { return this.control ? this.control.valid : null; } - get invalid(): boolean { return this.control ? this.control.invalid : null; } + get invalid(): boolean|null { return this.control ? this.control.invalid : null; } - get pending(): boolean { return this.control ? this.control.pending : null; } + get pending(): boolean|null { return this.control ? this.control.pending : null; } get errors(): ValidationErrors|null { return this.control ? this.control.errors : null; } - get pristine(): boolean { return this.control ? this.control.pristine : null; } + get pristine(): boolean|null { return this.control ? this.control.pristine : null; } - get dirty(): boolean { return this.control ? this.control.dirty : null; } + get dirty(): boolean|null { return this.control ? this.control.dirty : null; } - get touched(): boolean { return this.control ? this.control.touched : null; } + get touched(): boolean|null { return this.control ? this.control.touched : null; } - get untouched(): boolean { return this.control ? this.control.untouched : null; } + get untouched(): boolean|null { return this.control ? this.control.untouched : null; } - get disabled(): boolean { return this.control ? this.control.disabled : null; } + get disabled(): boolean|null { return this.control ? this.control.disabled : null; } - get enabled(): boolean { return this.control ? this.control.enabled : null; } + get enabled(): boolean|null { return this.control ? this.control.enabled : null; } - get statusChanges(): Observable { return this.control ? this.control.statusChanges : null; } + get statusChanges(): Observable|null { + return this.control ? this.control.statusChanges : null; + } - get valueChanges(): Observable { return this.control ? this.control.valueChanges : null; } + get valueChanges(): Observable|null { + return this.control ? this.control.valueChanges : null; + } - get path(): string[] { return null; } + get path(): string[]|null { return null; } reset(value: any = undefined): void { if (this.control) this.control.reset(value); } - hasError(errorCode: string, path: string[] = null): boolean { + hasError(errorCode: string, path?: string[]): boolean { return this.control ? this.control.hasError(errorCode, path) : false; } - getError(errorCode: string, path: string[] = null): any { + getError(errorCode: string, path?: string[]): any { return this.control ? this.control.getError(errorCode, path) : null; } } diff --git a/packages/forms/src/directives/abstract_form_group_directive.ts b/packages/forms/src/directives/abstract_form_group_directive.ts index 19661ec147..30829df9f2 100644 --- a/packages/forms/src/directives/abstract_form_group_directive.ts +++ b/packages/forms/src/directives/abstract_form_group_directive.ts @@ -34,7 +34,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn ngOnInit(): void { this._checkParentType(); - this.formDirective.addFormGroup(this); + this.formDirective !.addFormGroup(this); } ngOnDestroy(): void { @@ -46,7 +46,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn /** * Get the {@link FormGroup} backing this binding. */ - get control(): FormGroup { return this.formDirective.getFormGroup(this); } + get control(): FormGroup { return this.formDirective !.getFormGroup(this); } /** * Get the path to this control group. @@ -56,11 +56,13 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn /** * Get the {@link Form} to which this group belongs. */ - get formDirective(): Form { return this._parent ? this._parent.formDirective : null; } + get formDirective(): Form|null { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn { return composeValidators(this._validators); } + get validator(): ValidatorFn|null { return composeValidators(this._validators); } - get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); } + get asyncValidator(): AsyncValidatorFn|null { + return composeAsyncValidators(this._asyncValidators); + } /** @internal */ _checkParentType(): void {} diff --git a/packages/forms/src/directives/control_container.ts b/packages/forms/src/directives/control_container.ts index a0ee276426..39225c0dca 100644 --- a/packages/forms/src/directives/control_container.ts +++ b/packages/forms/src/directives/control_container.ts @@ -17,16 +17,16 @@ import {Form} from './form_interface'; * * @stable */ -export class ControlContainer extends AbstractControlDirective { +export abstract class ControlContainer extends AbstractControlDirective { name: string; /** * Get the form to which this container belongs. */ - get formDirective(): Form { return null; } + get formDirective(): Form|null { return null; } /** * Get the path to this container. */ - get path(): string[] { return null; } + get path(): string[]|null { return null; } } diff --git a/packages/forms/src/directives/ng_control.ts b/packages/forms/src/directives/ng_control.ts index 7f2d4b04cc..503adf9281 100644 --- a/packages/forms/src/directives/ng_control.ts +++ b/packages/forms/src/directives/ng_control.ts @@ -26,16 +26,16 @@ function unimplemented(): any { */ export abstract class NgControl extends AbstractControlDirective { /** @internal */ - _parent: ControlContainer = null; - name: string = null; - valueAccessor: ControlValueAccessor = null; + _parent: ControlContainer|null = null; + name: string|null = null; + valueAccessor: ControlValueAccessor|null = null; /** @internal */ _rawValidators: Array = []; /** @internal */ _rawAsyncValidators: Array = []; - get validator(): ValidatorFn { return unimplemented(); } - get asyncValidator(): AsyncValidatorFn { return unimplemented(); } + get validator(): ValidatorFn|null { return unimplemented(); } + get asyncValidator(): AsyncValidatorFn|null { return unimplemented(); } abstract viewToModelUpdate(newValue: any): void; } diff --git a/packages/forms/src/directives/ng_form.ts b/packages/forms/src/directives/ng_form.ts index 6803a6d296..6f69007d69 100644 --- a/packages/forms/src/directives/ng_form.ts +++ b/packages/forms/src/directives/ng_form.ts @@ -130,7 +130,7 @@ export class NgForm extends ControlContainer implements Form { updateModel(dir: NgControl, value: any): void { resolvedPromise.then(() => { - const ctrl = this.form.get(dir.path); + const ctrl = this.form.get(dir.path !); ctrl.setValue(value); }); } diff --git a/packages/forms/src/directives/ng_model.ts b/packages/forms/src/directives/ng_model.ts index c27b28d026..7d58e51e80 100644 --- a/packages/forms/src/directives/ng_model.ts +++ b/packages/forms/src/directives/ng_model.ts @@ -158,9 +158,9 @@ export class NgModel extends NgControl implements OnChanges, get formDirective(): any { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } - get asyncValidator(): AsyncValidatorFn { + get asyncValidator(): AsyncValidatorFn|null { return composeAsyncValidators(this._rawAsyncValidators); } @@ -176,7 +176,7 @@ export class NgModel extends NgControl implements OnChanges, } private _isStandalone(): boolean { - return !this._parent || (this.options && this.options.standalone); + return !this._parent || !!(this.options && this.options.standalone); } private _setUpStandalone(): void { diff --git a/packages/forms/src/directives/number_value_accessor.ts b/packages/forms/src/directives/number_value_accessor.ts index 8c4baf8433..3710faa248 100644 --- a/packages/forms/src/directives/number_value_accessor.ts +++ b/packages/forms/src/directives/number_value_accessor.ts @@ -47,7 +47,7 @@ export class NumberValueAccessor implements ControlValueAccessor { this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue); } - registerOnChange(fn: (_: number) => void): void { + registerOnChange(fn: (_: number|null) => void): void { this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } diff --git a/packages/forms/src/directives/range_value_accessor.ts b/packages/forms/src/directives/range_value_accessor.ts index cf89bc920c..01b5dc0ff3 100644 --- a/packages/forms/src/directives/range_value_accessor.ts +++ b/packages/forms/src/directives/range_value_accessor.ts @@ -45,7 +45,7 @@ export class RangeValueAccessor implements ControlValueAccessor { this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', parseFloat(value)); } - registerOnChange(fn: (_: number) => void): void { + registerOnChange(fn: (_: number|null) => void): void { this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; } diff --git a/packages/forms/src/directives/reactive_directives/form_control_directive.ts b/packages/forms/src/directives/reactive_directives/form_control_directive.ts index 9db23e2aeb..eefabe0702 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_directive.ts @@ -88,8 +88,8 @@ export class FormControlDirective extends NgControl implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (this._isControlChanged(changes)) { setUpControl(this.form, this); - if (this.control.disabled && this.valueAccessor.setDisabledState) { - this.valueAccessor.setDisabledState(true); + if (this.control.disabled && this.valueAccessor !.setDisabledState) { + this.valueAccessor !.setDisabledState !(true); } this.form.updateValueAndValidity({emitEvent: false}); } @@ -101,9 +101,9 @@ export class FormControlDirective extends NgControl implements OnChanges { get path(): string[] { return []; } - get validator(): ValidatorFn { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } - get asyncValidator(): AsyncValidatorFn { + get asyncValidator(): AsyncValidatorFn|null { return composeAsyncValidators(this._rawAsyncValidators); } diff --git a/packages/forms/src/directives/reactive_directives/form_control_name.ts b/packages/forms/src/directives/reactive_directives/form_control_name.ts index def1aeb898..b4e06d4a5c 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_name.ts @@ -125,14 +125,14 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { this.update.emit(newValue); } - get path(): string[] { return controlPath(this.name, this._parent); } + get path(): string[] { return controlPath(this.name, this._parent !); } get formDirective(): any { return this._parent ? this._parent.formDirective : null; } - get validator(): ValidatorFn { return composeValidators(this._rawValidators); } + get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } get asyncValidator(): AsyncValidatorFn { - return composeAsyncValidators(this._rawAsyncValidators); + return composeAsyncValidators(this._rawAsyncValidators) !; } get control(): FormControl { return this._control; } @@ -151,8 +151,8 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { private _setUpControl() { this._checkParentType(); this._control = this.formDirective.addControl(this); - if (this.control.disabled && this.valueAccessor.setDisabledState) { - this.valueAccessor.setDisabledState(true); + if (this.control.disabled && this.valueAccessor !.setDisabledState) { + this.valueAccessor !.setDisabledState !(true); } this._added = true; } diff --git a/packages/forms/src/directives/reactive_directives/form_group_directive.ts b/packages/forms/src/directives/reactive_directives/form_group_directive.ts index 6ec1773e2d..9b5ec7af0f 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_directive.ts @@ -69,7 +69,7 @@ export class FormGroupDirective extends ControlContainer implements Form, private _oldForm: FormGroup; directives: FormControlName[] = []; - @Input('formGroup') form: FormGroup = null; + @Input('formGroup') form: FormGroup = null !; @Output() ngSubmit = new EventEmitter(); constructor( @@ -167,10 +167,10 @@ export class FormGroupDirective extends ControlContainer implements Form, private _updateValidators() { const sync = composeValidators(this._validators); - this.form.validator = Validators.compose([this.form.validator, sync]); + this.form.validator = Validators.compose([this.form.validator !, sync !]); const async = composeAsyncValidators(this._asyncValidators); - this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]); + this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator !, async !]); } private _checkFormPresent() { diff --git a/packages/forms/src/directives/reactive_directives/form_group_name.ts b/packages/forms/src/directives/reactive_directives/form_group_name.ts index 3dca7c5a4c..3d7904f155 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_name.ts @@ -166,7 +166,7 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy ngOnInit(): void { this._checkParentType(); - this.formDirective.addFormArray(this); + this.formDirective !.addFormArray(this); } ngOnDestroy(): void { @@ -175,17 +175,19 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy } } - get control(): FormArray { return this.formDirective.getFormArray(this); } + get control(): FormArray { return this.formDirective !.getFormArray(this); } - get formDirective(): FormGroupDirective { + get formDirective(): FormGroupDirective|null { return this._parent ? this._parent.formDirective : null; } get path(): string[] { return controlPath(this.name, this._parent); } - get validator(): ValidatorFn { return composeValidators(this._validators); } + get validator(): ValidatorFn|null { return composeValidators(this._validators); } - get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); } + get asyncValidator(): AsyncValidatorFn|null { + return composeAsyncValidators(this._asyncValidators); + } private _checkParentType(): void { if (_hasInvalidParent(this._parent)) { diff --git a/packages/forms/src/directives/select_control_value_accessor.ts b/packages/forms/src/directives/select_control_value_accessor.ts index daf5d03727..4c5c58dd4a 100644 --- a/packages/forms/src/directives/select_control_value_accessor.ts +++ b/packages/forms/src/directives/select_control_value_accessor.ts @@ -15,7 +15,7 @@ export const SELECT_VALUE_ACCESSOR: Provider = { multi: true }; -function _buildValueString(id: string, value: any): string { +function _buildValueString(id: string | null, value: any): string { if (id == null) return `${value}`; if (value && typeof value === 'object') value = 'Object'; return `${id}: ${value}`.slice(0, 50); @@ -118,7 +118,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor { writeValue(value: any): void { this.value = value; - const id: string = this._getOptionId(value); + const id: string|null = this._getOptionId(value); if (id == null) { this._renderer.setElementProperty(this._elementRef.nativeElement, 'selectedIndex', -1); } @@ -142,7 +142,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor { _registerOption(): string { return (this._idCounter++).toString(); } /** @internal */ - _getOptionId(value: any): string { + _getOptionId(value: any): string|null { for (const id of Array.from(this._optionMap.keys())) { if (this._compareWith(this._optionMap.get(id), value)) return id; } diff --git a/packages/forms/src/directives/select_multiple_control_value_accessor.ts b/packages/forms/src/directives/select_multiple_control_value_accessor.ts index 960cea7a64..37591b5e6f 100644 --- a/packages/forms/src/directives/select_multiple_control_value_accessor.ts +++ b/packages/forms/src/directives/select_multiple_control_value_accessor.ts @@ -149,9 +149,9 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor } /** @internal */ - _getOptionId(value: any): string { + _getOptionId(value: any): string|null { for (const id of Array.from(this._optionMap.keys())) { - if (this._compareWith(this._optionMap.get(id)._value, value)) return id; + if (this._compareWith(this._optionMap.get(id) !._value, value)) return id; } return null; } @@ -159,7 +159,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor /** @internal */ _getOptionValue(valueString: string): any { const id: string = _extractId(valueString); - return this._optionMap.has(id) ? this._optionMap.get(id)._value : valueString; + return this._optionMap.has(id) ? this._optionMap.get(id) !._value : valueString; } } diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts index c8342bd334..8823b62818 100644 --- a/packages/forms/src/directives/shared.ts +++ b/packages/forms/src/directives/shared.ts @@ -27,55 +27,55 @@ import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './valida export function controlPath(name: string, parent: ControlContainer): string[] { - return [...parent.path, name]; + return [...parent.path !, name]; } export function setUpControl(control: FormControl, dir: NgControl): void { if (!control) _throwError(dir, 'Cannot find control with'); if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with'); - control.validator = Validators.compose([control.validator, dir.validator]); - control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]); - dir.valueAccessor.writeValue(control.value); + control.validator = Validators.compose([control.validator !, dir.validator]); + control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]); + dir.valueAccessor !.writeValue(control.value); // view -> model - dir.valueAccessor.registerOnChange((newValue: any) => { + dir.valueAccessor !.registerOnChange((newValue: any) => { dir.viewToModelUpdate(newValue); control.markAsDirty(); control.setValue(newValue, {emitModelToViewChange: false}); }); // touched - dir.valueAccessor.registerOnTouched(() => control.markAsTouched()); + dir.valueAccessor !.registerOnTouched(() => control.markAsTouched()); control.registerOnChange((newValue: any, emitModelEvent: boolean) => { // control -> view - dir.valueAccessor.writeValue(newValue); + dir.valueAccessor !.writeValue(newValue); // control -> ngModel if (emitModelEvent) dir.viewToModelUpdate(newValue); }); - if (dir.valueAccessor.setDisabledState) { + if (dir.valueAccessor !.setDisabledState) { control.registerOnDisabledChange( - (isDisabled: boolean) => { dir.valueAccessor.setDisabledState(isDisabled); }); + (isDisabled: boolean) => { dir.valueAccessor !.setDisabledState !(isDisabled); }); } // re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4 dir._rawValidators.forEach((validator: Validator | ValidatorFn) => { if ((validator).registerOnValidatorChange) - (validator).registerOnValidatorChange(() => control.updateValueAndValidity()); + (validator).registerOnValidatorChange !(() => control.updateValueAndValidity()); }); dir._rawAsyncValidators.forEach((validator: AsyncValidator | AsyncValidatorFn) => { if ((validator).registerOnValidatorChange) - (validator).registerOnValidatorChange(() => control.updateValueAndValidity()); + (validator).registerOnValidatorChange !(() => control.updateValueAndValidity()); }); } export function cleanUpControl(control: FormControl, dir: NgControl) { - dir.valueAccessor.registerOnChange(() => _noControlError(dir)); - dir.valueAccessor.registerOnTouched(() => _noControlError(dir)); + dir.valueAccessor !.registerOnChange(() => _noControlError(dir)); + dir.valueAccessor !.registerOnTouched(() => _noControlError(dir)); dir._rawValidators.forEach((validator: any) => { if (validator.registerOnValidatorChange) { @@ -105,9 +105,9 @@ function _noControlError(dir: NgControl) { function _throwError(dir: AbstractControlDirective, message: string): void { let messageEnd: string; - if (dir.path.length > 1) { - messageEnd = `path: '${dir.path.join(' -> ')}'`; - } else if (dir.path[0]) { + if (dir.path !.length > 1) { + messageEnd = `path: '${dir.path!.join(' -> ')}'`; + } else if (dir.path ![0]) { messageEnd = `name: '${dir.path}'`; } else { messageEnd = 'unspecified name attribute'; @@ -115,11 +115,12 @@ function _throwError(dir: AbstractControlDirective, message: string): void { throw new Error(`${message} ${messageEnd}`); } -export function composeValidators(validators: Array): ValidatorFn { +export function composeValidators(validators: Array): ValidatorFn|null { return validators != null ? Validators.compose(validators.map(normalizeValidator)) : null; } -export function composeAsyncValidators(validators: Array): AsyncValidatorFn { +export function composeAsyncValidators(validators: Array): AsyncValidatorFn| + null { return validators != null ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) : null; } @@ -147,12 +148,12 @@ export function isBuiltInAccessor(valueAccessor: ControlValueAccessor): boolean // TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented export function selectValueAccessor( - dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor { + dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor|null { if (!valueAccessors) return null; - let defaultAccessor: ControlValueAccessor; - let builtinAccessor: ControlValueAccessor; - let customAccessor: ControlValueAccessor; + let defaultAccessor: ControlValueAccessor|undefined = undefined; + let builtinAccessor: ControlValueAccessor|undefined = undefined; + let customAccessor: ControlValueAccessor|undefined = undefined; valueAccessors.forEach((v: ControlValueAccessor) => { if (v.constructor === DefaultValueAccessor) { defaultAccessor = v; diff --git a/packages/forms/src/form_builder.ts b/packages/forms/src/form_builder.ts index 1384ea1d38..b10797c04d 100644 --- a/packages/forms/src/form_builder.ts +++ b/packages/forms/src/form_builder.ts @@ -39,7 +39,7 @@ export class FormBuilder { * * See the {@link FormGroup} constructor for more details. */ - group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): FormGroup { + group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any}|null = null): FormGroup { const controls = this._reduceControls(controlsConfig); const validator: ValidatorFn = extra != null ? extra['validator'] : null; const asyncValidator: AsyncValidatorFn = extra != null ? extra['asyncValidator'] : null; @@ -54,8 +54,8 @@ export class FormBuilder { * */ control( - formState: Object, validator: ValidatorFn|ValidatorFn[] = null, - asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): FormControl { + formState: Object, validator?: ValidatorFn|ValidatorFn[]|null, + asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl { return new FormControl(formState, validator, asyncValidator); } @@ -64,8 +64,8 @@ export class FormBuilder { * configuration, with the given optional `validator` and `asyncValidator`. */ array( - controlsConfig: any[], validator: ValidatorFn = null, - asyncValidator: AsyncValidatorFn = null): FormArray { + controlsConfig: any[], validator?: ValidatorFn|null, + asyncValidator?: AsyncValidatorFn|null): FormArray { const controls = controlsConfig.map(c => this._createControl(c)); return new FormArray(controls, validator, asyncValidator); } diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index cd00942f60..d072b7ed05 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -42,7 +42,7 @@ function _find(control: AbstractControl, path: Array| string, del } if (path instanceof Array && (path.length === 0)) return null; - return (>path).reduce((v, name) => { + return (>path).reduce((v: AbstractControl, name) => { if (v instanceof FormGroup) { return v.controls[name] || null; } @@ -55,13 +55,14 @@ function _find(control: AbstractControl, path: Array| string, del }, control); } -function coerceToValidator(validator: ValidatorFn | ValidatorFn[]): ValidatorFn { - return Array.isArray(validator) ? composeValidators(validator) : validator; +function coerceToValidator(validator?: ValidatorFn | ValidatorFn[] | null): ValidatorFn|null { + return Array.isArray(validator) ? composeValidators(validator) : validator || null; } -function coerceToAsyncValidator(asyncValidator: AsyncValidatorFn | AsyncValidatorFn[]): - AsyncValidatorFn { - return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) : asyncValidator; +function coerceToAsyncValidator(asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): + AsyncValidatorFn|null { + return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) : + asyncValidator || null; } /** @@ -90,7 +91,7 @@ export abstract class AbstractControl { private _parent: FormGroup|FormArray; private _asyncValidationSubscription: any; - constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {} + constructor(public validator: ValidatorFn|null, public asyncValidator: AsyncValidatorFn|null) {} /** * The value of the control. @@ -209,7 +210,7 @@ export abstract class AbstractControl { * Sets the synchronous validators that are active on this control. Calling * this will overwrite any existing sync validators. */ - setValidators(newValidator: ValidatorFn|ValidatorFn[]): void { + setValidators(newValidator: ValidatorFn|ValidatorFn[]|null): void { this.validator = coerceToValidator(newValidator); } @@ -322,7 +323,7 @@ export abstract class AbstractControl { this._statusChanges.emit(this._status); } - this._updateAncestors(onlySelf); + this._updateAncestors(!!onlySelf); this._onDisabledChange.forEach((changeFn) => changeFn(true)); } @@ -338,7 +339,7 @@ export abstract class AbstractControl { this._forEachChild((control: AbstractControl) => { control.enable({onlySelf: true}); }); this.updateValueAndValidity({onlySelf: true, emitEvent}); - this._updateAncestors(onlySelf); + this._updateAncestors(!!onlySelf); this._onDisabledChange.forEach((changeFn) => changeFn(false)); } @@ -409,7 +410,7 @@ export abstract class AbstractControl { return this.validator ? this.validator(this) : null; } - private _runAsyncValidator(emitEvent: boolean): void { + private _runAsyncValidator(emitEvent?: boolean): void { if (this.asyncValidator) { this._status = PENDING; const obs = toObservable(this.asyncValidator(this)); @@ -465,7 +466,7 @@ export abstract class AbstractControl { * * * `this.form.get(['person', 'name']);` */ - get(path: Array|string): AbstractControl { return _find(this, path, '.'); } + get(path: Array|string): AbstractControl|null { return _find(this, path, '.'); } /** * Returns true if the control with the given path has the error specified. Otherwise @@ -473,7 +474,7 @@ export abstract class AbstractControl { * * If no path is given, it checks for the error on the present control. */ - getError(errorCode: string, path: string[] = null): any { + getError(errorCode: string, path?: string[]): any { const control = path ? this.get(path) : this; return control && control._errors ? control._errors[errorCode] : null; } @@ -484,9 +485,7 @@ export abstract class AbstractControl { * * If no path is given, it checks for the error on the present control. */ - hasError(errorCode: string, path: string[] = null): boolean { - return !!this.getError(errorCode, path); - } + hasError(errorCode: string, path?: string[]): boolean { return !!this.getError(errorCode, path); } /** * Retrieves the top-level ancestor of this control. @@ -635,8 +634,8 @@ export class FormControl extends AbstractControl { _onChange: Function[] = []; constructor( - formState: any = null, validator: ValidatorFn|ValidatorFn[] = null, - asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null) { + formState: any = null, validator?: ValidatorFn|ValidatorFn[]|null, + asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) { super(coerceToValidator(validator), coerceToAsyncValidator(asyncValidator)); this._applyFormState(formState); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); @@ -831,9 +830,9 @@ export class FormControl extends AbstractControl { */ export class FormGroup extends AbstractControl { constructor( - public controls: {[key: string]: AbstractControl}, validator: ValidatorFn = null, - asyncValidator: AsyncValidatorFn = null) { - super(validator, asyncValidator); + public controls: {[key: string]: AbstractControl}, validator?: ValidatorFn|null, + asyncValidator?: AsyncValidatorFn|null) { + super(validator || null, asyncValidator || null); this._initObservables(); this._setUpControls(); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); @@ -1137,9 +1136,9 @@ export class FormGroup extends AbstractControl { */ export class FormArray extends AbstractControl { constructor( - public controls: AbstractControl[], validator: ValidatorFn = null, - asyncValidator: AsyncValidatorFn = null) { - super(validator, asyncValidator); + public controls: AbstractControl[], validator?: ValidatorFn|null, + asyncValidator?: AsyncValidatorFn|null) { + super(validator || null, asyncValidator || null); this._initObservables(); this._setUpControls(); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); diff --git a/packages/forms/src/validators.ts b/packages/forms/src/validators.ts index 78cbee570f..de355fcbd4 100644 --- a/packages/forms/src/validators.ts +++ b/packages/forms/src/validators.ts @@ -143,9 +143,11 @@ export class Validators { * Compose multiple validators into a single function that returns the union * of the individual error maps. */ - static compose(validators: ValidatorFn[]): ValidatorFn { + static compose(validators: null): null; + static compose(validators: (ValidatorFn|null|undefined)[]): ValidatorFn|null; + static compose(validators: (ValidatorFn|null|undefined)[]|null): ValidatorFn|null { if (!validators) return null; - const presentValidators = validators.filter(isPresent); + const presentValidators: ValidatorFn[] = validators.filter(isPresent) as any; if (presentValidators.length == 0) return null; return function(control: AbstractControl) { @@ -153,9 +155,9 @@ export class Validators { }; } - static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn { + static composeAsync(validators: (AsyncValidatorFn|null)[]): AsyncValidatorFn|null { if (!validators) return null; - const presentValidators = validators.filter(isPresent); + const presentValidators: AsyncValidatorFn[] = validators.filter(isPresent) as any; if (presentValidators.length == 0) return null; return function(control: AbstractControl) { @@ -188,7 +190,7 @@ function _executeAsyncValidators(control: AbstractControl, validators: AsyncVali function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null { const res: {[key: string]: any} = arrayOfErrors.reduce((res: ValidationErrors | null, errors: ValidationErrors | null) => { - return errors != null ? {...res, ...errors} : res; + return errors != null ? {...res !, ...errors} : res !; }, {}); return Object.keys(res).length === 0 ? null : res; } diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index 642886c991..00001a6fff 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -28,7 +28,7 @@ class CustomValidatorDirective implements Validator { function asyncValidator(expected: any, timeout = 0) { return (c: AbstractControl): any => { - let resolve: (result: any) => void; + let resolve: (result: any) => void = undefined !; const promise = new Promise(res => { resolve = res; }); const res = c.value != expected ? {'async': true} : null; if (timeout == 0) { @@ -44,7 +44,7 @@ export function main() { describe('Form Directives', () => { let defaultAccessor: DefaultValueAccessor; - beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null, null); }); + beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null !, null !, null !); }); describe('shared', () => { describe('selectValueAccessor', () => { @@ -59,42 +59,42 @@ export function main() { () => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); }); it('should return checkbox accessor when provided', () => { - const checkboxAccessor = new CheckboxControlValueAccessor(null, null); + const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); expect(selectValueAccessor(dir, [ defaultAccessor, checkboxAccessor ])).toEqual(checkboxAccessor); }); it('should return select accessor when provided', () => { - const selectAccessor = new SelectControlValueAccessor(null, null); + const selectAccessor = new SelectControlValueAccessor(null !, null !); expect(selectValueAccessor(dir, [ defaultAccessor, selectAccessor ])).toEqual(selectAccessor); }); it('should return select multiple accessor when provided', () => { - const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null, null); + const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null !, null !); expect(selectValueAccessor(dir, [ defaultAccessor, selectMultipleAccessor ])).toEqual(selectMultipleAccessor); }); it('should throw when more than one build-in accessor is provided', () => { - const checkboxAccessor = new CheckboxControlValueAccessor(null, null); - const selectAccessor = new SelectControlValueAccessor(null, null); + const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); + const selectAccessor = new SelectControlValueAccessor(null !, null !); expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError(); }); it('should return custom accessor when provided', () => { const customAccessor = new SpyValueAccessor(); - const checkboxAccessor = new CheckboxControlValueAccessor(null, null); + const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !); expect(selectValueAccessor(dir, [defaultAccessor, customAccessor, checkboxAccessor])) .toEqual(customAccessor); }); it('should return custom accessor when provided with select multiple', () => { const customAccessor = new SpyValueAccessor(); - const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null, null); + const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null !, null !); expect(selectValueAccessor( dir, [defaultAccessor, customAccessor, selectMultipleAccessor])) .toEqual(customAccessor); @@ -110,13 +110,13 @@ export function main() { it('should compose functions', () => { const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true}); const dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true}); - const v = composeValidators([dummy1, dummy2]); + const v = composeValidators([dummy1, dummy2]) !; expect(v(new FormControl(''))).toEqual({'dummy1': true, 'dummy2': true}); }); it('should compose validator directives', () => { const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true}); - const v = composeValidators([dummy1, new CustomValidatorDirective()]); + const v = composeValidators([dummy1, new CustomValidatorDirective()]) !; expect(v(new FormControl(''))).toEqual({'dummy1': true, 'custom': true}); }); }); @@ -168,7 +168,7 @@ export function main() { describe('addControl', () => { it('should throw when no control found', () => { - const dir = new FormControlName(form, null, null, [defaultAccessor]); + const dir = new FormControlName(form, null !, null !, [defaultAccessor]); dir.name = 'invalidName'; expect(() => form.addControl(dir)) @@ -176,7 +176,7 @@ export function main() { }); it('should throw for a named control when no value accessor', () => { - const dir = new FormControlName(form, null, null, null); + const dir = new FormControlName(form, null !, null !, null !); dir.name = 'login'; expect(() => form.addControl(dir)) @@ -184,8 +184,8 @@ export function main() { }); it('should throw when no value accessor with path', () => { - const group = new FormGroupName(form, null, null); - const dir = new FormControlName(group, null, null, null); + const group = new FormGroupName(form, null !, null !); + const dir = new FormControlName(group, null !, null !, null !); group.name = 'passwords'; dir.name = 'password'; @@ -315,7 +315,7 @@ export function main() { personControlGroupDir = new NgModelGroup(form, [], []); personControlGroupDir.name = 'person'; - loginControlDir = new NgModel(personControlGroupDir, null, null, [defaultAccessor]); + loginControlDir = new NgModel(personControlGroupDir, null !, null !, [defaultAccessor]); loginControlDir.name = 'login'; loginControlDir.valueAccessor = new DummyControlValueAccessor(); }); @@ -534,7 +534,7 @@ export function main() { beforeEach(() => { ngModel = new NgModel( - null, [Validators.required], [asyncValidator('expected')], [defaultAccessor]); + null !, [Validators.required], [asyncValidator('expected')], [defaultAccessor]); ngModel.valueAccessor = new DummyControlValueAccessor(); control = ngModel.control; }); @@ -566,7 +566,7 @@ export function main() { }); it('should throw when no value accessor with named control', () => { - const namedDir = new NgModel(null, null, null, null); + const namedDir = new NgModel(null !, null !, null !, null !); namedDir.name = 'one'; expect(() => namedDir.ngOnChanges({})) @@ -574,7 +574,7 @@ export function main() { }); it('should throw when no value accessor with unnamed control', () => { - const unnamedDir = new NgModel(null, null, null, null); + const unnamedDir = new NgModel(null !, null !, null !, null !); expect(() => unnamedDir.ngOnChanges({})) .toThrowError( diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts index 66976e9e74..2b94b80ec7 100644 --- a/packages/forms/test/form_array_spec.ts +++ b/packages/forms/test/form_array_spec.ts @@ -15,7 +15,7 @@ import {Validators} from '../src/validators'; export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: AbstractControl) => { - let resolve: (result: any) => void; + let resolve: (result: any) => void = undefined !; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -89,7 +89,7 @@ export function main() { new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}), new FormArray([new FormControl('v4'), new FormControl('v5')]) ]); - a.at(0).get('c3').disable(); + a.at(0).get('c3') !.disable(); (a.at(1) as FormArray).at(1).disable(); expect(a.getRawValue()).toEqual([{'c2': 'v2', 'c3': 'v3'}, ['v4', 'v5']]); @@ -693,7 +693,7 @@ export function main() { describe('get', () => { it('should return null when path is null', () => { const g = new FormGroup({}); - expect(g.get(null)).toEqual(null); + expect(g.get(null !)).toEqual(null); }); it('should return null when path is empty', () => { @@ -712,23 +712,23 @@ export function main() { 'nested': new FormGroup({'two': new FormControl('222')}) }); - expect(g.get(['one']).value).toEqual('111'); - expect(g.get('one').value).toEqual('111'); - expect(g.get(['nested', 'two']).value).toEqual('222'); - expect(g.get('nested.two').value).toEqual('222'); + expect(g.get(['one']) !.value).toEqual('111'); + expect(g.get('one') !.value).toEqual('111'); + expect(g.get(['nested', 'two']) !.value).toEqual('222'); + expect(g.get('nested.two') !.value).toEqual('222'); }); it('should return an element of an array', () => { const g = new FormGroup({'array': new FormArray([new FormControl('111')])}); - expect(g.get(['array', 0]).value).toEqual('111'); + expect(g.get(['array', 0]) !.value).toEqual('111'); }); }); describe('asyncValidator', () => { it('should run the async validator', fakeAsync(() => { const c = new FormControl('value'); - const g = new FormArray([c], null, asyncValidator('expected')); + const g = new FormArray([c], null !, asyncValidator('expected')); expect(g.pending).toEqual(true); @@ -793,10 +793,10 @@ export function main() { }); expect(g.valid).toBe(false); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.valid).toBe(true); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.valid).toBe(false); }); @@ -805,36 +805,36 @@ export function main() { {nested: new FormArray([new FormControl('one')]), two: new FormControl('two')}); expect(g.value).toEqual({'nested': ['one'], 'two': 'two'}); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.value).toEqual({'two': 'two'}); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.value).toEqual({'nested': ['one'], 'two': 'two'}); }); it('should ignore disabled controls when determining dirtiness', () => { const g = new FormGroup({nested: a, two: new FormControl('two')}); - g.get(['nested', 0]).markAsDirty(); + g.get(['nested', 0]) !.markAsDirty(); expect(g.dirty).toBe(true); - g.get('nested').disable(); - expect(g.get('nested').dirty).toBe(true); + g.get('nested') !.disable(); + expect(g.get('nested') !.dirty).toBe(true); expect(g.dirty).toEqual(false); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.dirty).toEqual(true); }); it('should ignore disabled controls when determining touched state', () => { const g = new FormGroup({nested: a, two: new FormControl('two')}); - g.get(['nested', 0]).markAsTouched(); + g.get(['nested', 0]) !.markAsTouched(); expect(g.touched).toBe(true); - g.get('nested').disable(); - expect(g.get('nested').touched).toBe(true); + g.get('nested') !.disable(); + expect(g.get('nested') !.touched).toBe(true); expect(g.touched).toEqual(false); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.touched).toEqual(true); }); @@ -901,7 +901,7 @@ export function main() { }); it('should clear out async array errors when disabled', fakeAsync(() => { - const arr = new FormArray([new FormControl()], null, asyncValidator('expected')); + const arr = new FormArray([new FormControl()], null !, asyncValidator('expected')); tick(); expect(arr.errors).toEqual({'async': true}); @@ -914,7 +914,7 @@ export function main() { })); it('should re-populate async array errors when enabled from a child', fakeAsync(() => { - const arr = new FormArray([new FormControl()], null, asyncValidator('expected')); + const arr = new FormArray([new FormControl()], null !, asyncValidator('expected')); tick(); expect(arr.errors).toEqual({'async': true}); @@ -988,7 +988,7 @@ export function main() { }); it('should remove control if new control is null', () => { - a.setControl(0, null); + a.setControl(0, null !); expect(a.controls[0]).not.toBeDefined(); expect(a.value).toEqual([]); }); diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index e52c23bbac..4fa4d9f3a2 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -16,7 +16,7 @@ import {FormArray} from '../src/model'; export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: FormControl) => { - let resolve: (result: any) => void; + let resolve: (result: any) => void = undefined !; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -49,7 +49,7 @@ export function main() { describe('boxed values', () => { it('should support valid boxed values on creation', () => { - const c = new FormControl({value: 'some val', disabled: true}, null, null); + const c = new FormControl({value: 'some val', disabled: true}, null !, null !); expect(c.disabled).toBe(true); expect(c.value).toBe('some val'); expect(c.status).toBe('DISABLED'); @@ -63,13 +63,13 @@ export function main() { }); it('should not treat objects as boxed values if they have more than two props', () => { - const c = new FormControl({value: '', disabled: true, test: 'test'}, null, null); + const c = new FormControl({value: '', disabled: true, test: 'test'}, null !, null !); expect(c.value).toEqual({value: '', disabled: true, test: 'test'}); expect(c.disabled).toBe(false); }); it('should not treat objects as boxed values if disabled is missing', () => { - const c = new FormControl({value: '', test: 'test'}, null, null); + const c = new FormControl({value: '', test: 'test'}, null !, null !); expect(c.value).toEqual({value: '', test: 'test'}); expect(c.disabled).toBe(false); }); @@ -156,7 +156,7 @@ export function main() { describe('asyncValidator', () => { it('should run validator with the initial value', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidator('expected')); + const c = new FormControl('value', null !, asyncValidator('expected')); tick(); expect(c.valid).toEqual(false); @@ -164,7 +164,7 @@ export function main() { })); it('should support validators returning observables', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidatorReturningObservable); + const c = new FormControl('value', null !, asyncValidatorReturningObservable); tick(); expect(c.valid).toEqual(false); @@ -172,7 +172,7 @@ export function main() { })); it('should rerun the validator when the value changes', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidator('expected')); + const c = new FormControl('value', null !, asyncValidator('expected')); c.setValue('expected'); tick(); @@ -193,7 +193,7 @@ export function main() { })); it('should mark the control as pending while running the async validation', fakeAsync(() => { - const c = new FormControl('', null, asyncValidator('expected')); + const c = new FormControl('', null !, asyncValidator('expected')); expect(c.pending).toEqual(true); @@ -204,7 +204,7 @@ export function main() { it('should only use the latest async validation run', fakeAsync(() => { const c = new FormControl( - '', null, asyncValidator('expected', {'long': 200, 'expected': 100})); + '', null !, asyncValidator('expected', {'long': 200, 'expected': 100})); c.setValue('long'); c.setValue('expected'); @@ -216,14 +216,14 @@ export function main() { it('should support arrays of async validator functions if passed', fakeAsync(() => { const c = - new FormControl('value', null, [asyncValidator('expected'), otherAsyncValidator]); + new FormControl('value', null !, [asyncValidator('expected'), otherAsyncValidator]); tick(); expect(c.errors).toEqual({'async': true, 'other': true}); })); it('should add single async validator', fakeAsync(() => { - const c = new FormControl('value', null); + const c = new FormControl('value', null !); c.setAsyncValidators(asyncValidator('expected')); expect(c.asyncValidator).not.toEqual(null); @@ -235,7 +235,7 @@ export function main() { })); it('should add async validator from array', fakeAsync(() => { - const c = new FormControl('value', null); + const c = new FormControl('value', null !); c.setAsyncValidators([asyncValidator('expected')]); expect(c.asyncValidator).not.toEqual(null); @@ -634,8 +634,7 @@ export function main() { tick(); expect(log).toEqual([ - '' + - 'value: \'\'', + 'value: \'\'', 'status: \'INVALID\'', 'value: \'nonEmpty\'', 'status: \'PENDING\'', @@ -935,7 +934,7 @@ export function main() { }); it('should clear out async errors when disabled', fakeAsync(() => { - const c = new FormControl('', null, asyncValidator('expected')); + const c = new FormControl('', null !, asyncValidator('expected')); tick(); expect(c.errors).toEqual({'async': true}); diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index dd2d222d10..aa62b78c9c 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -15,7 +15,7 @@ import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@a export function main() { function asyncValidator(expected: string, timeouts = {}) { return (c: AbstractControl) => { - let resolve: (result: any) => void; + let resolve: (result: any) => void = undefined !; const promise = new Promise(res => { resolve = res; }); const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const res = c.value != expected ? {'async': true} : null; @@ -70,7 +70,7 @@ export function main() { 'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}), 'array': new FormArray([new FormControl('v4'), new FormControl('v5')]) }); - fg.get('group').get('c3').disable(); + fg.get('group') !.get('c3') !.disable(); (fg.get('array') as FormArray).at(1).disable(); expect(fg.getRawValue()) @@ -690,7 +690,7 @@ export function main() { describe('asyncValidator', () => { it('should run the async validator', fakeAsync(() => { const c = new FormControl('value'); - const g = new FormGroup({'one': c}, null, asyncValidator('expected')); + const g = new FormGroup({'one': c}, null !, asyncValidator('expected')); expect(g.pending).toEqual(true); @@ -701,7 +701,7 @@ export function main() { })); it('should set the parent group\'s status to pending', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidator('expected')); + const c = new FormControl('value', null !, asyncValidator('expected')); const g = new FormGroup({'one': c}); expect(g.pending).toEqual(true); @@ -713,13 +713,13 @@ export function main() { it('should run the parent group\'s async validator when children are pending', fakeAsync(() => { - const c = new FormControl('value', null, asyncValidator('expected')); - const g = new FormGroup({'one': c}, null, asyncValidator('expected')); + const c = new FormControl('value', null !, asyncValidator('expected')); + const g = new FormGroup({'one': c}, null !, asyncValidator('expected')); tick(1); expect(g.errors).toEqual({'async': true}); - expect(g.get('one').errors).toEqual({'async': true}); + expect(g.get('one') !.errors).toEqual({'async': true}); })); }); @@ -772,10 +772,10 @@ export function main() { }); expect(g.valid).toBe(false); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.valid).toBe(true); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.valid).toBe(false); }); @@ -784,10 +784,10 @@ export function main() { {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); expect(g.value).toEqual({'nested': {'one': 'one'}, 'two': 'two'}); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.value).toEqual({'two': 'two'}); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.value).toEqual({'nested': {'one': 'one'}, 'two': 'two'}); }); @@ -795,13 +795,13 @@ export function main() { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one'), two: new FormControl('two')})}); - g.get('nested.two').disable(); + g.get('nested.two') !.disable(); expect(g.value).toEqual({nested: {one: 'one'}}); - g.get('nested').disable(); + g.get('nested') !.disable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); }); @@ -809,38 +809,38 @@ export function main() { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one'), two: new FormControl('two')})}); - g.get('nested.two').disable(); + g.get('nested.two') !.disable(); expect(g.value).toEqual({nested: {one: 'one'}}); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.value).toEqual({nested: {one: 'one', two: 'two'}}); }); it('should ignore disabled controls when determining dirtiness', () => { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); - g.get('nested.one').markAsDirty(); + g.get('nested.one') !.markAsDirty(); expect(g.dirty).toBe(true); - g.get('nested').disable(); - expect(g.get('nested').dirty).toBe(true); + g.get('nested') !.disable(); + expect(g.get('nested') !.dirty).toBe(true); expect(g.dirty).toEqual(false); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.dirty).toEqual(true); }); it('should ignore disabled controls when determining touched state', () => { const g = new FormGroup( {nested: new FormGroup({one: new FormControl('one')}), two: new FormControl('two')}); - g.get('nested.one').markAsTouched(); + g.get('nested.one') !.markAsTouched(); expect(g.touched).toBe(true); - g.get('nested').disable(); - expect(g.get('nested').touched).toBe(true); + g.get('nested') !.disable(); + expect(g.get('nested') !.touched).toBe(true); expect(g.touched).toEqual(false); - g.get('nested').enable(); + g.get('nested') !.enable(); expect(g.touched).toEqual(true); }); @@ -907,7 +907,8 @@ export function main() { }); it('should clear out async group errors when disabled', fakeAsync(() => { - const g = new FormGroup({'one': new FormControl()}, null, asyncValidator('expected')); + const g = + new FormGroup({'one': new FormControl()}, null !, asyncValidator('expected')); tick(); expect(g.errors).toEqual({'async': true}); @@ -920,7 +921,8 @@ export function main() { })); it('should re-populate async group errors when enabled from a child', fakeAsync(() => { - const g = new FormGroup({'one': new FormControl()}, null, asyncValidator('expected')); + const g = + new FormGroup({'one': new FormControl()}, null !, asyncValidator('expected')); tick(); expect(g.errors).toEqual({'async': true}); @@ -1028,7 +1030,7 @@ export function main() { }); it('should remove control if new control is null', () => { - g.setControl('one', null); + g.setControl('one', null !); expect(g.controls['one']).not.toBeDefined(); expect(g.value).toEqual({}); }); diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts index 8d5ba36e43..1dfd3c63d3 100644 --- a/packages/forms/test/reactive_integration_spec.ts +++ b/packages/forms/test/reactive_integration_spec.ts @@ -166,7 +166,7 @@ export function main() { }); fixture.componentInstance.form = form; fixture.detectChanges(); - expect(form.get('login').errors).toEqual({required: true}); + expect(form.get('login') !.errors).toEqual({required: true}); const newForm = new FormGroup({ 'login': new FormControl(''), @@ -177,7 +177,7 @@ export function main() { fixture.componentInstance.form = newForm; fixture.detectChanges(); - expect(newForm.get('login').errors).toEqual({required: true}); + expect(newForm.get('login') !.errors).toEqual({required: true}); }); it('should pick up dir validators from nested form groups', () => { @@ -188,7 +188,7 @@ export function main() { }); fixture.componentInstance.form = form; fixture.detectChanges(); - expect(form.get('signin').valid).toBe(false); + expect(form.get('signin') !.valid).toBe(false); const newForm = new FormGroup({ 'signin': @@ -197,7 +197,7 @@ export function main() { fixture.componentInstance.form = newForm; fixture.detectChanges(); - expect(form.get('signin').valid).toBe(false); + expect(form.get('signin') !.valid).toBe(false); }); it('should strip named controls that are not found', () => { @@ -373,7 +373,7 @@ export function main() { it('should throw an error if compareWith is not a function', () => { const fixture = initTest(FormControlSelectWithCompareFn); - fixture.componentInstance.compareFn = null; + fixture.componentInstance.compareFn = null !; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -412,7 +412,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(FormControlSelectMultipleWithCompareFn); - fixture.componentInstance.compareFn = null; + fixture.componentInstance.compareFn = null !; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -623,7 +623,7 @@ export function main() { it('should emit ngSubmit event with the original submit event on submit', () => { const fixture = initTest(FormGroupComp); fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')}); - fixture.componentInstance.event = null; + fixture.componentInstance.event = null !; fixture.detectChanges(); const formEl = fixture.debugElement.query(By.css('form')).nativeElement; @@ -739,7 +739,7 @@ export function main() { it('should work with single fields and async validators', fakeAsync(() => { const fixture = initTest(FormControlComp); - const control = new FormControl('', null, uniqLoginAsyncValidator('good')); + const control = new FormControl('', null !, uniqLoginAsyncValidator('good')); fixture.debugElement.componentInstance.control = control; fixture.detectChanges(); @@ -995,10 +995,10 @@ export function main() { fixture.detectChanges(); // view -> model - expect(form.get('food').value).toEqual('chicken'); + expect(form.get('food') !.value).toEqual('chicken'); expect(inputs[1].nativeElement.checked).toEqual(false); - form.get('food').setValue('fish'); + form.get('food') !.setValue('fish'); fixture.detectChanges(); // programmatic change -> view @@ -1039,16 +1039,16 @@ export function main() { fixture.componentInstance.form = form; fixture.detectChanges(); - form.get('food').setValue(null); + form.get('food') !.setValue(null); fixture.detectChanges(); const inputs = fixture.debugElement.queryAll(By.css('input')); expect(inputs[0].nativeElement.checked).toEqual(false); - form.get('food').setValue('chicken'); + form.get('food') !.setValue('chicken'); fixture.detectChanges(); - form.get('food').setValue(undefined); + form.get('food') !.setValue(undefined); fixture.detectChanges(); expect(inputs[0].nativeElement.checked).toEqual(false); }); @@ -1139,8 +1139,8 @@ export function main() { fixture.detectChanges(); // view -> model - expect(form.get('food').value).toEqual('chicken'); - expect(form.get('nested.food').value).toEqual('fish'); + expect(form.get('food') !.value).toEqual('chicken'); + expect(form.get('nested.food') !.value).toEqual('fish'); expect(inputs[1].nativeElement.checked).toEqual(false); expect(inputs[2].nativeElement.checked).toEqual(false); @@ -1161,7 +1161,7 @@ export function main() { expect(inputs[2].nativeElement.disabled).toEqual(false); expect(inputs[3].nativeElement.disabled).toEqual(false); - form.get('food').disable(); + form.get('food') !.disable(); expect(inputs[0].nativeElement.disabled).toEqual(true); expect(inputs[1].nativeElement.disabled).toEqual(true); expect(inputs[2].nativeElement.disabled).toEqual(false); @@ -1267,9 +1267,9 @@ export function main() { expect(form.value).toEqual({'login': 'bb'}); // custom validator - expect(form.get('login').errors).toEqual({'err': true}); + expect(form.get('login') !.errors).toEqual({'err': true}); form.setValue({login: 'expected'}); - expect(form.get('login').errors).toEqual(null); + expect(form.get('login') !.errors).toEqual(null); }); it('should support non builtin input elements that fire a change event without a \'target\' property', @@ -1295,7 +1295,7 @@ export function main() { }); fixture.detectChanges(); expect(fixture.componentInstance.form.status).toEqual('DISABLED'); - expect(fixture.componentInstance.form.get('login').status).toEqual('DISABLED'); + expect(fixture.componentInstance.form.get('login') !.status).toEqual('DISABLED'); }); it('should support custom accessors without setDisabledState - formControlDirective', @@ -1539,9 +1539,9 @@ export function main() { .toEqual(pattern.nativeElement.getAttribute('pattern')); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null; - fixture.componentInstance.maxLen = null; - fixture.componentInstance.pattern = null; + fixture.componentInstance.minLen = null !; + fixture.componentInstance.maxLen = null !; + fixture.componentInstance.pattern = null !; fixture.detectChanges(); expect(form.hasError('required', ['login'])).toEqual(false); @@ -1581,9 +1581,9 @@ export function main() { fixture.detectChanges(); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null; - fixture.componentInstance.maxLen = null; - fixture.componentInstance.pattern = null; + fixture.componentInstance.minLen = null !; + fixture.componentInstance.maxLen = null !; + fixture.componentInstance.pattern = null !; fixture.detectChanges(); expect(newForm.hasError('required', ['login'])).toEqual(false); @@ -1681,7 +1681,7 @@ export function main() { const fixture = initTest(FormControlComp); const resultArr: number[] = []; fixture.componentInstance.control = - new FormControl('', null, observableValidator(resultArr)); + new FormControl('', null !, observableValidator(resultArr)); fixture.detectChanges(); tick(100); diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts index 4ae9c7cd08..87fd77bdf8 100644 --- a/packages/forms/test/template_integration_spec.ts +++ b/packages/forms/test/template_integration_spec.ts @@ -107,9 +107,9 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name').value).toEqual({first: 'Nancy', last: 'Drew'}); - expect(form.control.get('name.first').value).toEqual('Nancy'); - expect(form.control.get('email').value).toEqual('some email'); + expect(form.control.get('name') !.value).toEqual({first: 'Nancy', last: 'Drew'}); + expect(form.control.get('name.first') !.value).toEqual('Nancy'); + expect(form.control.get('email') !.value).toEqual('some email'); })); it('should remove controls and control groups from form control model', fakeAsync(() => { @@ -121,7 +121,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('email').value).toEqual('some email'); + expect(form.control.get('email') !.value).toEqual('some email'); expect(form.value).toEqual({name: {first: 'Nancy'}, email: 'some email'}); // should remove individual control successfully @@ -132,8 +132,8 @@ export function main() { expect(form.control.get('email')).toBe(null); expect(form.value).toEqual({name: {first: 'Nancy'}}); - expect(form.control.get('name').value).toEqual({first: 'Nancy'}); - expect(form.control.get('name.first').value).toEqual('Nancy'); + expect(form.control.get('name') !.value).toEqual({first: 'Nancy'}); + expect(form.control.get('name.first') !.value).toEqual('Nancy'); // should remove form group successfully fixture.componentInstance.groupShowing = false; @@ -228,7 +228,7 @@ export function main() { it('should not create a template-driven form when ngNoForm is used', () => { const fixture = initTest(NgNoFormComp); fixture.detectChanges(); - expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0); + expect(fixture.debugElement.children[0].providerTokens !.length).toEqual(0); }); it('should not add novalidate when ngNoForm is used', () => { @@ -282,7 +282,7 @@ export function main() { describe('submit and reset events', () => { it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => { const fixture = initTest(NgModelForm); - fixture.componentInstance.event = null; + fixture.componentInstance.event = null !; const form = fixture.debugElement.query(By.css('form')); dispatchEvent(form.nativeElement, 'submit'); @@ -355,11 +355,11 @@ export function main() { expect(form.valid).toEqual(true); expect(form.value).toEqual({}); - let formValidity: string; - let formValue: Object; + let formValidity: string = undefined !; + let formValue: Object = undefined !; - form.statusChanges.subscribe((status: string) => formValidity = status); - form.valueChanges.subscribe((value: string) => formValue = value); + form.statusChanges !.subscribe((status: string) => formValidity = status); + form.valueChanges !.subscribe((value: string) => formValue = value); tick(); @@ -374,8 +374,8 @@ export function main() { fixture.detectChanges(); tick(); - form.get('name').valueChanges.subscribe( - () => { expect(form.get('name').dirty).toBe(true); }); + form.get('name') !.valueChanges.subscribe( + () => { expect(form.get('name') !.dirty).toBe(true); }); const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; inputEl.value = 'newValue'; @@ -396,10 +396,10 @@ export function main() { inputEl.value = 'newValue'; dispatchEvent(inputEl, 'input'); - expect(form.get('name').pristine).toBe(false); + expect(form.get('name') !.pristine).toBe(false); - form.get('name').valueChanges.subscribe( - () => { expect(form.get('name').pristine).toBe(true); }); + form.get('name') !.valueChanges.subscribe( + () => { expect(form.get('name') !.pristine).toBe(true); }); dispatchEvent(formEl, 'reset'); })); @@ -418,7 +418,7 @@ export function main() { const form = fixture.debugElement.children[0].injector.get(NgForm); expect(form.value).toEqual({name: {first: '', last: 'Drew'}, email: 'some email'}); expect(form.valid).toBe(false); - expect(form.control.get('name.first').disabled).toBe(false); + expect(form.control.get('name.first') !.disabled).toBe(false); fixture.componentInstance.isDisabled = true; fixture.detectChanges(); @@ -426,7 +426,7 @@ export function main() { expect(form.value).toEqual({name: {last: 'Drew'}, email: 'some email'}); expect(form.valid).toBe(true); - expect(form.control.get('name.first').disabled).toBe(true); + expect(form.control.get('name.first') !.disabled).toBe(true); })); it('should add disabled attribute in the UI if disable() is called programmatically', @@ -438,7 +438,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - form.control.get('name.first').disable(); + form.control.get('name.first') !.disable(); fixture.detectChanges(); tick(); @@ -455,7 +455,7 @@ export function main() { fixture.detectChanges(); fixture.whenStable().then(() => { const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name').disabled).toBe(true); + expect(form.control.get('name') !.disabled).toBe(true); const customInput = fixture.debugElement.query(By.css('[name="custom"]')); expect(customInput.nativeElement.disabled).toEqual(true); @@ -477,7 +477,7 @@ export function main() { fixture.detectChanges(); tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - expect(form.control.get('name').disabled).toBe(true); + expect(form.control.get('name') !.disabled).toBe(true); const input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.disabled).toEqual(true); @@ -495,7 +495,7 @@ export function main() { tick(); const form = fixture.debugElement.children[0].injector.get(NgForm); - form.control.get('food').disable(); + form.control.get('food') !.disable(); tick(); const inputs = fixture.debugElement.queryAll(By.css('input')); @@ -620,7 +620,7 @@ export function main() { fixture.detectChanges(); tick(); - fixture.componentInstance.food = null; + fixture.componentInstance.food = null !; fixture.detectChanges(); tick(); @@ -632,7 +632,7 @@ export function main() { fixture.detectChanges(); tick(); - fixture.componentInstance.food = undefined; + fixture.componentInstance.food = undefined !; fixture.detectChanges(); tick(); expect(inputs[0].nativeElement.checked).toEqual(false); @@ -724,7 +724,7 @@ export function main() { const fixture = initTest(NgModelSelectWithNullForm); const comp = fixture.componentInstance; comp.cities = [{'name': 'SF'}, {'name': 'NYC'}]; - comp.selectedCity = null; + comp.selectedCity = null !; fixture.detectChanges(); const select = fixture.debugElement.query(By.css('select')); @@ -745,7 +745,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(NgModelSelectWithCustomCompareFnForm); const comp = fixture.componentInstance; - comp.compareFn = null; + comp.compareFn = null !; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -833,7 +833,7 @@ export function main() { it('should throw an error when compareWith is not a function', () => { const fixture = initTest(NgModelSelectMultipleWithCustomCompareFnForm); const comp = fixture.componentInstance; - comp.compareFn = null; + comp.compareFn = null !; expect(() => fixture.detectChanges()) .toThrowError(/compareWith must be a function, but received null/); }); @@ -885,7 +885,7 @@ export function main() { tick(); const control = - fixture.debugElement.children[0].injector.get(NgForm).control.get('checkbox'); + fixture.debugElement.children[0].injector.get(NgForm).control.get('checkbox') !; const input = fixture.debugElement.query(By.css('input')); expect(input.nativeElement.checked).toBe(false); @@ -921,7 +921,7 @@ export function main() { tick(); const control = - fixture.debugElement.children[0].injector.get(NgForm).control.get('email'); + fixture.debugElement.children[0].injector.get(NgForm).control.get('email') !; const input = fixture.debugElement.query(By.css('input')); expect(control.hasError('email')).toBe(false); @@ -1114,9 +1114,9 @@ export function main() { .toEqual(pattern.nativeElement.getAttribute('pattern')); fixture.componentInstance.required = false; - fixture.componentInstance.minLen = null; - fixture.componentInstance.maxLen = null; - fixture.componentInstance.pattern = null; + fixture.componentInstance.minLen = null !; + fixture.componentInstance.maxLen = null !; + fixture.componentInstance.pattern = null !; fixture.detectChanges(); expect(form.control.hasError('required', ['required'])).toEqual(false); diff --git a/packages/forms/test/validators_spec.ts b/packages/forms/test/validators_spec.ts index da1f6824dd..b44c8ce7c0 100644 --- a/packages/forms/test/validators_spec.ts +++ b/packages/forms/test/validators_spec.ts @@ -179,33 +179,33 @@ export function main() { }); it('should not error on "null" pattern', - () => expect(Validators.pattern(null)(new FormControl('aaAA'))).toBeNull()); + () => expect(Validators.pattern(null !)(new FormControl('aaAA'))).toBeNull()); it('should not error on "undefined" pattern', - () => expect(Validators.pattern(undefined)(new FormControl('aaAA'))).toBeNull()); + () => expect(Validators.pattern(undefined !)(new FormControl('aaAA'))).toBeNull()); }); describe('compose', () => { it('should return null when given null', - () => { expect(Validators.compose(null)).toBe(null); }); + () => { expect(Validators.compose(null !)).toBe(null); }); it('should collect errors from all the validators', () => { - const c = Validators.compose([validator('a', true), validator('b', true)]); + const c = Validators.compose([validator('a', true), validator('b', true)]) !; expect(c(new FormControl(''))).toEqual({'a': true, 'b': true}); }); it('should run validators left to right', () => { - const c = Validators.compose([validator('a', 1), validator('a', 2)]); + const c = Validators.compose([validator('a', 1), validator('a', 2)]) !; expect(c(new FormControl(''))).toEqual({'a': 2}); }); it('should return null when no errors', () => { - const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]); + const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]) !; expect(c(new FormControl(''))).toBeNull(); }); it('should ignore nulls', () => { - const c = Validators.compose([null, Validators.required]); + const c = Validators.compose([null !, Validators.required]) !; expect(c(new FormControl(''))).toEqual({'required': true}); }); }); @@ -221,13 +221,13 @@ export function main() { } it('should return null when given null', - () => { expect(Validators.composeAsync(null)).toBeNull(); }); + () => { expect(Validators.composeAsync(null !)).toBeNull(); }); it('should collect errors from all the validators', fakeAsync(() => { const v = Validators.composeAsync( - [promiseValidator({'one': true}), promiseValidator({'two': true})]); + [promiseValidator({'one': true}), promiseValidator({'two': true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -236,10 +236,10 @@ export function main() { })); it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => { - const v = Validators.composeAsync( - [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]); + const v = Validators.composeAsync([normalizeAsyncValidator( + new AsyncValidatorDirective('expected', {'one': true}))]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -248,9 +248,9 @@ export function main() { })); it('should return null when no errors', fakeAsync(() => { - const v = Validators.composeAsync([promiseValidator({'one': true})]); + const v = Validators.composeAsync([promiseValidator({'one': true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('expected'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -259,9 +259,9 @@ export function main() { })); it('should ignore nulls', fakeAsync(() => { - const v = Validators.composeAsync([promiseValidator({'one': true}), null]); + const v = Validators.composeAsync([promiseValidator({'one': true}), null !]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); tick(); @@ -279,13 +279,13 @@ export function main() { } it('should return null when given null', - () => { expect(Validators.composeAsync(null)).toBeNull(); }); + () => { expect(Validators.composeAsync(null !)).toBeNull(); }); it('should collect errors from all the validators', () => { const v = Validators.composeAsync( - [observableValidator({'one': true}), observableValidator({'two': true})]); + [observableValidator({'one': true}), observableValidator({'two': true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -294,19 +294,19 @@ export function main() { it('should normalize and evaluate async validator-directives correctly', () => { const v = Validators.composeAsync( - [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]); + [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) - .subscribe((errors: {[key: string]: any}) => errorMap = errors); + .subscribe((errors: {[key: string]: any}) => errorMap = errors) !; expect(errorMap).toEqual({'one': true}); }); it('should return null when no errors', () => { - const v = Validators.composeAsync([observableValidator({'one': true})]); + const v = Validators.composeAsync([observableValidator({'one': true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('expected'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -314,9 +314,9 @@ export function main() { }); it('should ignore nulls', () => { - const v = Validators.composeAsync([observableValidator({'one': true}), null]); + const v = Validators.composeAsync([observableValidator({'one': true}), null !]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); @@ -329,9 +329,9 @@ export function main() { } const v = Validators.composeAsync( - [getTimerObs(100, {one: true}), getTimerObs(200, {two: true})]); + [getTimerObs(100, {one: true}), getTimerObs(200, {two: true})]) !; - let errorMap: {[key: string]: any}; + let errorMap: {[key: string]: any} = undefined !; first.call(v(new FormControl('invalid'))) .subscribe((errors: {[key: string]: any}) => errorMap = errors); diff --git a/packages/forms/tsconfig-build.json b/packages/forms/tsconfig-build.json index 74d0fa0488..8cfa4196f1 100644 --- a/packages/forms/tsconfig-build.json +++ b/packages/forms/tsconfig-build.json @@ -4,6 +4,7 @@ "declaration": true, "stripInternal": true, "experimentalDecorators": true, + "strictNullChecks": true, "module": "es2015", "moduleResolution": "node", "outDir": "../../dist/packages/forms", diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts index 889a1fc264..fdfc00298b 100644 --- a/tools/public_api_guard/forms/forms.d.ts +++ b/tools/public_api_guard/forms/forms.d.ts @@ -1,6 +1,6 @@ /** @stable */ export declare abstract class AbstractControl { - asyncValidator: AsyncValidatorFn; + asyncValidator: AsyncValidatorFn | null; readonly dirty: boolean; readonly disabled: boolean; readonly enabled: boolean; @@ -15,10 +15,10 @@ export declare abstract class AbstractControl { readonly touched: boolean; readonly untouched: boolean; readonly valid: boolean; - validator: ValidatorFn; + validator: ValidatorFn | null; readonly value: any; readonly valueChanges: Observable; - constructor(validator: ValidatorFn, asyncValidator: AsyncValidatorFn); + constructor(validator: ValidatorFn | null, asyncValidator: AsyncValidatorFn | null); clearAsyncValidators(): void; clearValidators(): void; disable({onlySelf, emitEvent}?: { @@ -29,7 +29,7 @@ export declare abstract class AbstractControl { onlySelf?: boolean; emitEvent?: boolean; }): void; - get(path: Array | string): AbstractControl; + get(path: Array | string): AbstractControl | null; getError(errorCode: string, path?: string[]): any; hasError(errorCode: string, path?: string[]): boolean; markAsDirty({onlySelf}?: { @@ -54,7 +54,7 @@ export declare abstract class AbstractControl { emitEvent?: boolean; }): void; setParent(parent: FormGroup | FormArray): void; - setValidators(newValidator: ValidatorFn | ValidatorFn[]): void; + setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void; abstract setValue(value: any, options?: Object): void; updateValueAndValidity({onlySelf, emitEvent}?: { onlySelf?: boolean; @@ -64,21 +64,21 @@ export declare abstract class AbstractControl { /** @stable */ export declare abstract class AbstractControlDirective { - readonly control: AbstractControl; - readonly dirty: boolean; - readonly disabled: boolean; - readonly enabled: boolean; + readonly abstract control: AbstractControl | null; + readonly dirty: boolean | null; + readonly disabled: boolean | null; + readonly enabled: boolean | null; readonly errors: ValidationErrors | null; - readonly invalid: boolean; - readonly path: string[]; - readonly pending: boolean; - readonly pristine: boolean; - readonly statusChanges: Observable; - readonly touched: boolean; - readonly untouched: boolean; - readonly valid: boolean; + readonly invalid: boolean | null; + readonly path: string[] | null; + readonly pending: boolean | null; + readonly pristine: boolean | null; + readonly statusChanges: Observable | null; + readonly touched: boolean | null; + readonly untouched: boolean | null; + readonly valid: boolean | null; readonly value: any; - readonly valueChanges: Observable; + readonly valueChanges: Observable | null; getError(errorCode: string, path?: string[]): any; hasError(errorCode: string, path?: string[]): boolean; reset(value?: any): void; @@ -86,11 +86,11 @@ export declare abstract class AbstractControlDirective { /** @stable */ export declare class AbstractFormGroupDirective extends ControlContainer implements OnInit, OnDestroy { - readonly asyncValidator: AsyncValidatorFn; + readonly asyncValidator: AsyncValidatorFn | null; readonly control: FormGroup; - readonly formDirective: Form; + readonly formDirective: Form | null; readonly path: string[]; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; ngOnDestroy(): void; ngOnInit(): void; } @@ -125,10 +125,10 @@ export declare class CheckboxRequiredValidator extends RequiredValidator { export declare const COMPOSITION_BUFFER_MODE: InjectionToken; /** @stable */ -export declare class ControlContainer extends AbstractControlDirective { - readonly formDirective: Form; +export declare abstract class ControlContainer extends AbstractControlDirective { + readonly formDirective: Form | null; name: string; - readonly path: string[]; + readonly path: string[] | null; } /** @stable */ @@ -175,7 +175,7 @@ export interface Form { export declare class FormArray extends AbstractControl { controls: AbstractControl[]; readonly length: number; - constructor(controls: AbstractControl[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn); + constructor(controls: AbstractControl[], validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null); at(index: number): AbstractControl; getRawValue(): any[]; insert(index: number, control: AbstractControl): void; @@ -198,12 +198,12 @@ export declare class FormArray extends AbstractControl { /** @stable */ export declare class FormArrayName extends ControlContainer implements OnInit, OnDestroy { - readonly asyncValidator: AsyncValidatorFn; + readonly asyncValidator: AsyncValidatorFn | null; readonly control: FormArray; - readonly formDirective: FormGroupDirective; + readonly formDirective: FormGroupDirective | null; name: string; readonly path: string[]; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; constructor(parent: ControlContainer, validators: any[], asyncValidators: any[]); ngOnDestroy(): void; ngOnInit(): void; @@ -211,18 +211,18 @@ export declare class FormArrayName extends ControlContainer implements OnInit, O /** @stable */ export declare class FormBuilder { - array(controlsConfig: any[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn): FormArray; - control(formState: Object, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]): FormControl; + array(controlsConfig: any[], validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null): FormArray; + control(formState: Object, validator?: ValidatorFn | ValidatorFn[] | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl; group(controlsConfig: { [key: string]: any; }, extra?: { [key: string]: any; - }): FormGroup; + } | null): FormGroup; } /** @stable */ export declare class FormControl extends AbstractControl { - constructor(formState?: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]); + constructor(formState?: any, validator?: ValidatorFn | ValidatorFn[] | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null); patchValue(value: any, options?: { onlySelf?: boolean; emitEvent?: boolean; @@ -245,14 +245,14 @@ export declare class FormControl extends AbstractControl { /** @stable */ export declare class FormControlDirective extends NgControl implements OnChanges { - readonly asyncValidator: AsyncValidatorFn; + readonly asyncValidator: AsyncValidatorFn | null; readonly control: FormControl; form: FormControl; isDisabled: boolean; model: any; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; viewModel: any; constructor(validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; @@ -269,7 +269,7 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD name: string; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; @@ -283,7 +283,7 @@ export declare class FormGroup extends AbstractControl { }; constructor(controls: { [key: string]: AbstractControl; - }, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn); + }, validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null); addControl(name: string, control: AbstractControl): void; contains(controlName: string): boolean; getRawValue(): any; @@ -371,10 +371,10 @@ export declare const NG_VALUE_ACCESSOR: InjectionToken; /** @stable */ export declare abstract class NgControl extends AbstractControlDirective { - readonly asyncValidator: AsyncValidatorFn; - name: string; - readonly validator: ValidatorFn; - valueAccessor: ControlValueAccessor; + readonly asyncValidator: AsyncValidatorFn | null; + name: string | null; + readonly validator: ValidatorFn | null; + valueAccessor: ControlValueAccessor | null; abstract viewToModelUpdate(newValue: any): void; } @@ -417,7 +417,7 @@ export declare class NgForm extends ControlContainer implements Form { /** @stable */ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { - readonly asyncValidator: AsyncValidatorFn; + readonly asyncValidator: AsyncValidatorFn | null; readonly control: FormControl; readonly formDirective: any; isDisabled: boolean; @@ -429,7 +429,7 @@ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { }; readonly path: string[]; update: EventEmitter<{}>; - readonly validator: ValidatorFn; + readonly validator: ValidatorFn | null; viewModel: any; constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; @@ -532,8 +532,9 @@ export interface ValidatorFn { /** @stable */ export declare class Validators { - static compose(validators: ValidatorFn[]): ValidatorFn; - static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn; + static compose(validators: null): null; + static compose(validators: (ValidatorFn | null | undefined)[]): ValidatorFn | null; + static composeAsync(validators: (AsyncValidatorFn | null)[]): AsyncValidatorFn | null; static email(control: AbstractControl): ValidationErrors | null; static maxLength(maxLength: number): ValidatorFn; static minLength(minLength: number): ValidatorFn; From dfc81c3dab06dd45ad2fb9a27b29d127ada7272d Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Sat, 15 Apr 2017 00:04:34 -0700 Subject: [PATCH 0034/1039] feat(aio): sort search results by area/title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Display area names in all caps Exclude results with no title because they don’t show & can’t be clicked. Should find these and give their docs a title. --- .../search-results.component.spec.ts | 43 ++++++++++++++++--- .../search-results.component.ts | 13 +++++- aio/src/styles/1-layouts/_search-results.scss | 3 +- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/aio/src/app/search/search-results/search-results.component.spec.ts b/aio/src/app/search/search-results/search-results.component.spec.ts index 14b37e9d36..d6beda7214 100644 --- a/aio/src/app/search/search-results/search-results.component.spec.ts +++ b/aio/src/app/search/search-results/search-results.component.spec.ts @@ -42,30 +42,63 @@ describe('SearchResultsComponent', () => { searchResults.next({ query: '', results: results}); expect(currentAreas).toEqual([ + { name: 'api', pages: [ + { path: 'api/c', title: 'API C', type: 'class', keywords: '', titleWords: '' } + ] }, { name: 'guide', pages: [ { path: 'guide/a', title: 'Guide A', type: 'content', keywords: '', titleWords: '' }, { path: 'guide/b', title: 'Guide B', type: 'content', keywords: '', titleWords: '' }, { path: 'guide/b/c', title: 'Guide B - C', type: 'content', keywords: '', titleWords: '' } - ] }, - { name: 'api', pages: [ - { path: 'api/c', title: 'API C', type: 'class', keywords: '', titleWords: '' } ] } ]); }); - it('should put search results with no containing folder into the default area (Other)', () => { + it('should sort by title within sorted area', () => { + const results = [ + {path: 'guide/b', title: 'Guide B', type: 'content', keywords: '', titleWords: '' }, + {path: 'guide/a', title: 'Guide A', type: 'content', keywords: '', titleWords: '' }, + {path: 'api/d', title: 'API D', type: 'class', keywords: '', titleWords: '' }, + {path: 'guide/a/c', title: 'Guide A - C', type: 'content', keywords: '', titleWords: '' }, + {path: 'api/c', title: 'API C', type: 'class', keywords: '', titleWords: '' }, + ]; + + searchResults.next({ query: '', results: results }); + + expect(currentAreas).toEqual([ + { name: 'api', pages: [ + {path: 'api/c', title: 'API C', type: 'class', keywords: '', titleWords: '' }, + {path: 'api/d', title: 'API D', type: 'class', keywords: '', titleWords: '' }, + ] }, + { name: 'guide', pages: [ + {path: 'guide/a', title: 'Guide A', type: 'content', keywords: '', titleWords: '' }, + {path: 'guide/a/c', title: 'Guide A - C', type: 'content', keywords: '', titleWords: '' }, + {path: 'guide/b', title: 'Guide B', type: 'content', keywords: '', titleWords: '' }, + ] } + ]); + }); + + it('should put search results with no containing folder into the default area (other)', () => { const results = [ {path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' } ]; searchResults.next({ query: '', results: results }); expect(currentAreas).toEqual([ - { name: 'Other', pages: [ + { name: 'other', pages: [ { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' } ] } ]); }); + it('should omit search results with no title', () => { + const results = [ + {path: 'news', title: undefined, type: 'marketing', keywords: '', titleWords: '' } + ]; + + searchResults.next({ query: '', results: results }); + expect(currentAreas).toEqual([]); + }); + it('should emit an "resultSelected" event when a search result anchor is clicked', () => { let selectedResult: SearchResult; component.resultSelected.subscribe((result: SearchResult) => selectedResult = result); diff --git a/aio/src/app/search/search-results/search-results.component.ts b/aio/src/app/search/search-results/search-results.component.ts index 8f2a189bcd..93a64751d3 100644 --- a/aio/src/app/search/search-results/search-results.component.ts +++ b/aio/src/app/search/search-results/search-results.component.ts @@ -18,7 +18,7 @@ export interface SearchArea { }) export class SearchResultsComponent implements OnInit { - readonly defaultArea = 'Other'; + readonly defaultArea = 'other'; showResults = false; @@ -57,11 +57,16 @@ export class SearchResultsComponent implements OnInit { this.showResults = true; const searchAreaMap = {}; search.results.forEach(result => { + if (!result.title) { return; } // bad data; should fix const areaName = this.computeAreaName(result) || this.defaultArea; const area = searchAreaMap[areaName] = searchAreaMap[areaName] || []; area.push(result); }); - return Object.keys(searchAreaMap).map(name => ({ name, pages: searchAreaMap[name] })); + const keys = Object.keys(searchAreaMap).sort((l, r) => l > r ? 1 : -1); + return keys.map(name => ({ + name, + pages: searchAreaMap[name].sort(compareResults) + })); } // Split the search result path and use the top level folder, if there is one, as the area name. @@ -70,3 +75,7 @@ export class SearchResultsComponent implements OnInit { return rest && areaName; } } + +function compareResults(l: {title: string}, r: {title: string}) { + return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1; +} diff --git a/aio/src/styles/1-layouts/_search-results.scss b/aio/src/styles/1-layouts/_search-results.scss index 9f6b39e794..38a0f509d0 100644 --- a/aio/src/styles/1-layouts/_search-results.scss +++ b/aio/src/styles/1-layouts/_search-results.scss @@ -37,6 +37,7 @@ aio-search-results { h2 { font-size: 16px; margin: 10px 0px 5px; + text-transform: uppercase; } a { font-size: 14px; @@ -56,4 +57,4 @@ aio-search-results { width: 100%; display: block; } -} \ No newline at end of file +} From 092f0df7a6810c66cca2b592fceccdeaaee8fb9d Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Thu, 13 Apr 2017 18:58:25 -0700 Subject: [PATCH 0035/1039] test(aio): remove dross from NavMenuComponent spec --- .../nav-menu/nav-menu.component.spec.ts | 37 +++---------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/aio/src/app/layout/nav-menu/nav-menu.component.spec.ts b/aio/src/app/layout/nav-menu/nav-menu.component.spec.ts index 9ffc5c28c1..9350d04562 100644 --- a/aio/src/app/layout/nav-menu/nav-menu.component.spec.ts +++ b/aio/src/app/layout/nav-menu/nav-menu.component.spec.ts @@ -1,26 +1,18 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; - import { NavMenuComponent } from './nav-menu.component'; -import { CurrentNode, NavigationService, NavigationViews, NavigationNode } from 'app/navigation/navigation.service'; - describe('NavMenuComponent', () => { let component: NavMenuComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ declarations: [ NavMenuComponent ], - providers: [ - {provide: NavigationService, useClass: TestNavigationService } - ], schemas: [CUSTOM_ELEMENTS_SCHEMA] - }) - .compileComponents(); - })); + }); + }); beforeEach(() => { fixture = TestBed.createComponent(NavMenuComponent); @@ -31,23 +23,6 @@ describe('NavMenuComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + // TODO: Add TestHostComponent and tests. }); - -//// Test Helpers //// -class TestNavigationService { - navJson = { - SideNav: [ - { title: 'a', children: [ - { url: 'b', title: 'b', children: [ - { url: 'c', title: 'c' }, - { url: 'd', title: 'd' } - ] }, - { url: 'e', title: 'e' } - ] }, - { url: 'f', title: 'f' } - ] - }; - - navigationViews = new BehaviorSubject(this.navJson); - currentNode = new BehaviorSubject(undefined); -} From c0b1bbea3e8b44fbe916d2bd4c660e548aaf0ea1 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Fri, 14 Apr 2017 14:45:33 +0300 Subject: [PATCH 0036/1039] feat(aio): add log rotation in preview server --- aio/aio-builds-setup/dockerbuild/Dockerfile | 5 +++++ aio/aio-builds-setup/dockerbuild/logrotate/aio-misc | 9 +++++++++ .../dockerbuild/logrotate/aio-nginx | 13 +++++++++++++ .../dockerbuild/logrotate/aio-upload-server | 9 +++++++++ .../docs/vm-setup--create-host-dirs-and-files.md | 5 +++-- 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 aio/aio-builds-setup/dockerbuild/logrotate/aio-misc create mode 100644 aio/aio-builds-setup/dockerbuild/logrotate/aio-nginx create mode 100644 aio/aio-builds-setup/dockerbuild/logrotate/aio-upload-server diff --git a/aio/aio-builds-setup/dockerbuild/Dockerfile b/aio/aio-builds-setup/dockerbuild/Dockerfile index e75e104940..025ad5ef91 100644 --- a/aio/aio-builds-setup/dockerbuild/Dockerfile +++ b/aio/aio-builds-setup/dockerbuild/Dockerfile @@ -79,6 +79,11 @@ RUN apt-get update -y && apt-get install -y \ RUN yarn global add pm2@2 +# Set up log rotation +COPY logrotate/* /etc/logrotate.d/ +RUN chmod 0644 /etc/logrotate.d/* + + # Set up cronjobs COPY cronjobs/aio-builds-cleanup /etc/cron.d/ RUN chmod 0744 /etc/cron.d/aio-builds-cleanup diff --git a/aio/aio-builds-setup/dockerbuild/logrotate/aio-misc b/aio/aio-builds-setup/dockerbuild/logrotate/aio-misc new file mode 100644 index 0000000000..165009cd55 --- /dev/null +++ b/aio/aio-builds-setup/dockerbuild/logrotate/aio-misc @@ -0,0 +1,9 @@ +/var/log/aio/clean-up.log /var/log/aio/init.log /var/log/aio/verify-setup.log { + compress + create + delaycompress + missingok + monthly + notifempty + rotate 6 +} diff --git a/aio/aio-builds-setup/dockerbuild/logrotate/aio-nginx b/aio/aio-builds-setup/dockerbuild/logrotate/aio-nginx new file mode 100644 index 0000000000..59d434243e --- /dev/null +++ b/aio/aio-builds-setup/dockerbuild/logrotate/aio-nginx @@ -0,0 +1,13 @@ +/var/log/aio/nginx/*.log /var/log/aio/nginx-test/*.log { + compress + create + delaycompress + missingok + monthly + notifempty + rotate 6 + sharedscripts + postrotate + service nginx rotate >/dev/null 2>&1 + endscript +} diff --git a/aio/aio-builds-setup/dockerbuild/logrotate/aio-upload-server b/aio/aio-builds-setup/dockerbuild/logrotate/aio-upload-server new file mode 100644 index 0000000000..d3f2fc6188 --- /dev/null +++ b/aio/aio-builds-setup/dockerbuild/logrotate/aio-upload-server @@ -0,0 +1,9 @@ +/var/log/aio/upload-server-*.log { + compress + copytruncate + delaycompress + missingok + monthly + notifempty + rotate 6 +} diff --git a/aio/aio-builds-setup/docs/vm-setup--create-host-dirs-and-files.md b/aio/aio-builds-setup/docs/vm-setup--create-host-dirs-and-files.md index d3b4df8a42..423904954b 100644 --- a/aio/aio-builds-setup/docs/vm-setup--create-host-dirs-and-files.md +++ b/aio/aio-builds-setup/docs/vm-setup--create-host-dirs-and-files.md @@ -41,9 +41,10 @@ certificate covering both the domain and subdomains. ## Create directory for logs (Optional) Optionally, a logs directory can pe passed to the docker container for storing non-system-related logs. If not provided, the logs are kept locally on the container and will be lost whenever the -container is replaced (e.g. when updating to use a newer version of the docker image). +container is replaced (e.g. when updating to use a newer version of the docker image). Log files are +rotated and retained for 6 months. -The following files log files are kept in this directory: +The following log files are kept in this directory: - `clean-up.log`: Output of the `aio-clean-up` command, run as a cronjob for cleaning up the build artifacts of From 8479e9e6d71aee79871a53a01aac0ddd149855b4 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Thu, 13 Apr 2017 13:18:15 -0700 Subject: [PATCH 0037/1039] feat(aio): high res screen spacing --- aio/src/styles/1-layouts/_layout-global.scss | 4 ++++ aio/src/styles/1-layouts/_sidenav.scss | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/aio/src/styles/1-layouts/_layout-global.scss b/aio/src/styles/1-layouts/_layout-global.scss index e92354fc23..e6c79ab69d 100644 --- a/aio/src/styles/1-layouts/_layout-global.scss +++ b/aio/src/styles/1-layouts/_layout-global.scss @@ -1,3 +1,7 @@ +html, body { + height: 100%; +} + .clearfix { content: ""; display: table; diff --git a/aio/src/styles/1-layouts/_sidenav.scss b/aio/src/styles/1-layouts/_sidenav.scss index 7834a72760..c671880c13 100644 --- a/aio/src/styles/1-layouts/_sidenav.scss +++ b/aio/src/styles/1-layouts/_sidenav.scss @@ -11,7 +11,9 @@ aio-nav-menu.top-menu .vertical-menu-item { } md-sidenav-container.sidenav-container { - height: auto; + min-height: 100%; + height: auto !important; + margin: 0 auto; transform: none; } From b1a3c47766837f36fa8e2b9519a9630a359a1e4c Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Sat, 1 Apr 2017 02:17:19 +0300 Subject: [PATCH 0038/1039] fix(aio): add better message for disabled JavaScript --- aio/src/index.html | 15 ++++++++++++++- aio/src/styles/main.scss | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/aio/src/index.html b/aio/src/index.html index 795c0dc5db..e0bfcd73f6 100644 --- a/aio/src/index.html +++ b/aio/src/index.html @@ -33,6 +33,19 @@ - Loading... + + + diff --git a/aio/src/styles/main.scss b/aio/src/styles/main.scss index c6e61a0005..d516cddf12 100755 --- a/aio/src/styles/main.scss +++ b/aio/src/styles/main.scss @@ -2,7 +2,7 @@ @import '~@angular/material/theming'; @import './ng-io-theme'; -// import global variables / +// import global variables @import './constants'; // import global mixins @@ -11,7 +11,7 @@ // import directories @import './0-base/base-dir'; @import './1-layouts/layouts-dir'; -@import'./2-modules/modules-dir'; +@import './2-modules/modules-dir'; // import additional scss From 2535769a65b9d7f84b882533d0982ece3d61c50b Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Sat, 1 Apr 2017 02:21:11 +0300 Subject: [PATCH 0039/1039] feat(aio): allow reloading page from ServiceWorker update notification --- aio/src/app/sw-updates/global.value.ts | 5 +++++ .../sw-update-notifications.service.spec.ts | 22 ++++++++++++++++++- .../sw-update-notifications.service.ts | 18 ++++++++++++--- aio/src/app/sw-updates/sw-updates.module.ts | 3 +++ 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 aio/src/app/sw-updates/global.value.ts diff --git a/aio/src/app/sw-updates/global.value.ts b/aio/src/app/sw-updates/global.value.ts new file mode 100644 index 0000000000..58ff6b2562 --- /dev/null +++ b/aio/src/app/sw-updates/global.value.ts @@ -0,0 +1,5 @@ +import { InjectionToken } from '@angular/core'; + + +export const Global = new InjectionToken('global'); +export const globalProvider = { provide: Global, useValue: window }; diff --git a/aio/src/app/sw-updates/sw-update-notifications.service.spec.ts b/aio/src/app/sw-updates/sw-update-notifications.service.spec.ts index 5f1ec50d66..35d2b8178b 100644 --- a/aio/src/app/sw-updates/sw-update-notifications.service.spec.ts +++ b/aio/src/app/sw-updates/sw-update-notifications.service.spec.ts @@ -5,6 +5,7 @@ import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { MockSwUpdatesService } from 'testing/sw-updates.service'; +import { Global } from './global.value'; import { SwUpdateNotificationsService } from './sw-update-notifications.service'; import { SwUpdatesService } from './sw-updates.service'; @@ -26,6 +27,7 @@ describe('SwUpdateNotificationsService', () => { beforeEach(() => { injector = ReflectiveInjector.resolveAndCreate([ + { provide: Global, useClass: MockGlobal }, { provide: MdSnackBar, useClass: MockMdSnackBar }, { provide: SwUpdatesService, useClass: MockSwUpdatesService }, SwUpdateNotificationsService @@ -87,10 +89,22 @@ describe('SwUpdateNotificationsService', () => { activateUpdate(true); expect(snackBar.$$lastRef.$$message).toContain('Update activated successfully'); - expect(snackBar.$$lastRef.$$action).toBeNull(); + expect(snackBar.$$lastRef.$$action).toBe('Reload'); expect(snackBar.$$lastRef.$$config.duration).toBeUndefined(); })); + it('should reload the page when clicking on `Reload` (after a successful activation)', + fakeAsync(() => { + const global = injector.get(Global); + + activateUpdate(true); + expect(global.location.reload).not.toHaveBeenCalled(); + + snackBar.$$lastRef.$$onActionSubj.next(); + expect(global.location.reload).toHaveBeenCalled(); + }) + ); + it('should report a failed activation', fakeAsync(() => { activateUpdate(false); @@ -158,6 +172,12 @@ describe('SwUpdateNotificationsService', () => { }); // Mocks +class MockGlobal { + location = { + reload: jasmine.createSpy('MockGlobal.reload') + }; +} + class MockMdSnackBarRef { $$afterDismissedSubj = new Subject(); $$onActionSubj = new Subject(); diff --git a/aio/src/app/sw-updates/sw-update-notifications.service.ts b/aio/src/app/sw-updates/sw-update-notifications.service.ts index d321f1f5d3..8ce39ed2e9 100644 --- a/aio/src/app/sw-updates/sw-update-notifications.service.ts +++ b/aio/src/app/sw-updates/sw-update-notifications.service.ts @@ -1,8 +1,9 @@ -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { MdSnackBar, MdSnackBarConfig, MdSnackBarRef } from '@angular/material'; import { Subject } from 'rxjs/Subject'; import 'rxjs/add/operator/filter'; +import { Global } from './global.value'; import { SwUpdatesService } from './sw-updates.service'; @@ -29,7 +30,9 @@ export class SwUpdateNotificationsService { private snackBars: MdSnackBarRef[] = []; private enabled = false; - constructor(private snackBarService: MdSnackBar, private swUpdates: SwUpdatesService) { + constructor(@Inject(Global) private global: any, + private snackBarService: MdSnackBar, + private swUpdates: SwUpdatesService) { this.onDisable.subscribe(() => this.snackBars.forEach(sb => sb.dismiss())); } @@ -71,7 +74,9 @@ export class SwUpdateNotificationsService { } private onActivateSuccess() { - this.openSnackBar('Update activated successfully! Reload the page to see the latest content.'); + const message = 'Update activated successfully! Reload the page to see the latest content.'; + this.openSnackBar(message, 'Reload') + .onAction().subscribe(() => this.reloadPage()); } private openSnackBar(message: string, action?: string, config?: MdSnackBarConfig): MdSnackBarRef { @@ -82,4 +87,11 @@ export class SwUpdateNotificationsService { return snackBar; } + + private reloadPage() { + const location = this.global && (this.global as Window).location; + if (location && location.reload) { + location.reload(); + } + } } diff --git a/aio/src/app/sw-updates/sw-updates.module.ts b/aio/src/app/sw-updates/sw-updates.module.ts index eea4900b19..71b53ea6e3 100644 --- a/aio/src/app/sw-updates/sw-updates.module.ts +++ b/aio/src/app/sw-updates/sw-updates.module.ts @@ -2,16 +2,19 @@ import { NgModule } from '@angular/core'; import { MdSnackBarModule } from '@angular/material'; import { ServiceWorkerModule } from '@angular/service-worker'; +import { globalProvider } from './global.value'; import { noopNgServiceWorkerProviders } from './noop-ng-service-worker'; import { SwUpdateNotificationsService } from './sw-update-notifications.service'; import { SwUpdatesService } from './sw-updates.service'; + @NgModule({ imports: [ MdSnackBarModule, ServiceWorkerModule ], providers: [ + globalProvider, noopNgServiceWorkerProviders, SwUpdateNotificationsService, SwUpdatesService From a73050de483c2e9393c77b9dfcab7feed72b4cda Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Sat, 1 Apr 2017 15:09:39 +0300 Subject: [PATCH 0040/1039] ci(aio): convert `deploy-staging` to a shell script --- aio/package.json | 4 +--- aio/scripts/deploy-staging.sh | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 aio/scripts/deploy-staging.sh diff --git a/aio/package.json b/aio/package.json index 0b2368da6c..7db2bb4eb6 100644 --- a/aio/package.json +++ b/aio/package.json @@ -14,15 +14,13 @@ "pree2e": "yarn ~~update-webdriver", "e2e": "yarn check-env && ng e2e --no-webdriver-update", "deploy-preview": "scripts/deploy-preview.sh", - "deploy-staging": "firebase use staging --token \"$FIREBASE_TOKEN\" && yarn ~~deploy", + "deploy-staging": "scripts/deploy-staging.sh", "check-env": "node ../tools/check-environment.js", "predocs": "rimraf src/content", "docs": "dgeni ./transforms/angular.io-package", "docs-lint": "eslint --ignore-path=\"transforms/.eslintignore\" transforms", "docs-test": "node ../dist/tools/cjs-jasmine/index-tools ../../transforms/**/*.spec.js", "~~update-webdriver": "webdriver-manager update --standalone false --gecko false", - "pre~~deploy": "yarn build", - "~~deploy": "firebase deploy --message \"Commit: $TRAVIS_COMMIT\" --non-interactive --token \"$FIREBASE_TOKEN\"", "boilerplate:add": "node ./tools/examples/add-example-boilerplate add", "boilerplate:remove": "node ./tools/examples/add-example-boilerplate remove", "generate-plunkers": "node ./tools/plunker-builder/generatePlunkers" diff --git a/aio/scripts/deploy-staging.sh b/aio/scripts/deploy-staging.sh new file mode 100644 index 0000000000..a33ee0f09b --- /dev/null +++ b/aio/scripts/deploy-staging.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# WARNING: FIREBASE_TOKEN should NOT be printed. +set +x -eu -o pipefail + + +FIREBASE_PROJECT_ID=aio-staging + +cd "`dirname $0`/.." + +# Build the app +yarn build + +# Deploy to staging +firebase use "$FIREBASE_PROJECT_ID" --token "$FIREBASE_TOKEN" +firebase deploy --message "Commit: $TRAVIS_COMMIT" --non-interactive --token "$FIREBASE_TOKEN" + +cd - From 49d97e1216080d1062060a7124522e999988676c Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Sat, 1 Apr 2017 02:24:25 +0300 Subject: [PATCH 0041/1039] ci(aio): verify that the Lighthouse PWA score remains above a threshold --- aio/package.json | 6 + aio/scripts/deploy-preview.sh | 12 +- aio/scripts/deploy-staging.sh | 4 + aio/scripts/test-pwa-score.js | 106 +++++++++++++++ aio/yarn.lock | 237 ++++++++++++++++++++++++++++++++-- scripts/ci/deploy.sh | 4 +- scripts/ci/env.sh | 6 + scripts/ci/test-aio.sh | 10 +- 8 files changed, 366 insertions(+), 19 deletions(-) create mode 100644 aio/scripts/test-pwa-score.js diff --git a/aio/package.json b/aio/package.json index 7db2bb4eb6..70e78091c8 100644 --- a/aio/package.json +++ b/aio/package.json @@ -13,6 +13,9 @@ "test": "yarn check-env && ng test --sourcemap=false", "pree2e": "yarn ~~update-webdriver", "e2e": "yarn check-env && ng e2e --no-webdriver-update", + "pretest-pwa-score-local": "yarn build", + "test-pwa-score-local": "concurrently --kill-others --success first \"http-server dist -p 4200 --silent\" \"yarn test-pwa-score -- http://localhost:4200 90\"", + "test-pwa-score": "node scripts/test-pwa-score", "deploy-preview": "scripts/deploy-preview.sh", "deploy-staging": "scripts/deploy-staging.sh", "check-env": "node ../tools/check-environment.js", @@ -50,6 +53,7 @@ "@types/node": "~6.0.60", "canonical-path": "^0.0.2", "codelyzer": "~2.0.0", + "concurrently": "^3.4.0", "dgeni": "^0.4.7", "dgeni-packages": "0.17.0", "entities": "^1.1.1", @@ -59,6 +63,7 @@ "fs-extra": "^2.1.1", "globby": "^6.1.0", "html": "^1.0.0", + "http-server": "^0.9.0", "jasmine-core": "~2.5.2", "jasmine-spec-reporter": "~3.2.0", "jsdom": "^9.12.0", @@ -68,6 +73,7 @@ "karma-coverage-istanbul-reporter": "^0.2.0", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", + "lighthouse": "^1.6.3", "lodash": "^4.17.4", "protractor": "~5.1.0", "remark": "^7.0.0", diff --git a/aio/scripts/deploy-preview.sh b/aio/scripts/deploy-preview.sh index 9c94d7da4f..48119de1ff 100755 --- a/aio/scripts/deploy-preview.sh +++ b/aio/scripts/deploy-preview.sh @@ -6,14 +6,17 @@ set +x -eu -o pipefail INPUT_DIR=dist/ OUTPUT_FILE=/tmp/snapshot.tar.gz -AIO_BUILDS_HOST=https://ngbuilds.io -UPLOAD_URL=$AIO_BUILDS_HOST/create-build/$TRAVIS_PULL_REQUEST/$TRAVIS_PULL_REQUEST_SHA +AIO_BUILDS_DOMAIN=ngbuilds.io +UPLOAD_URL=https://$AIO_BUILDS_DOMAIN/create-build/$TRAVIS_PULL_REQUEST/$TRAVIS_PULL_REQUEST_SHA +DEPLOYED_URL=https://pr$TRAVIS_PULL_REQUEST-$TRAVIS_PULL_REQUEST_SHA.$AIO_BUILDS_DOMAIN cd "`dirname $0`/.." -yarn run build +# Build the app +yarn build tar --create --gzip --directory "$INPUT_DIR" --file "$OUTPUT_FILE" . +# Deploy to staging exec 3>&1 httpCode=$( curl --include --location --request POST --silent --write-out "\nHTTP_CODE: %{http_code}\n" \ @@ -30,4 +33,7 @@ if [ $httpCode -lt 200 ] || ([ $httpCode -ge 400 ] && [ $httpCode -ne 409 ]); th exit 1 fi +# Run PWA-score tests +yarn test-pwa-score -- "$DEPLOYED_URL" "$MIN_PWA_SCORE_PREVIEW" + cd - diff --git a/aio/scripts/deploy-staging.sh b/aio/scripts/deploy-staging.sh index a33ee0f09b..bb8dc47d59 100644 --- a/aio/scripts/deploy-staging.sh +++ b/aio/scripts/deploy-staging.sh @@ -5,6 +5,7 @@ set +x -eu -o pipefail FIREBASE_PROJECT_ID=aio-staging +DEPLOYED_URL=https://$FIREBASE_PROJECT_ID.firebaseapp.com cd "`dirname $0`/.." @@ -15,4 +16,7 @@ yarn build firebase use "$FIREBASE_PROJECT_ID" --token "$FIREBASE_TOKEN" firebase deploy --message "Commit: $TRAVIS_COMMIT" --non-interactive --token "$FIREBASE_TOKEN" +# Run PWA-score tests +yarn test-pwa-score -- "$DEPLOYED_URL" "$MIN_PWA_SCORE_STAGING" + cd - diff --git a/aio/scripts/test-pwa-score.js b/aio/scripts/test-pwa-score.js new file mode 100644 index 0000000000..756665e6f4 --- /dev/null +++ b/aio/scripts/test-pwa-score.js @@ -0,0 +1,106 @@ +#!/bin/env node + +/** + * Usage: + * node scripts/test-pwa-score [ []] + * + * Defaults: + * url: http://localhost:4200 + * minScore: 90 + * + * (Ignores HTTPS-related audits, when run for HTTP URL.) + */ + +// Imports +const lighthouse = require('lighthouse'); +const ChromeLauncher = require('lighthouse/lighthouse-cli/chrome-launcher').ChromeLauncher; +const Printer = require('lighthouse/lighthouse-cli/printer'); +const config = require('lighthouse/lighthouse-core/config/default.json'); + +// Constants +const FLAGS = {output: 'json'}; + +// Specify the path to Chrome on Travis +if (process.env.TRAVIS) { + process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN; +} + +// Run +_main(process.argv.slice(2)); + +// Functions - Definitions +function _main(args) { + const {url, minScore} = parseInput(args); + const isOnHttp = /^http:/.test(url); + + console.log(`Running PWA audit for '${url}'...`); + + if (isOnHttp) { + ignoreHttpsAudits(config.aggregations); + } + + launchChromeAndRunLighthouse(url, FLAGS, config). + then(getScore). + then(score => evaluateScore(minScore, score)). + catch(onError); +} + +function evaluateScore(expectedScore, actualScore) { + console.log('Lighthouse PWA score:'); + console.log(` - Expected: ${expectedScore} / 100 (or higher)`); + console.log(` - Actual: ${actualScore} / 100`); + + if (actualScore < expectedScore) { + throw new Error(`PWA score is too low. (${actualScore} < ${expectedScore})`); + } +} + +function getScore(results) { + const scoredAggregations = results.aggregations.filter(a => a.scored); + const total = scoredAggregations.reduce((sum, a) => sum + a.total, 0); + + return Math.round((total / scoredAggregations.length) * 100); +} + +function ignoreHttpsAudits(aggregations) { + const httpsAudits = [ + 'is-on-https', + 'redirects-http' + ]; + + console.info(`Ignoring HTTPS-related audits (${httpsAudits.join(', ')})...`); + + aggregations.forEach(aggregation => + aggregation.items.forEach(item => + httpsAudits.map(key => item.audits[key]).forEach(audit => + // Ugly hack to ignore HTTPS-related audits (i.e. simulate them passing). + // Only meant for use during development. + audit && (audit.expectedValue = !audit.expectedValue)))); +} + +function launchChromeAndRunLighthouse(url, flags, config) { + const launcher = new ChromeLauncher({autoSelectChrome: true}); + + return launcher.run(). + then(() => lighthouse(url, flags, config)). + then(results => launcher.kill().then(() => results)). + catch(err => launcher.kill().then(() => { throw err; }, () => { throw err; })); +} + +function onError(err) { + console.error(err); + process.exit(1); +} + +function parseInput(args) { + const url = args[0]; + const minScore = Number(args[1]); + + if (!url) { + onError('Invalid arguments: not specified.'); + } else if (isNaN(minScore)) { + onError('Invalid arguments: not specified or not a number.'); + } + + return {url, minScore}; +} diff --git a/aio/yarn.lock b/aio/yarn.lock index 647b86a591..3c09edefa3 100644 --- a/aio/yarn.lock +++ b/aio/yarn.lock @@ -275,10 +275,18 @@ ansi-html@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" +ansi-regex@^0.2.0, ansi-regex@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" +ansi-styles@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -495,6 +503,16 @@ aws4@^1.2.1: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +axe-core@2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-2.1.7.tgz#4f66f2b3ee3b58ec2d3db4339dd124c5b33b79c3" + +babar@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/babar/-/babar-0.0.3.tgz#2f394d4a5918f7e1ae9e5408e9a96f3f935ee1e2" + dependencies: + colors "~0.6.2" + babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.20.0, babel-code-frame@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" @@ -916,6 +934,16 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" +chalk@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" + dependencies: + ansi-styles "^1.1.0" + escape-string-regexp "^1.0.0" + has-ansi "^0.1.0" + strip-ansi "^0.3.0" + supports-color "^0.2.0" + chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -992,6 +1020,14 @@ chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0: optionalDependencies: fsevents "^1.0.0" +chrome-devtools-frontend@1.0.401423: + version "1.0.401423" + resolved "https://registry.yarnpkg.com/chrome-devtools-frontend/-/chrome-devtools-frontend-1.0.401423.tgz#32a89b8d04e378a494be3c8d63271703be1c04ea" + +chrome-devtools-frontend@1.0.422034: + version "1.0.422034" + resolved "https://registry.yarnpkg.com/chrome-devtools-frontend/-/chrome-devtools-frontend-1.0.422034.tgz#071c8ce14466b7653032fcd1ad1a4a68d5e3cbd9" + cipher-base@^1.0.0, cipher-base@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07" @@ -1135,6 +1171,10 @@ colors@1.1.2, colors@^1.1.0, colors@^1.1.2, colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +colors@~0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" + combine-lists@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/combine-lists/-/combine-lists-1.0.1.tgz#458c07e09e0d900fc28b70a3fec2dacd1d2cb7f6" @@ -1153,6 +1193,10 @@ comma-separated-tokens@^1.0.1: dependencies: trim "0.0.1" +commander@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" + commander@2.9.x, commander@^2.8.1, commander@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -1225,6 +1269,19 @@ concat-stream@^1.4.7, concat-stream@^1.5.2: readable-stream "^2.2.2" typedarray "^0.0.6" +concurrently@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.4.0.tgz#60662b3defde07375bae19aac0ab780ec748ba79" + dependencies: + chalk "0.5.1" + commander "2.6.0" + date-fns "^1.23.0" + lodash "^4.5.1" + rx "2.3.24" + spawn-command "^0.0.2-1" + supports-color "^3.2.3" + tree-kill "^1.1.0" + configstore@^1.0.0, configstore@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021" @@ -1335,6 +1392,10 @@ core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +corser@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" + crc32-stream@~0.3.1: version "0.3.4" resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-0.3.4.tgz#73bc25b45fac1db6632231a7bfce8927e9f06552" @@ -1567,6 +1628,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-fns@^1.23.0: + version "1.28.2" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.28.2.tgz#19e4192d68875c0bf7c9537e3f296a8ec64853ef" + date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -1666,6 +1731,13 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +devtools-timeline-model@1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/devtools-timeline-model/-/devtools-timeline-model-1.1.6.tgz#7be51a73b55d727b597bb30dd1ed2e8e210639a5" + dependencies: + chrome-devtools-frontend "1.0.401423" + resolve "1.1.7" + dgeni-packages@0.17.0: version "0.17.0" resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.17.0.tgz#b2e5117670e99109f664703af26a460a5064d6cc" @@ -1849,6 +1921,15 @@ ecdsa-sig-formatter@1.0.9: base64url "^2.0.0" safe-buffer "^5.0.1" +ecstatic@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-1.4.1.tgz#32cb7b6fa2e290d58668674d115e8f0c3d567d6a" + dependencies: + he "^0.5.0" + mime "^1.2.11" + minimist "^1.1.0" + url-join "^1.0.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2024,7 +2105,7 @@ escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2611,6 +2692,10 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +gl-matrix@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-2.3.2.tgz#aac808c74af7d5db05fe04cb60ca1a0fcb174d74" + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -2764,6 +2849,16 @@ handle-thing@^1.2.4: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" +handlebars@4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.5.tgz#92c6ed6bb164110c50d4d8d0fbddc70806c6f8e7" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + handlebars@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-1.3.0.tgz#9e9b130a93e389491322d975cf3ec1818c37ce34" @@ -2791,6 +2886,12 @@ har-validator@~2.0.6: is-my-json-valid "^2.12.4" pinkie-promise "^2.0.0" +has-ansi@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" + dependencies: + ansi-regex "^0.2.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -2876,6 +2977,10 @@ he@1.1.x: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" +he@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/he/-/he-0.5.0.tgz#2c05ffaef90b68e860f3fd2b54ef580989277ee2" + header-case@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/header-case/-/header-case-1.0.0.tgz#d9e335909505d56051ec16a0106821889e910781" @@ -2993,13 +3098,26 @@ http-proxy-middleware@~0.17.1: lodash "^4.17.2" micromatch "^2.3.11" -http-proxy@^1.13.0, http-proxy@^1.16.2: +http-proxy@^1.13.0, http-proxy@^1.16.2, http-proxy@^1.8.1: version "1.16.2" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" dependencies: eventemitter3 "1.x.x" requires-port "1.x.x" +http-server@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/http-server/-/http-server-0.9.0.tgz#8f1b06bdc733618d4dc42831c7ba1aff4e06001a" + dependencies: + colors "1.0.3" + corser "~2.0.0" + ecstatic "^1.4.0" + http-proxy "^1.8.1" + opener "~1.4.0" + optimist "0.6.x" + portfinder "0.4.x" + union "~0.4.3" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -3044,6 +3162,10 @@ image-size@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.1.tgz#28eea8548a4b1443480ddddc1e083ae54652439f" +image-ssim@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/image-ssim/-/image-ssim-0.2.0.tgz#83b42c7a2e6e4b85505477fe6917f5dbc56420e5" + img-stats@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/img-stats/-/img-stats-0.5.2.tgz#c203496c42f2d9eb2e5ab8232fa756bab32c9e2b" @@ -3512,6 +3634,10 @@ join-path@^1.0.0: url-join "0.0.1" valid-url "^1" +jpeg-js@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.1.2.tgz#135b992c0575c985cfa0f494a3227ed238583ece" + js-base64@^2.1.5, js-base64@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" @@ -3587,7 +3713,7 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stringify-safe@~5.0.1: +json-stringify-safe@5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -3815,6 +3941,28 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lighthouse@^1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/lighthouse/-/lighthouse-1.6.3.tgz#099ef484d94d844fab7189ef105e1f33464326b6" + dependencies: + axe-core "2.1.7" + chrome-devtools-frontend "1.0.422034" + debug "2.2.0" + devtools-timeline-model "1.1.6" + gl-matrix "2.3.2" + handlebars "4.0.5" + json-stringify-safe "5.0.1" + marked "0.3.6" + metaviewport-parser "0.0.1" + mkdirp "0.5.1" + opn "4.0.2" + rimraf "2.2.8" + semver "5.3.0" + speedline "1.0.3" + whatwg-url "4.0.0" + ws "1.1.1" + yargs "3.32.0" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -3924,7 +4072,7 @@ lodash@^3.10.0, lodash@^3.10.1, lodash@^3.8.0, lodash@~3.10.0, lodash@~3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.11.1, lodash@^4.11.2, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1: +lodash@^4.0.0, lodash@^4.11.1, lodash@^4.11.2, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.5.1, lodash@^4.6.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3953,7 +4101,7 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" -loud-rejection@^1.0.0: +loud-rejection@^1.0.0, loud-rejection@^1.3.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" dependencies: @@ -4011,7 +4159,7 @@ markdown-table@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.0.tgz#1f5ae61659ced8808d882554c32e8b3f38dd1143" -marked@^0.3.2: +marked@0.3.6, marked@^0.3.2: version "0.3.6" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" @@ -4086,6 +4234,10 @@ merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" +metaviewport-parser@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/metaviewport-parser/-/metaviewport-parser-0.0.1.tgz#9b28179659b76ff9d21de84ae25583257909b206" + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -4161,7 +4313,7 @@ mkdirp-promise@^5.0.0: dependencies: mkdirp "*" -mkdirp@*, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -4501,6 +4653,10 @@ open@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc" +opener@~1.4.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" + opn@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95" @@ -4508,7 +4664,7 @@ opn@4.0.2: object-assign "^4.0.1" pinkie-promise "^2.0.0" -optimist@^0.6.1, optimist@~0.6.0, optimist@~0.6.1: +optimist@0.6.x, optimist@^0.6.1, optimist@~0.6.0, optimist@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" dependencies: @@ -4756,7 +4912,7 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" -portfinder@^0.4.0: +portfinder@0.4.x, portfinder@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-0.4.0.tgz#a3ffadffafe4fb98e0601a85eda27c27ce84ca1e" dependencies: @@ -5142,6 +5298,10 @@ qs@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/qs/-/qs-1.1.0.tgz#2845cd9df462b2db28a90370e142d492c5a45dde" +qs@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404" + qs@~6.3.0: version "6.3.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.1.tgz#918c0b3bcd36679772baf135b1acb4c1651ed79d" @@ -5507,6 +5667,10 @@ resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + resolve@^1.1.6, resolve@^1.1.7: version "1.2.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c" @@ -5544,7 +5708,7 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@^2. dependencies: glob "^7.0.5" -rimraf@~2.2.6: +rimraf@2.2.8, rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" @@ -5590,6 +5754,10 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" +rx@2.3.24: + version "2.3.24" + resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" + rx@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" @@ -5679,7 +5847,7 @@ semver-dsl@^1.0.1: dependencies: semver "^5.3.0" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.2.0, semver@^5.3.0, semver@~5.3.0: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@5.3.0, semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.2.0, semver@^5.3.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -5911,6 +6079,10 @@ space-separated-tokens@^1.0.0: dependencies: trim "0.0.1" +spawn-command@^0.0.2-1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" + spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" @@ -5949,6 +6121,16 @@ spdy@^3.4.1: select-hose "^2.0.0" spdy-transport "^2.0.15" +speedline@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/speedline/-/speedline-1.0.3.tgz#ee1d98c18d583a2d8488aaded2db9334b943dbbd" + dependencies: + babar "0.0.3" + image-ssim "^0.2.0" + jpeg-js "^0.1.2" + loud-rejection "^1.3.0" + meow "^3.7.0" + sprintf-js@^1.0.3, sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6054,6 +6236,12 @@ stringstream@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" +strip-ansi@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" + dependencies: + ansi-regex "^0.2.1" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -6147,6 +6335,10 @@ superstatic@^4.0: try-require "^1.0.0" update-notifier "^1.0.1" +supports-color@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -6342,6 +6534,10 @@ tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" +tree-kill@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.1.0.tgz#c963dcf03722892ec59cba569e940b71954d1729" + trim-lines@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.0.tgz#9926d03ede13ba18f7d42222631fb04c79ff26fe" @@ -6524,6 +6720,12 @@ unified@^6.0.0: x-is-function "^1.0.4" x-is-string "^0.1.0" +union@~0.4.3: + version "0.4.6" + resolved "https://registry.yarnpkg.com/union/-/union-0.4.6.tgz#198fbdaeba254e788b0efcb630bc11f24a2959e0" + dependencies: + qs "~2.3.3" + uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -6657,6 +6859,10 @@ url-join@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8" +url-join@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" + url-loader@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.7.tgz#67e8779759f8000da74994906680c943a9b0925d" @@ -6944,6 +7150,13 @@ whatwg-encoding@^1.0.1: dependencies: iconv-lite "0.4.13" +whatwg-url@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.0.0.tgz#5be362f0b6e2f8760f7260df6e0e1df536f5479c" + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^4.3.0: version "4.7.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.7.0.tgz#202035ac1955b087cdd20fa8b58ded3ab1cd2af5" @@ -7147,7 +7360,7 @@ yargs-parser@^4.2.0: dependencies: camelcase "^3.0.0" -yargs@^3.32.0: +yargs@3.32.0, yargs@^3.32.0: version "3.32.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" dependencies: diff --git a/scripts/ci/deploy.sh b/scripts/ci/deploy.sh index f9aaaa149d..b0155a588d 100755 --- a/scripts/ci/deploy.sh +++ b/scripts/ci/deploy.sh @@ -71,7 +71,7 @@ case ${CI_MODE} in ;; 0) # Preconditions met: Deploy - yarn run deploy-preview + yarn deploy-preview ;; esac fi @@ -82,7 +82,7 @@ case ${CI_MODE} in else # This is upstream master: Deploy to staging travisFoldStart "deploy.aio.staging" - yarn run deploy-staging + yarn deploy-staging travisFoldEnd "deploy.aio.staging" fi ) diff --git a/scripts/ci/env.sh b/scripts/ci/env.sh index 574c711fe2..d529cb376a 100755 --- a/scripts/ci/env.sh +++ b/scripts/ci/env.sh @@ -58,6 +58,12 @@ if [[ ${TRAVIS:-} ]]; then browserstack_optional) setEnvVar KARMA_JS_BROWSERS `node -e "console.log(require('/home/travis/build/angular/angular/browser-providers.conf').browserstackAliases.CI_OPTIONAL.join(','))"` ;; + aio) + # Due to network latency/server performance, the min accepted PWA score + # on previews is a little lower than on staging. + setEnvVar MIN_PWA_SCORE_PREVIEW 93 + setEnvVar MIN_PWA_SCORE_STAGING 95 + ;; esac else setEnvVar KARMA_JS_BROWSERS Chrome diff --git a/scripts/ci/test-aio.sh b/scripts/ci/test-aio.sh index febeefb1e3..cc86badba2 100755 --- a/scripts/ci/test-aio.sh +++ b/scripts/ci/test-aio.sh @@ -14,7 +14,7 @@ source ${thisDir}/_travis-fold.sh # Lint the code travisFoldStart "test.aio.lint" - yarn run lint + yarn lint travisFoldEnd "test.aio.lint" @@ -34,10 +34,16 @@ source ${thisDir}/_travis-fold.sh # Run e2e tests travisFoldStart "test.aio.e2e" - yarn run e2e + yarn e2e travisFoldEnd "test.aio.e2e" + # Run PWA-score tests + travisFoldStart "test.aio.pwaScore" + yarn test-pwa-score-local + travisFoldEnd "test.aio.pwaScore" + + # Run unit tests for aio/aio-builds-setup travisFoldStart "test.aio.aio-builds-setup" ./aio-builds-setup/scripts/test.sh From 16673fa38b6f6226b855b5d75ec28dde7d6eb015 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 14 Apr 2017 19:28:00 +0100 Subject: [PATCH 0042/1039] build(aio): remove unused config code --- aio/transforms/angular.io-package/index.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/aio/transforms/angular.io-package/index.js b/aio/transforms/angular.io-package/index.js index bf0cb09285..e866fe3e0a 100644 --- a/aio/transforms/angular.io-package/index.js +++ b/aio/transforms/angular.io-package/index.js @@ -207,7 +207,7 @@ module.exports = // Configure nunjucks rendering of docs via templates .config(function( - renderDocsProcessor, versionInfo, templateFinder, templateEngine, getInjectables, renderMarkdown) { + renderDocsProcessor, versionInfo, templateFinder, templateEngine, getInjectables) { // Where to find the templates for the doc rendering templateFinder.templateFolders = [TEMPLATES_PATH]; @@ -234,12 +234,6 @@ module.exports = renderDocsProcessor.helpers.relativePath = function(from, to) { return path.relative(from, to); }; - - // Tell the HTML formatter not to format code-example blocks - renderMarkdown.unformattedTags = [ - 'code-example', - 'code-pane' - ]; }) From 8f9ba62dc3723ede5d667ea109180c4fb1883a03 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Fri, 14 Apr 2017 15:50:14 -0700 Subject: [PATCH 0043/1039] feat(aio): positioning UX for nav and search --- aio/src/app/app.component.html | 2 +- aio/src/styles/1-layouts/_search-results.scss | 8 ++++---- aio/src/styles/1-layouts/_sidenav.scss | 20 +++++++++++++++++-- aio/src/styles/1-layouts/_top-menu.scss | 5 ++++- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/aio/src/app/app.component.html b/aio/src/app/app.component.html index 011ec48c39..e0370e8813 100644 --- a/aio/src/app/app.component.html +++ b/aio/src/app/app.component.html @@ -11,7 +11,7 @@ - + diff --git a/aio/src/styles/1-layouts/_search-results.scss b/aio/src/styles/1-layouts/_search-results.scss index 38a0f509d0..b14db7c06e 100644 --- a/aio/src/styles/1-layouts/_search-results.scss +++ b/aio/src/styles/1-layouts/_search-results.scss @@ -5,10 +5,10 @@ aio-search-results { .search-results { display: flex; flex-direction: row; - overflow: scroll; - padding-top: 50px; + overflow: auto; + padding: 68px 32px 0; color: $offwhite; - width: 100%; + width: auto; max-height: 40%; position: fixed; top: 0; @@ -16,7 +16,7 @@ aio-search-results { right: 0; z-index: 5; background-color: $darkgray; - border: 24px solid $darkgray; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.3); @media (max-width: 480px) { display: block; diff --git a/aio/src/styles/1-layouts/_sidenav.scss b/aio/src/styles/1-layouts/_sidenav.scss index c671880c13..1a6f9acb42 100644 --- a/aio/src/styles/1-layouts/_sidenav.scss +++ b/aio/src/styles/1-layouts/_sidenav.scss @@ -6,16 +6,21 @@ aio-nav-menu.top-menu .vertical-menu-item { .mat-sidenav.sidenav { box-shadow: 6px 0 6px rgba(0,0,0,0.10); background-color: $offwhite; - padding: 10px 0px 0px; + padding: 80px 0px 0px; min-width: 260px; } +md-sidenav.mat-sidenav.sidenav.collapsed { + padding-top: 32px; +} + md-sidenav-container.sidenav-container { min-height: 100%; height: auto !important; margin: 0 auto; transform: none; - } + padding-top: 52px; +} // md-sidenav.sidenav.mat-sidenav.mat-sidenav-side.mat-sidenav-opened { // position: fixed; @@ -145,3 +150,14 @@ a.selected.level-1, max-width: 100%; } } + +aio-nav-menu.top-menu { + aio-nav-item:first-child .vertical-menu-item { + padding-top: 48px; + } + + aio-nav-item:last-child div { + border-bottom: 2px solid rgba($mediumgray, 0.5); + } + +} \ No newline at end of file diff --git a/aio/src/styles/1-layouts/_top-menu.scss b/aio/src/styles/1-layouts/_top-menu.scss index da57bfa6fd..394df75984 100644 --- a/aio/src/styles/1-layouts/_top-menu.scss +++ b/aio/src/styles/1-layouts/_top-menu.scss @@ -55,7 +55,10 @@ aio-top-menu { } md-toolbar.mat-toolbar { - position: relative; + position: fixed; + top: 0; + right: 0; + left: 0; z-index: 10; box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30); padding: 0 16px 0 0; From 49dfc9fe2de04557b2bada6aece39a5e6b1b35ed Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Fri, 14 Apr 2017 16:23:19 -0700 Subject: [PATCH 0044/1039] feat(aio): scroll bar styling --- aio/src/styles/2-modules/_modules-dir.scss | 3 ++- aio/src/styles/2-modules/_scrollbar.scss | 25 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 aio/src/styles/2-modules/_scrollbar.scss diff --git a/aio/src/styles/2-modules/_modules-dir.scss b/aio/src/styles/2-modules/_modules-dir.scss index a8370d2a6b..69007c7ccb 100644 --- a/aio/src/styles/2-modules/_modules-dir.scss +++ b/aio/src/styles/2-modules/_modules-dir.scss @@ -19,4 +19,5 @@ @import 'banner'; @import 'api-list'; @import 'hr'; - @import 'live-example'; \ No newline at end of file + @import 'live-example'; + @import 'scrollbar'; \ No newline at end of file diff --git a/aio/src/styles/2-modules/_scrollbar.scss b/aio/src/styles/2-modules/_scrollbar.scss new file mode 100644 index 0000000000..772ea9faf8 --- /dev/null +++ b/aio/src/styles/2-modules/_scrollbar.scss @@ -0,0 +1,25 @@ +body::-webkit-scrollbar { + width: 6px; +} + +body::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); +} + +body::-webkit-scrollbar-thumb { + background-color: $mediumgray; + outline: 1px solid $darkgray; +} + +.search-results::-webkit-scrollbar { + width: 4px; +} + +.search-results::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); +} + +.search-results::-webkit-scrollbar-thumb { + background-color: $mediumgray; + outline: 1px solid slategrey; +} \ No newline at end of file From b57c9605ce58e8f056a7428673cfa2a0fed7261f Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Wed, 12 Apr 2017 13:57:55 -0700 Subject: [PATCH 0045/1039] feat(aio): pwa bg color and name --- aio/src/pwa-manifest.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aio/src/pwa-manifest.json b/aio/src/pwa-manifest.json index 63cabcd814..3b320e2608 100644 --- a/aio/src/pwa-manifest.json +++ b/aio/src/pwa-manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Angular Docs", - "name": "Angular Docs", + "short_name": "Angular", + "name": "Angular", "icons": [ { "src":"assets/images/favicons/favicon-194x194.png", @@ -9,7 +9,7 @@ } ], "start_url": "/?utm_source=homescreen", - "background_color": "#fafafa", + "background_color": "#1976D2", "theme_color": "#1976d2", "display": "standalone" } From 624b5a5f836d0091b9884cf8bf063bf4409ba325 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Wed, 12 Apr 2017 15:19:10 -0700 Subject: [PATCH 0046/1039] feat(aio): filetree and top bar hover styles --- aio/src/styles/0-base/_typography.scss | 1 + aio/src/styles/1-layouts/_top-menu.scss | 11 ++++++++--- aio/src/styles/2-modules/_filetree.scss | 9 +++++---- aio/src/styles/_constants.scss | 1 + 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/aio/src/styles/0-base/_typography.scss b/aio/src/styles/0-base/_typography.scss index 0b8570ec57..cc6bebee78 100755 --- a/aio/src/styles/0-base/_typography.scss +++ b/aio/src/styles/0-base/_typography.scss @@ -84,6 +84,7 @@ color: white; font-family: $main-font; text-transform: uppercase; + padding: 21px 0; } strong { diff --git a/aio/src/styles/1-layouts/_top-menu.scss b/aio/src/styles/1-layouts/_top-menu.scss index 394df75984..c33431c317 100644 --- a/aio/src/styles/1-layouts/_top-menu.scss +++ b/aio/src/styles/1-layouts/_top-menu.scss @@ -4,8 +4,7 @@ } .nav-link { - margin-right: 10px; - margin-left: 20px; + margin: 0 16px; cursor: pointer; } @@ -49,7 +48,13 @@ aio-top-menu { li { list-style-type: none; - padding: 0px; + line-height: 64px; + height: 64px; + cursor: pointer; + + &:hover { + background-color: $accentblue; + } } } } diff --git a/aio/src/styles/2-modules/_filetree.scss b/aio/src/styles/2-modules/_filetree.scss index 94dc4a4fee..e3b4ae8499 100644 --- a/aio/src/styles/2-modules/_filetree.scss +++ b/aio/src/styles/2-modules/_filetree.scss @@ -1,15 +1,16 @@ .filetree { background: $offwhite; - box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12); - border: 2px solid $lightgray; + border: 4px solid $lightgray; + border-radius: 4px; margin: 0 0 24px 0; - padding: 24px; + padding: 16px 32px; .file { display: block; font-family: $main-font; + letter-spacing: 0.3px; line-height: 32px; - color: $lightgray; + color: $darkgray; } .children { diff --git a/aio/src/styles/_constants.scss b/aio/src/styles/_constants.scss index 430e3af12d..24f8f8372c 100755 --- a/aio/src/styles/_constants.scss +++ b/aio/src/styles/_constants.scss @@ -7,6 +7,7 @@ $code-font: "Droid Sans Mono", monospace; // COLOR PALETTE $blue: #1976D2; +$accentblue: #1E88E5; $brightred: #DD0031; $darkred: #C3002F; $white: #FFFFFF; From a2bdcc9ba81f42fb8b7e63fbab50811ad2a3178d Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Fri, 14 Apr 2017 10:54:48 -0700 Subject: [PATCH 0047/1039] feat(aio): callout, code ex header styles lighten up code ex background --- aio/src/styles/2-modules/_callout.scss | 47 ++++++++++++++++++++++ aio/src/styles/2-modules/_code.scss | 5 +-- aio/src/styles/2-modules/_modules-dir.scss | 3 +- 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 aio/src/styles/2-modules/_callout.scss diff --git a/aio/src/styles/2-modules/_callout.scss b/aio/src/styles/2-modules/_callout.scss new file mode 100644 index 0000000000..9de5d2295f --- /dev/null +++ b/aio/src/styles/2-modules/_callout.scss @@ -0,0 +1,47 @@ +.callout { + @extend .alert; + padding: 0px; + border-left: none !important; + border-radius: 4px; + + header { + color: $white; + line-height: 24px; + font-weight: 500; + text-transform: uppercase; + border-radius: 4px 4px 0 0; + } + + p { + padding: 16px; + margin: 0px; + font-size: 14px; + } + + &.is-critical { + border-color: $brightred; + background: rgba($brightred, 0.05); + + header { + background: $brightred; + } + } + + &.is-important { + border-color: $orange; + background: rgba($orange, 0.05); + + header { + background: $amber-700; + } + } + + &.is-helpful { + border-color: $blue; + background: rgba($blue, 0.05); + + header { + background: $blue; + } + } +} \ No newline at end of file diff --git a/aio/src/styles/2-modules/_code.scss b/aio/src/styles/2-modules/_code.scss index bef542fe44..49e60a8a16 100644 --- a/aio/src/styles/2-modules/_code.scss +++ b/aio/src/styles/2-modules/_code.scss @@ -1,6 +1,6 @@ code-example, code-tabs md-tab-body { - background-color: $backgroundgray; + background-color: rgba($backgroundgray, 0.2); border: 0.5px solid $lightgray; border-radius: 5px; color: $darkgray; @@ -20,8 +20,7 @@ code-example.code-shell, code-example[language=sh], code-example[language=bash] } code-example header { - background-color: $mediumgray; - border: 0.5px solid $mediumgray; + background-color: $accentblue; border-radius: 5px 5px 0 0; color: $offwhite; font-size: 16px; diff --git a/aio/src/styles/2-modules/_modules-dir.scss b/aio/src/styles/2-modules/_modules-dir.scss index 69007c7ccb..b9afb37424 100644 --- a/aio/src/styles/2-modules/_modules-dir.scss +++ b/aio/src/styles/2-modules/_modules-dir.scss @@ -20,4 +20,5 @@ @import 'api-list'; @import 'hr'; @import 'live-example'; - @import 'scrollbar'; \ No newline at end of file + @import 'scrollbar'; + @import 'callout'; \ No newline at end of file From 7d986ae5dc62587cccd11d442186ac3e6f2db97b Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Fri, 14 Apr 2017 12:28:39 -0700 Subject: [PATCH 0048/1039] feat(aio): smaller typography, code header pad --- aio/src/styles/0-base/_typography.scss | 24 +++++++++++++----------- aio/src/styles/1-layouts/_footer.scss | 1 + aio/src/styles/2-modules/_code.scss | 2 +- aio/src/styles/2-modules/_table.scss | 1 + 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/aio/src/styles/0-base/_typography.scss b/aio/src/styles/0-base/_typography.scss index cc6bebee78..ba5f327cb6 100755 --- a/aio/src/styles/0-base/_typography.scss +++ b/aio/src/styles/0-base/_typography.scss @@ -7,12 +7,14 @@ margin: 0; color: $darkgray; font-size: 14px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } h1 { display:inline-block; - font-size: 36px; - font-weight: 400; + font-size: 24px; + font-weight: 500; margin: 8px 0px; } @@ -21,38 +23,38 @@ display: block; height: 1px; width: 40%; - margin: 32px 0px 10px; + margin: 24px 0px 10px; background: $lightgray; } h2 { - font-size: 30px; + font-size: 20px; font-weight: 500; margin: 32px 0px 24px; } h3 { - font-size: 24px; - font-weight: 500; + font-size: 20px; + font-weight: 400; margin: 24px 0px; } h4 { - font-size: 24px; + font-size: 18px; font-weight: 400; margin: 8px 0px; } h5 { - font-size: 18px; + font-size: 16px; font-weight: 500; margin: 8px 0px; } h6 { color: $mediumgray; - font-size: 18px; - font-weight: 400; + font-size: 16px; + font-weight: 500; margin: 8px 0px; } @@ -79,7 +81,7 @@ } .mat-toolbar-row a { - font-size: 18px; + font-size: 16px; font-weight: 300; color: white; font-family: $main-font; diff --git a/aio/src/styles/1-layouts/_footer.scss b/aio/src/styles/1-layouts/_footer.scss index 7344d68154..f6c6b620a5 100644 --- a/aio/src/styles/1-layouts/_footer.scss +++ b/aio/src/styles/1-layouts/_footer.scss @@ -20,6 +20,7 @@ footer { a { color: $offwhite; + font-weight: 300; text-decoration: none; z-index: 20; position: relative; diff --git a/aio/src/styles/2-modules/_code.scss b/aio/src/styles/2-modules/_code.scss index 49e60a8a16..56678ee8e5 100644 --- a/aio/src/styles/2-modules/_code.scss +++ b/aio/src/styles/2-modules/_code.scss @@ -24,7 +24,7 @@ code-example header { border-radius: 5px 5px 0 0; color: $offwhite; font-size: 16px; - padding: 10px; + padding: 8px 16px; margin: -17px; } diff --git a/aio/src/styles/2-modules/_table.scss b/aio/src/styles/2-modules/_table.scss index 31e2a309b6..91d44d1d56 100644 --- a/aio/src/styles/2-modules/_table.scss +++ b/aio/src/styles/2-modules/_table.scss @@ -37,6 +37,7 @@ table { padding: 16px 32px; text-align: left; line-height: 24px; + vertical-align: top; @media (max-width: 480px) { &:before { From b6a0098aa3061921c7a8cdb86f30039bca1c5e0a Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Sat, 15 Apr 2017 15:30:49 +0300 Subject: [PATCH 0049/1039] ci(aio): make `deploy-staging.sh` executable --- aio/scripts/deploy-staging.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 aio/scripts/deploy-staging.sh diff --git a/aio/scripts/deploy-staging.sh b/aio/scripts/deploy-staging.sh old mode 100644 new mode 100755 From 46b0c7a18c3466806daf41504fa74716991f138c Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 15 Apr 2017 10:39:52 +0100 Subject: [PATCH 0050/1039] docs(aio): fix links to quickstart on homepage --- aio/content/marketing/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aio/content/marketing/index.html b/aio/content/marketing/index.html index f5bc61192c..e82d4c5bf0 100755 --- a/aio/content/marketing/index.html +++ b/aio/content/marketing/index.html @@ -2,7 +2,7 @@
-
From 196203f6d76ac5240d1485dbd9701bfb026eab56 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Fri, 14 Apr 2017 17:53:49 -0700 Subject: [PATCH 0051/1039] feat(aio): implement resources with resources.json --- aio/content/marketing/resources.html | 2 + aio/content/marketing/resources.json | 582 ++++++++++++++++++ .../marketing/{resources.md => resources2.md} | 0 .../contributor/contributor-list.component.ts | 29 +- .../contributor/contributor.service.spec.ts | 118 ++++ .../contributor/contributor.service.ts | 40 +- .../contributor/contributors.model.ts | 6 + aio/src/app/embedded/embedded.module.ts | 7 +- .../resource/resource-list.component.html | 42 ++ .../resource/resource-list.component.spec.ts | 81 +++ .../resource/resource-list.component.ts | 37 ++ .../app/embedded/resource/resource.model.ts | 23 + .../resource/resource.service.spec.ts | 161 +++++ .../app/embedded/resource/resource.service.ts | 83 +++ .../app/navigation/navigation.service.spec.ts | 5 + aio/src/app/shared/location.service.spec.ts | 2 +- .../styles/1-layouts/_marketing-layout.scss | 6 +- aio/src/styles/2-modules/_modules-dir.scss | 3 +- aio/src/styles/2-modules/_resources.scss | 238 +++++++ aio/src/styles/_constants.scss | 1 + aio/transforms/angular.io-package/index.js | 8 +- 21 files changed, 1445 insertions(+), 29 deletions(-) create mode 100644 aio/content/marketing/resources.html create mode 100644 aio/content/marketing/resources.json rename aio/content/marketing/{resources.md => resources2.md} (100%) create mode 100644 aio/src/app/embedded/contributor/contributor.service.spec.ts create mode 100644 aio/src/app/embedded/resource/resource-list.component.html create mode 100644 aio/src/app/embedded/resource/resource-list.component.spec.ts create mode 100644 aio/src/app/embedded/resource/resource-list.component.ts create mode 100644 aio/src/app/embedded/resource/resource.model.ts create mode 100644 aio/src/app/embedded/resource/resource.service.spec.ts create mode 100644 aio/src/app/embedded/resource/resource.service.ts create mode 100644 aio/src/styles/2-modules/_resources.scss diff --git a/aio/content/marketing/resources.html b/aio/content/marketing/resources.html new file mode 100644 index 0000000000..4d38a75de9 --- /dev/null +++ b/aio/content/marketing/resources.html @@ -0,0 +1,2 @@ +

Explore Angular Resources

+ diff --git a/aio/content/marketing/resources.json b/aio/content/marketing/resources.json new file mode 100644 index 0000000000..fd50fbe6e8 --- /dev/null +++ b/aio/content/marketing/resources.json @@ -0,0 +1,582 @@ +{ + "Community": { + "order": 3, + "subCategories": { + "Community Curations": { + "order": 1, + "resources": { + "awesome-angular-components": { + "desc": "A community index of components and libraries maintained on GitHub", + "rev": true, + "title": "Catalog of Angular Components & Libraries", + "url": "https://github.com/brillout/awesome-angular-components" + } + } + }, + "Groups": { + "order": 2, + "resources": { + "sldkjfslkjfslkjdsklj": { + "desc": "Meetup in Barcelona, Spain. Express your motivations, share your ideas and play together creating awesome things in team.", + "rev": true, + "title": "Angular Beers", + "url": "http://www.meetup.com/AngularJS-Beers/" + }, + "sldkjfslkjfslkjdskzzzlj": { + "desc": "Angular Conferences and Angular Camps in Barcelona, Spain.", + "rev": true, + "title": "Angular Camp", + "url": "http://angularcamp.org/" + } + } + }, + "Podcasts": { + "order": 3, + "resources": { + "sdfjkdkfj": { + "desc": "Adventures in Angular is a weekly podcast dedicated to the Angular platform and related technologies, tools, languages, and practices.", + "logo": "", + "rev": true, + "title": "Adventures in Angular", + "url": "https://devchat.tv/adventures-in-angular" + }, + "sdlkfjsldfkj": { + "desc": "Weekly video podcast hosted by Jeff Whelpley with all the latest and greatest happenings in the wild world of Angular.", + "logo": "", + "rev": true, + "title": "AngularAir", + "url": "https://angularair.com/" + }, + "sldkfjsldjf": { + "desc": "The live broadcast podcast all about JavaScript", + "logo": "", + "rev": true, + "title": "Javascript Air", + "url": "https://javascriptair.com/" + } + } + } + } + }, + "Development": { + "order": 1, + "subCategories": { + "Cross-Platform Development": { + "order": 5, + "resources": { + "a2b": { + "desc": "Angular 2 and React Native to build applications for Android and iOS", + "logo": "", + "rev": true, + "title": "ReactNative", + "url": "http://angular.github.io/react-native-renderer/" + }, + "a3b": { + "desc": "Ionic offers a library of mobile-optimized HTML, CSS and JS components and tools for building highly interactive native and progressive web apps.", + "logo": "http://ionicframework.com/img/ionic-logo-white.svg", + "rev": true, + "title": "Ionic", + "url": "http://ionicframework.com/docs/v2/" + }, + "a4b": { + "desc": "Electron Platform for Angular 2.", + "logo": "", + "rev": true, + "title": "Electron", + "url": "http://github.com/angular/angular-electron" + }, + "ab": { + "desc": "NativeScript is how you build cross-platform, native iOS and Android apps with Angular and TypeScript. Get 100% access to native APIs via JavaScript and reuse of packages from NPM, CocoaPods and Gradle. Open source and backed by Telerik.​​​", + "logo": "", + "rev": true, + "title": "NativeScript", + "url": "https://github.com/NativeScript/nativescript-angular" + }, + "ab5": { + "desc": "An Universal Windows App (uwp) powered by Angular 2", + "logo": "", + "rev": true, + "title": "Windows (UWP)", + "url": "http://github.com/preboot/angular2-universal-windows-app" + } + } + }, + "Data Libraries": { + "order": 3, + "resources": { + "-KLIzHDRfiB3d7W7vk-e": { + "desc": "Reactive Extensions for angular2", + "rev": true, + "title": "ngrx", + "url": "http://github.com/ngrx" + }, + "ab": { + "desc": "The official library for Firebase and Angular 2", + "logo": "", + "rev": true, + "title": "Angular Fire", + "url": "https://github.com/angular/angularfire2" + }, + "ab2": { + "desc": "Use Angular 2 and Meteor to build full-stack JavaScript apps for Mobile and Desktop.", + "logo": "http://www.angular-meteor.com/images/logo.png", + "rev": true, + "title": "Meteor", + "url": "http://www.angular-meteor.com/angular2" + }, + "ab3": { + "desc": "Apollo is a data stack for modern apps, built with GraphQL.", + "logo": "http://docs.apollostack.com/logo/large.png", + "rev": true, + "title": "Apollo", + "url": "http://docs.apollostack.com/apollo-client/angular2.html" + } + } + }, + "IDEs": { + "order": 1, + "resources": { + "ab": { + "desc": "VS Code is a Free, Lightweight Tool for Editing and Debugging Web Apps.", + "logo": "", + "rev": true, + "title": "Visual Studio Code", + "url": "http://code.visualstudio.com/" + }, + "ab2": { + "desc": "Lightweight yet powerful IDE, perfectly equipped for complex client-side development and server-side development with Node.js", + "logo": "", + "rev": true, + "title": "Webstorm", + "url": "https://www.jetbrains.com/webstorm/" + }, + "ab3": { + "desc": "Capable and Ergonomic Java * IDE", + "logo": "", + "rev": true, + "title": "IntelliJ IDEA", + "url": "https://www.jetbrains.com/idea/" + }, + "angular-ide": { + "desc": "Built first and foremost for Angular. Turnkey setup for beginners; powerful for experts.", + "rev": true, + "title": "Angular IDE by Webclipse", + "url": "https://www.genuitec.com/products/angular-ide" + } + } + }, + "Tooling": { + "order": 2, + "resources": { + "-KLIzHfBEr1qMMUDxfq3": { + "desc": "Generate an Angular 2 CRUD application from an existing database schema", + "rev": true, + "title": "Celerio Angular Quickstart", + "url": "https://github.com/jaxio/celerio-angular-quickstart" + }, + "a1": { + "desc": "A Google Chrome Dev Tools extension for debugging Angular 2 applications.", + "logo": "https://augury.angular.io/images/augury-logo.svg", + "rev": true, + "title": "Augury", + "url": "http://augury.angular.io/" + }, + "b1": { + "desc": "Server-side Rendering for Angular 2 apps.", + "logo": "https://cloud.githubusercontent.com/assets/1016365/10639063/138338bc-7806-11e5-8057-d34c75f3cafc.png", + "rev": true, + "title": "Angular Universal", + "url": "https://github.com/angular/universal" + }, + "c1": { + "desc": "Lightweight development only node server", + "logo": "", + "rev": true, + "title": "Lite-server", + "url": "https://github.com/johnpapa/lite-server" + }, + "cli": { + "desc": "The official Angular CLI makes it easy to create and develop applications from initial commit to production deployment. It already follows our best practices right out of the box!", + "rev": true, + "title": "Angular CLI", + "url": "https://cli.angular.io" + }, + "d1": { + "desc": "A set of tslint rules for static code analysis of Angular 2 TypeScript projects.", + "logo": "", + "rev": true, + "title": "Codelyzer", + "url": "https://github.com/mgechev/codelyzer" + }, + "e1": { + "desc": "This package provides facilities for developers building Angular 2 applications on ASP.NET.", + "logo": "", + "rev": true, + "title": "Universal for ASP.NET", + "url": "https://github.com/aspnet/nodeservices" + } + } + }, + "UI Components": { + "order": 4, + "resources": { + "234237": { + "desc": "UX guidelines, HTML/CSS framework, and Angular 2 components working together to craft exceptional experiences", + "rev": true, + "title": "Clarity Design System", + "url": "https://vmware.github.io/clarity/" + }, + "-KLIzI9BTvvP_hUwutXk": { + "desc": "UI components for Angular using Semantic UI", + "rev": true, + "title": "Semantic UI", + "url": "https://github.com/vladotesanovic/ngSemantic" + }, + "-KMVB8P4TDfht8c0L1AE": { + "desc": "The Angular 2 version of the Angular UI Bootstrap library. This library is being built from scratch in Typescript using the Bootstrap 4 CSS framework.", + "rev": true, + "title": "ng-bootstrap", + "url": "https://ng-bootstrap.github.io/" + }, + "4ab": { + "desc": "Native Angular 2 components & directives for Lightning Design System", + "logo": "http://ng-lightning.github.io/ng-lightning/img/shield.svg", + "rev": true, + "title": "ng-lightning", + "url": "http://ng-lightning.github.io/ng-lightning/" + }, + "7ab": { + "desc": "UI components for hybrid mobile apps with bindings for both Angular 1 & 2.", + "rev": true, + "title": "Onsen UI", + "url": "https://onsen.io/v2/" + }, + "a2b": { + "desc": "PrimeNG is a collection of rich UI components for Angular 2", + "logo": "http://www.primefaces.org/primeng/showcase/resources/images/primeng.svg", + "rev": true, + "title": "Prime Faces", + "url": "http://www.primefaces.org/primeng/" + }, + "a3b": { + "desc": "One of the first major UI frameworks to support Angular 2", + "logo": "", + "rev": true, + "title": "Kendo UI", + "url": "http://www.telerik.com/kendo-angular-ui/" + }, + "a5b": { + "desc": "High-performance UI controls with the most complete Angular 2 support available. Wijmo’s controls are all written in TypeScript and have zero dependencies. FlexGrid control includes full declarative markup, including cell templates.", + "logo": "http://wijmocdn.azureedge.net/wijmositeblob/wijmo-theme/logos/wijmo-55.png", + "rev": true, + "title": "Wijmo", + "url": "http://wijmo.com/products/wijmo-5/" + }, + "a6b": { + "desc": "Material design inspired UI components for building great web apps. For mobile and desktop.", + "logo": "", + "rev": true, + "title": "Vaadin", + "url": "https://vaadin.com/elements" + }, + "a7b": { + "desc": "Native Angular2 directives for Bootstrap", + "logo": "", + "rev": true, + "title": "ng2-bootstrap", + "url": "http://valor-software.com/ng2-bootstrap/" + }, + "ab": { + "desc": "Material Design components for Angular 2", + "logo": "", + "rev": true, + "title": "Angular Material 2", + "url": "https://github.com/angular/material2" + }, + "aggrid": { + "desc": "A datagrid for Angular 2 with enterprise style features such as sorting, filtering, custom rendering, editing, grouping, aggregation and pivoting.", + "rev": true, + "title": "ag-Grid", + "url": "https://www.ag-grid.com/best-angular-2-data-grid/" + } + } + } + } + }, + "Education": { + "order": 2, + "subCategories": { + "Books": { + "order": 1, + "resources": { + "-KLI8vJ0ZkvWhqPembZ7": { + "desc": "A guide that helps developers get up to speed quickly on Angular and its accompanying technologies.", + "rev": true, + "title": "How to Get Started and Productive in Angular 2 Fast", + "url": "http://www.amazon.com/How-Started-Productive-Angular-Fast-ebook/dp/B01D3B0ET4/ref=sr_1_1_twi_kin_2?ie=UTF8&qid=1462381159&sr=8-1" + }, + "-KLIzGEp8Mh5W-FkiQnL": { + "desc": "Your quick, no-nonsense guide to building real-world apps with Angular", + "rev": true, + "title": "Learning Angular 2", + "url": "https://www.packtpub.com/web-development/learning-angular-2" + }, + "3ab": { + "desc": "More than 15 books from O'Reilly about Angular", + "rev": true, + "title": "O'Reilly Media", + "url": "https://ssearch.oreilly.com/?q=angular+2&x=0&y=0" + }, + "8ab": { + "desc": "This books shows all the steps necessary for the development of SPA (Single Page Application) applications with the brand new Angular", + "rev": true, + "title": "Practical Angular 2", + "url": "https://leanpub.com/practical-angular-2" + }, + "a2b": { + "desc": "Publications and books from Manning about Angular", + "rev": true, + "title": "Manning Publications", + "url": "https://www.manning.com/search?q=angular" + }, + "a4b": { + "desc": "From getting started with the Angular toolchain to writing applications with scalable front end architectures, this book walks you through everything you need to know.", + "rev": true, + "title": "Rangle's Angular Training Book", + "url": "http://ngcourse.rangle.io/" + }, + "a5b": { + "desc": "The in-depth, complete, and up-to-date book on Angular. Become an Angular expert today.", + "rev": true, + "title": "ng-book 2", + "url": "https://www.ng-book.com/2/" + }, + "a6b": { + "desc": "A Practical Introduction to the new Web Development Platform Angular", + "rev": true, + "title": "Angular 2 Book", + "url": "https://leanpub.com/angular2-book" + }, + "a7b": { + "desc": "This ebook will help you getting the philosophy of the framework: what comes from 1.x, what has been introduced and why", + "rev": true, + "title": "Becoming a Ninja with Angular", + "url": "https://books.ninja-squad.com/angular" + }, + "ab": { + "desc": "More than 10 books from Packt Publishing about Angular", + "rev": true, + "title": "Packt Publishing", + "url": "https://www.packtpub.com/all/?search=angular%202#" + }, + "cnoring-rxjs-fundamentals": { + "desc": "A free book that covers all facets of working with Rxjs from your first Observable to how to make your code run at optimal speed with Schedulers.", + "rev": true, + "title": "RxJS 5 Ultimate", + "url": "https://www.gitbook.com/book/chrisnoring/rxjs-5-ultimate/details" + }, + "vsavkin-angular-router": { + "desc": "This book is a comprehensive guide to the Angular router written by its designer. The book explores the library in depth, including the mental model, design constraints, subtleties of the API.", + "rev": true, + "title": "Angular Router", + "url": "https://leanpub.com/router" + }, + "vsavkin-essential-angular": { + "desc": "The book is a short, but at the same time, fairly complete overview of the key aspects of Angular written by its core contributors Victor Savkin and Jeff Cross. The book will give you a strong foundation. It will help you put all the concepts into right places. So you will get a good understanding of why the framework is the way it is.", + "rev": true, + "title": "Essential Angular", + "url": "https://gumroad.com/l/essential_angular" + } + } + }, + "Online Training": { + "order": 3, + "resources": { + "-KLIBoTWXMiBcvG0dAM6": { + "desc": "This course introduces you to the essentials of this \"superheroic\" framework, including declarative templates, two-way data binding, and dependency injection.", + "rev": true, + "title": "Angular 2: Essential Training", + "url": "https://www.lynda.com/AngularJS-tutorials/Angular-2-Essential-Training/540347-2.html" + }, + "-KLIzGq3CiFeoZUemVyE": { + "desc": "Learn the core concepts, play with the code, become a competent Angular 2 developer", + "rev": true, + "title": "Angular 2 Concepts, Code and Collective Wisdom", + "url": "https://www.udemy.com/angular-2-concepts-code-and-collective-wisdom/" + }, + "-KLIzHwg-glQLXni1hvL": { + "desc": "Spanish language Angular articles and information", + "rev": true, + "title": "Academia Binaria (español)", + "url": "http://academia-binaria.com/" + }, + "-KLIzIOgdPXzI4LMOzYP": { + "desc": "In this course, you will learn the features listed above and so much more. This amazing Angular 2 tutorial will cover the fundamentals of Angular 2 (you don’t even need to know Angular), TypeScript, and introduction to the programming concepts such as conditions, arrays, functions, directives, pipes, etc.", + "rev": true, + "title": "Eduonix Angular 2 Fundamentals", + "url": "https://www.eduonix.com/courses/Web-Development/angular-2-fundamentals-for-web-developers" + }, + "-KMUuOWwciL_S_o0fzFO": { + "desc": "The ngmigrate project is brought to you by Todd Motto, a Developer Advocate at Telerik, spreading the good word of Kendo UI, NativeScript and Angular 1 & 2. You can follow him on Twitter for questions, or even requests about this guide.", + "rev": true, + "title": "ngMigrate", + "url": "http://ngmigrate.telerik.com/" + }, + "-KN3uNQvxifu26D6WKJW": { + "category": "Education", + "desc": "Create the future of web applications by taking Angular 2 for a test drive.", + "rev": true, + "subcategory": "Online Training", + "title": "CodeSchool: Accelerating Through Angular 2", + "url": "https://www.codeschool.com/courses/accelerating-through-angular-2" + }, + "a2b": { + "desc": "Hundreds of Angular courses for all skill levels", + "logo": "", + "rev": true, + "title": "Pluralsight", + "url": "https://www.pluralsight.com/search?q=angular+2&categories=all" + }, + "ab": { + "desc": "Take this introduction to Angular 2 course, to learn the fundamentals in just two days, free of charge.", + "logo": "", + "rev": true, + "title": "Rangle.io", + "url": "https://rangle.io/services/javascript-training/training-angular1-angular2-with-ngupgrade/" + }, + "ab3": { + "desc": "Angular 2 courses hosted by Udemy", + "logo": "", + "rev": true, + "title": "Udemy", + "url": "https://www.udemy.com/courses/search/?ref=home&src=ukw&q=angular+2&lang=en" + }, + "ab4": { + "desc": "Angular 2 Fundamentals and advanced topics focused on Redux Style Angular Applications", + "logo": "", + "rev": true, + "title": "Egghead.io", + "url": "https://egghead.io/technologies/angular2" + }, + "ab5": { + "desc": "Build Web Apps with Angular 2 - recorded video content", + "logo": "", + "rev": true, + "title": "Frontend Masters", + "url": "https://frontendmasters.com/courses/angular-2/" + }, + "ac6": { + "desc": "French language Angular course covering TypeScript, ES6, Depdendency Injection, Observables, and more.", + "rev": true, + "title": "Wishtack's Angular Course (francais)", + "url": "http://courses.wishtack.com/angular-2/ecmascript-6" + }, + "angular-love": { + "desc": "Polish language Angular articles and information", + "rev": true, + "title": "angular.love (Polski)", + "url": "http://www.angular.love/" + }, + "angular2forms": { + "desc": "Learn about how to use Reactive Forms with Angular.", + "rev": true, + "title": "Angular 2 Forms: Data Binding and Validation", + "url": "https://www.lynda.com/AngularJS-tutorials/Angular-2-Forms-Data-Binding-Validation/461451-2.html" + }, + "learn-angular-fr": { + "desc": "French language Angular content.", + "rev": true, + "title": "Learn Angular (francais)", + "url": "http://www.learn-angular.fr/" + }, + "sad200": { + "desc": "Free Angular training delivered by SFEIR in France", + "rev": true, + "title": "SFEIR School (French)", + "url": "https://school.sfeir.com/project/sad-200/" + }, + "toddmotto-ultimateangular": { + "desc": "Online courses providing in-depth coverage of the Angular ecosystem, AngularJS, Angular and TypeScript, with functional code samples and a full-featured seed environment. Get a deep understanding of Angular and TypeScript from foundation to functional application, then move on to advanced topics with Todd Motto and collaborators.", + "rev": true, + "title": "Ultimate Angular", + "url": "https://ultimateangular.com/" + } + } + }, + "Workshops & Onsite Training": { + "order": 2, + "resources": { + "-KLIBo3ANF3-1B9wxsoB": { + "desc": "Angular 2 Classes from Intertech in Minnesota", + "rev": true, + "title": "Intertech", + "url": "http://www.intertech.com/Training/Web-Dev/AngularJS/AngularJS/Angular-2-Training" + }, + "-KLIBoFWStce29UCwkvY": { + "desc": "Private Angular 2 Training and Mentoring", + "rev": true, + "title": "Chariot Solutions", + "url": "http://chariotsolutions.com/course/angular2-workshop-fundamentals-architecture/" + }, + "-KLIBoN0p9be3kwC6-ga": { + "desc": "Angular Academy is a 2 day hands-on public course given in-person across Canada!", + "rev": true, + "title": "Angular Academy (Canada)", + "url": "http://www.angularacademy.ca" + }, + "-KLIBo_lm-WrK1Sjtt-2": { + "desc": "Basic and Advanced training across Europe in German", + "rev": true, + "title": "TheCodeCampus (German)", + "url": "https://www.thecodecampus.de/#!/angularjs" + }, + "-KLIzFhfGKi1xttqJ7Uh": { + "desc": "4 day in-depth Angular 2 training in Israel", + "rev": true, + "title": "ng-course (Israel)", + "url": "http://ng-course.org/" + }, + "-KLIzIcRoDq3TzCJWnYc": { + "desc": "Virtual and in-person training in Canada and the US", + "rev": true, + "title": "Web Age Solutions", + "url": "http://www.webagesolutions.com/courses/WA2533-angular-2-programming" + }, + "500tech": { + "desc": "Learn from 500Tech, an Angular consultancy in Israel. This course was built by an expert developer, who lives and breathes Angular 2, and has practical experience with real world large scale Angular 2 apps.", + "rev": true, + "title": "Angular Hands-on Course (Israel)", + "url": "http://angular2.courses.500tech.com/" + }, + "9ab": { + "desc": "OnSite Training From the Authors of \"Become A Ninja with Angular 2\"", + "rev": true, + "title": "Ninja Squad", + "url": "http://ninja-squad.com/formations/formation-angular2" + }, + "a2b": { + "desc": "Angular Boot Camp covers introductory and intermediate content. It includes extensive workshop session, with hands-on help from our experienced developer-trainers. At the end of this class, student are usually able to use AngularJS to make an end-to-end, working application.", + "logo": "", + "rev": true, + "title": "Angular Boot Camp", + "url": "https://angularbootcamp.com" + }, + "ab": { + "desc": "With Rangle’s Custom Training, you can cover Angular 2 in comprehensive detail, on your premises or theirs. Learn directly from Angular 2 experts who will tailor course material to suit your specific application needs.", + "logo": "", + "rev": true, + "title": "Rangle.io", + "url": "http://rangle.io/services/javascript-training/angular2-training/" + }, + "ab3": { + "desc": "Trainings & Code Reviews. We help people to get a deep understanding of different technologies through trainings and code reviews. Our services can be arranged online, making it possible to join in from anywhere in the world, or on-site to get the best experience possible.", + "logo": "", + "rev": true, + "title": "Thoughtram", + "url": "http://thoughtram.io/" + } + } + } + } + } +} diff --git a/aio/content/marketing/resources.md b/aio/content/marketing/resources2.md similarity index 100% rename from aio/content/marketing/resources.md rename to aio/content/marketing/resources2.md diff --git a/aio/src/app/embedded/contributor/contributor-list.component.ts b/aio/src/app/embedded/contributor/contributor-list.component.ts index 8e6579f68e..31a90d2227 100644 --- a/aio/src/app/embedded/contributor/contributor-list.component.ts +++ b/aio/src/app/embedded/contributor/contributor-list.component.ts @@ -1,25 +1,22 @@ -import { Component, OnInit } from '@angular/core'; -import { Contributor } from './contributors.model'; +import { Component } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; + +import { ContributorGroup } from './contributors.model'; import { ContributorService } from './contributor.service'; @Component({ - selector: `aio-contributor-list`, + selector: 'aio-contributor-list', template: ` -
-

{{group}}

- +
+

{{group.name}}

+
` }) -export class ContributorListComponent implements OnInit { - contributorGroups = new Map(); - groups: string[]; +export class ContributorListComponent { + groups: Observable; - constructor(private contributorService: ContributorService) { } - - ngOnInit() { - this.contributorService.contributors.subscribe(cgs => { - this.groups = ['Lead', 'Google', 'Community']; - this.contributorGroups = cgs; - }); + constructor(private contributorService: ContributorService) { + this.groups = this.contributorService.contributors; } } diff --git a/aio/src/app/embedded/contributor/contributor.service.spec.ts b/aio/src/app/embedded/contributor/contributor.service.spec.ts new file mode 100644 index 0000000000..75bc5d3b7c --- /dev/null +++ b/aio/src/app/embedded/contributor/contributor.service.spec.ts @@ -0,0 +1,118 @@ +import { ReflectiveInjector } from '@angular/core'; +import { Http, ConnectionBackend, RequestOptions, BaseRequestOptions, Response, ResponseOptions } from '@angular/http'; +import { MockBackend } from '@angular/http/testing'; + +import { ContributorService } from './contributor.service'; +import { Contributor, ContributorGroup } from './contributors.model'; +import { Logger } from 'app/shared/logger.service'; + +describe('ContributorService', () => { + + let injector: ReflectiveInjector; + let backend: MockBackend; + let contribService: ContributorService; + + function createResponse(body: any) { + return new Response(new ResponseOptions({ body: JSON.stringify(body) })); + } + + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ + ContributorService, + { provide: ConnectionBackend, useClass: MockBackend }, + { provide: RequestOptions, useClass: BaseRequestOptions }, + Http, + Logger + ]); + + backend = injector.get(ConnectionBackend); + contribService = injector.get(ContributorService); + }); + + it('should be creatable', () => { + expect(contribService).toBeTruthy(); + }); + + it('should make a single connection to the server', () => { + expect(backend.connectionsArray.length).toEqual(1); + expect(backend.connectionsArray[0].request.url).toEqual('content/contributors.json'); + }); + + describe('#contributors', () => { + + let contribs: ContributorGroup[]; + let testData: any; + + beforeEach(() => { + testData = getTestContribs(); + backend.connectionsArray[0].mockRespond(createResponse(testData)); + contribService.contributors.subscribe(results => contribs = results); + }); + + it('contributors observable should complete', () => { + let completed = false; + contribService.contributors.subscribe(null, null, () => completed = true); + expect(true).toBe(true, 'observable completed'); + }); + + it('should reshape the contributor json to expected result', () => { + const groupNames = contribs.map(g => g.name); + expect(groupNames).toEqual(['Lead', 'Google', 'Community']); + }); + + it('should have expected "Lead" contribs in order', () => { + const leads = contribs[0]; + const actualLeadNames = leads.contributors.map(l => l.name).join(','); + const expectedLeadNames = [testData.igor, testData.misko, testData.naomi].map(l => l.name).join(','); + expect(actualLeadNames).toEqual(expectedLeadNames); + }); + }); + + it('should do WHAT(?) if the request fails'); +}); + +function getTestContribs() { + // tslint:disable:quotemark + return { + "misko": { + "name": "Miško Hevery", + "picture": "misko.jpg", + "twitter": "mhevery", + "website": "http://misko.hevery.com", + "bio": "Miško Hevery is the creator of AngularJS framework.", + "group": "Lead" + }, + "igor": { + "name": "Igor Minar", + "picture": "igor-minar.jpg", + "twitter": "IgorMinar", + "website": "https://google.com/+IgorMinar", + "bio": "Igor is a software engineer at Google.", + "group": "Lead" + }, + "kara": { + "name": "Kara Erickson", + "picture": "kara-erickson.jpg", + "twitter": "karaforthewin", + "website": "https://github.com/kara", + "bio": "Kara is a software engineer on the Angular team at Google and a co-organizer of the Angular-SF Meetup. ", + "group": "Google" + }, + "jeffcross": { + "name": "Jeff Cross", + "picture": "jeff-cross.jpg", + "twitter": "jeffbcross", + "website": "https://twitter.com/jeffbcross", + "bio": "Jeff was one of the earliest core team members on AngularJS.", + "group": "Community" + }, + "naomi": { + "name": "Naomi Black", + "picture": "naomi.jpg", + "twitter": "naomitraveller", + "website": "http://google.com/+NaomiBlack", + "bio": "Naomi is Angular's TPM generalist and jack-of-all-trades.", + "group": "Lead" + } + }; +} diff --git a/aio/src/app/embedded/contributor/contributor.service.ts b/aio/src/app/embedded/contributor/contributor.service.ts index 69a57db92c..ae060cf4ac 100644 --- a/aio/src/app/embedded/contributor/contributor.service.ts +++ b/aio/src/app/embedded/contributor/contributor.service.ts @@ -6,13 +6,14 @@ import 'rxjs/add/operator/map'; import 'rxjs/add/operator/publishLast'; import { Logger } from 'app/shared/logger.service'; -import { Contributor } from './contributors.model'; +import { Contributor, ContributorGroup } from './contributors.model'; const contributorsPath = 'content/contributors.json'; +const knownGroups = ['Lead', 'Google', 'Community']; @Injectable() export class ContributorService { - contributors: Observable>; + contributors: Observable; constructor(private http: Http, private logger: Logger) { this.contributors = this.getContributors(); @@ -21,24 +22,49 @@ export class ContributorService { private getContributors() { const contributors = this.http.get(contributorsPath) .map(res => res.json()) - .map(contribs => { - const contribGroups = new Map(); + // Create group map + .map(contribs => { + const contribMap = new Map(); Object.keys(contribs).forEach(key => { const contributor = contribs[key]; const group = contributor.group; - const contribGroup = contribGroups[group]; + const contribGroup = contribMap[group]; if (contribGroup) { contribGroup.push(contributor); } else { - contribGroups[group] = [contributor]; + contribMap[group] = [contributor]; } }); - return contribGroups; + return contribMap; + }) + + // Flatten group map into sorted group array of sorted contributors + .map(cmap => { + return Object.keys(cmap).map(key => { + const order = knownGroups.indexOf(key); + return { + name: key, + order: order === -1 ? knownGroups.length : order, + contributors: cmap[key].sort(compareContributors) + } as ContributorGroup; + }) + .sort(compareGroups); }) .publishLast(); + contributors.connect(); return contributors; } } + +function compareContributors(l: Contributor, r: Contributor) { + return l.name.toUpperCase() > r.name.toUpperCase() ? 1 : -1; +} + +function compareGroups(l: ContributorGroup, r: ContributorGroup) { + return l.order === r.order ? + (l.name > r.name ? 1 : -1) : + l.order > r.order ? 1 : -1; +} diff --git a/aio/src/app/embedded/contributor/contributors.model.ts b/aio/src/app/embedded/contributor/contributors.model.ts index f3743f6379..cddfbb00e6 100644 --- a/aio/src/app/embedded/contributor/contributors.model.ts +++ b/aio/src/app/embedded/contributor/contributors.model.ts @@ -1,3 +1,9 @@ +export class ContributorGroup { + name: string; + order: number; + contributors: Contributor[]; +} + export class Contributor { group: string; name: string; diff --git a/aio/src/app/embedded/embedded.module.ts b/aio/src/app/embedded/embedded.module.ts index aaf358ec52..18b5b56455 100644 --- a/aio/src/app/embedded/embedded.module.ts +++ b/aio/src/app/embedded/embedded.module.ts @@ -20,13 +20,15 @@ import { ContributorListComponent } from './contributor/contributor-list.compone import { ContributorComponent } from './contributor/contributor.component'; import { DocTitleComponent } from './doc-title.component'; import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example/live-example.component'; +import { ResourceListComponent } from './resource/resource-list.component'; +import { ResourceService } from './resource/resource.service'; /** Components that can be embedded in docs * such as CodeExampleComponent, LiveExampleComponent,... */ export const embeddedComponents: any[] = [ ApiListComponent, CodeExampleComponent, CodeTabsComponent, - ContributorListComponent, DocTitleComponent, LiveExampleComponent + ContributorListComponent, DocTitleComponent, LiveExampleComponent, ResourceListComponent ]; /** Injectable class w/ property returning components that can be embedded in docs */ @@ -46,7 +48,8 @@ export class EmbeddedComponents { ContributorService, CopierService, EmbeddedComponents, - PrettyPrinter + PrettyPrinter, + ResourceService ], entryComponents: [ embeddedComponents ] }) diff --git a/aio/src/app/embedded/resource/resource-list.component.html b/aio/src/app/embedded/resource/resource-list.component.html new file mode 100644 index 0000000000..0c9faa405c --- /dev/null +++ b/aio/src/app/embedded/resource/resource-list.component.html @@ -0,0 +1,42 @@ +
+
+
+
+
+ +

{{category.title}}

+
+ + +
+
+
+ + + +
diff --git a/aio/src/app/embedded/resource/resource-list.component.spec.ts b/aio/src/app/embedded/resource/resource-list.component.spec.ts new file mode 100644 index 0000000000..3f3d7387b1 --- /dev/null +++ b/aio/src/app/embedded/resource/resource-list.component.spec.ts @@ -0,0 +1,81 @@ +import { ReflectiveInjector } from '@angular/core'; +import { PlatformLocation } from '@angular/common'; + +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; + +import { ResourceListComponent } from './resource-list.component'; +import { ResourceService } from './resource.service'; + +import { Category } from './resource.model'; + +// Testing the component class behaviors, independent of its template +// Let e2e tests verify how it displays. +describe('ResourceListComponent', () => { + + let injector: ReflectiveInjector; + let location: TestPlatformLocation; + let resourceService: TestResourceService; + + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ + ResourceListComponent, + {provide: PlatformLocation, useClass: TestPlatformLocation }, + {provide: ResourceService, useClass: TestResourceService } + ]); + + location = injector.get(PlatformLocation); + resourceService = injector.get(ResourceService); + }); + + it('should set the location w/o leading slashes', () => { + location.pathname = '////resources'; + const component = getComponent(); + expect(component.location).toBe('resources'); + }); + + it('href(id) should return the expected href', () => { + location.pathname = '////resources'; + const component = getComponent(); + expect(component.href({id: 'foo'})).toBe('resources#foo'); + }); + + it('should set scroll position to zero when no target element', () => { + const component = getComponent(); + component.onScroll(undefined); + expect(component.scrollPos).toBe(0); + }); + + it('should set scroll position to element.scrollTop when that is defined', () => { + const component = getComponent(); + component.onScroll({scrollTop: 42}); + expect(component.scrollPos).toBe(42); + }); + + it('should set scroll position to element.body.scrollTop when that is defined', () => { + const component = getComponent(); + component.onScroll({body: {scrollTop: 42}}); + expect(component.scrollPos).toBe(42); + }); + + it('should set scroll position to 0 when no target.body.scrollTop defined', () => { + const component = getComponent(); + component.onScroll({body: {}}); + expect(component.scrollPos).toBe(0); + }); + + //// Test Helpers //// + function getComponent(): ResourceListComponent { return injector.get(ResourceListComponent); } + + class TestPlatformLocation { + pathname = 'resources'; + } + + class TestResourceService { + categories = of(getTestData); + } + + function getTestData(): Category[] { + return []; // Not interested in the data in these tests + } +}); diff --git a/aio/src/app/embedded/resource/resource-list.component.ts b/aio/src/app/embedded/resource/resource-list.component.ts new file mode 100644 index 0000000000..2b424db538 --- /dev/null +++ b/aio/src/app/embedded/resource/resource-list.component.ts @@ -0,0 +1,37 @@ +import { Component, HostListener, OnInit } from '@angular/core'; +import { PlatformLocation } from '@angular/common'; + +import { Category } from './resource.model'; +import { ResourceService } from './resource.service'; + +@Component({ + selector: 'aio-resource-list', + templateUrl: 'resource-list.component.html' +}) +export class ResourceListComponent implements OnInit { + + categories: Category[]; + location: string; + scrollPos = 0; + + constructor( + location: PlatformLocation, + private resourceService: ResourceService) { + this.location = location.pathname.replace(/^\/+/, ''); + } + + href(cat: {id: string}) { + return this.location + '#' + cat.id; + } + + ngOnInit() { + // Not using async pipe because cats appear twice in template + // No need to unsubscribe because categories observable completes. + this.resourceService.categories.subscribe(cats => this.categories = cats); + } + + @HostListener('window:scroll', ['$event.target']) + onScroll(target: any) { + this.scrollPos = target ? target.scrollTop || target.body.scrollTop || 0 : 0; + } +} diff --git a/aio/src/app/embedded/resource/resource.model.ts b/aio/src/app/embedded/resource/resource.model.ts new file mode 100644 index 0000000000..87c299a01e --- /dev/null +++ b/aio/src/app/embedded/resource/resource.model.ts @@ -0,0 +1,23 @@ +export class Category { + id: string; // "education" + title: string; // "Education" + order: number; // 2 + subCategories: SubCategory[]; +} + +export class SubCategory { + id: string; // "books" + title: string; // "Books" + order: number; // 1 + resources: Resource[]; +} + +export class Resource { + category: string; // "Education" + subCategory: string; // "Books" + id: string; // "-KLI8vJ0ZkvWhqPembZ7" + desc: string; // "This books shows all the steps necessary for the development of SPA" + rev: boolean; // true (always true in the original) + title: string; // "Practical Angular 2", + url: string; // "https://leanpub.com/practical-angular-2" +} diff --git a/aio/src/app/embedded/resource/resource.service.spec.ts b/aio/src/app/embedded/resource/resource.service.spec.ts new file mode 100644 index 0000000000..b6073e2884 --- /dev/null +++ b/aio/src/app/embedded/resource/resource.service.spec.ts @@ -0,0 +1,161 @@ +import { ReflectiveInjector } from '@angular/core'; +import { Http, ConnectionBackend, RequestOptions, BaseRequestOptions, Response, ResponseOptions } from '@angular/http'; +import { MockBackend } from '@angular/http/testing'; + +import { ResourceService } from './resource.service'; +import { Category, SubCategory, Resource } from './resource.model'; +import { Logger } from 'app/shared/logger.service'; + +describe('ResourceService', () => { + + let injector: ReflectiveInjector; + let backend: MockBackend; + let resourceService: ResourceService; + + function createResponse(body: any) { + return new Response(new ResponseOptions({ body: JSON.stringify(body) })); + } + + beforeEach(() => { + injector = ReflectiveInjector.resolveAndCreate([ + ResourceService, + { provide: ConnectionBackend, useClass: MockBackend }, + { provide: RequestOptions, useClass: BaseRequestOptions }, + Http, + Logger + ]); + + backend = injector.get(ConnectionBackend); + resourceService = injector.get(ResourceService); + }); + + it('should be creatable', () => { + expect(resourceService).toBeTruthy(); + }); + + it('should make a single connection to the server', () => { + expect(backend.connectionsArray.length).toEqual(1); + expect(backend.connectionsArray[0].request.url).toEqual('content/resources.json'); + }); + + describe('#categories', () => { + + let categories: Category[]; + let testData: any; + + beforeEach(() => { + testData = getTestResources(); + backend.connectionsArray[0].mockRespond(createResponse(testData)); + resourceService.categories.subscribe(results => categories = results); + }); + + it('categories observable should complete', () => { + let completed = false; + resourceService.categories.subscribe(null, null, () => completed = true); + expect(true).toBe(true, 'observable completed'); + }); + + it('should reshape contributors.json to sorted category array', () => { + const actualIds = categories.map(c => c.id).join(','); + expect(actualIds).toBe('cat-1,cat-3'); + }); + + it('should convert ids to canonical form', () => { + // canonical form is lowercase with dashes for spaces + const cat = categories[1]; + const sub = cat.subCategories[0]; + const res = sub.resources[0]; + + expect(cat.id).toBe('cat-3', 'category id'); + expect(sub.id).toBe('cat3-subcat2', 'subcat id'); + expect(res.id).toBe('cat3-subcat2-res1', 'resources id'); + }); + + it('resource knows its category and sub-category titles', () => { + const cat = categories[1]; + const sub = cat.subCategories[0]; + const res = sub.resources[0]; + expect(res.category).toBe(cat.title, 'category title'); + expect(res.subCategory).toBe(sub.title, 'subcategory title'); + }); + + it('should have expected SubCategories of "Cat 3"', () => { + const actualIds = categories[1].subCategories.map(s => s.id).join(','); + expect(actualIds).toBe('cat3-subcat2,cat3-subcat1'); + }); + + it('should have expected sorted resources of "Cat 1:SubCat1"', () => { + const actualIds = categories[0].subCategories[0].resources.map(r => r.id).join(','); + expect(actualIds).toBe('a-a-a,s-s-s,z-z-z'); + }); + }); + + it('should do WHAT(?) if the request fails'); +}); + +function getTestResources() { + // tslint:disable:quotemark + return { + "Cat 3": { + "order": 3, + "subCategories": { + "Cat3 SubCat1": { + "order": 2, + "resources": { + "Cat3 SubCat1 Res1": { + "desc": "Meetup in Barcelona, Spain. ", + "rev": true, + "title": "Angular Beers", + "url": "http://www.meetup.com/AngularJS-Beers/" + }, + "Cat3 SubCat1 Res2": { + "desc": "Angular Camps in Barcelona, Spain.", + "rev": true, + "title": "Angular Camp", + "url": "http://angularcamp.org/" + } + } + }, + "Cat3 SubCat2": { + "order": 1, + "resources": { + "Cat3 SubCat2 Res1": { + "desc": "A community index of components and libraries", + "rev": true, + "title": "Catalog of Angular Components & Libraries", + "url": "https://a/b/c" + } + } + }, + } + }, + "Cat 1": { + "order": 1, + "subCategories": { + "Cat1 SubCat1": { + "order": 1, + "resources": { + "S S S": { + "desc": "SSS", + "rev": true, + "title": "Sssss", + "url": "http://s/s/s" + }, + "A A A": { + "desc": "AAA", + "rev": true, + "title": "Aaaa", + "url": "http://a/a/a" + }, + "Z Z Z": { + "desc": "ZZZ", + "rev": true, + "title": "Zzzzz", + "url": "http://z/z/z" + } + } + }, + }, + } + }; +} diff --git a/aio/src/app/embedded/resource/resource.service.ts b/aio/src/app/embedded/resource/resource.service.ts new file mode 100644 index 0000000000..d46f5afd9b --- /dev/null +++ b/aio/src/app/embedded/resource/resource.service.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/publishLast'; + +import { Logger } from 'app/shared/logger.service'; +import { Category, Resource, SubCategory } from './resource.model'; + +const resourcesPath = 'content/resources.json'; + +@Injectable() +export class ResourceService { + categories: Observable; + + constructor(private http: Http, private logger: Logger) { + this.categories = this.getCategories(); + } + + private getCategories(): Observable { + + const categories = this.http.get(resourcesPath) + .map(res => res.json()) + .map(data => mkCategories(data)) + .publishLast(); + + categories.connect(); + return categories; + }; +} + +// Extract sorted Category[] from resource JSON data +function mkCategories(categoryJson: any): Category[] { + return Object.keys(categoryJson).map(catKey => { + const cat = categoryJson[catKey]; + return { + id: makeId(catKey), + title: catKey, + order: cat.order, + subCategories: mkSubCategories(cat.subCategories, catKey) + } as Category; + }) + .sort(compareCats); +} + +// Extract sorted SubCategory[] from JSON category data +function mkSubCategories(subCategoryJson: any, catKey: string): SubCategory[] { + return Object.keys(subCategoryJson).map(subKey => { + const sub = subCategoryJson[subKey]; + return { + id: makeId(subKey), + title: subKey, + order: sub.order, + resources: mkResources(sub.resources, subKey, catKey) + } as SubCategory; + }) + .sort(compareCats); +} + +// Extract sorted Resource[] from JSON subcategory data +function mkResources(resourceJson: any, subKey: string, catKey: string): Resource[] { + return Object.keys(resourceJson).map(resKey => { + const res = resourceJson[resKey]; + res.category = catKey; + res.subCategory = subKey; + res.id = makeId(resKey); + return res as Resource; + }) + .sort(compareTitles); +} + +function compareCats(l: Category | SubCategory, r: Category | SubCategory) { + return l.order === r.order ? compareTitles(l, r) : l.order > r.order ? 1 : -1; +} + +function compareTitles(l: {title: string}, r: {title: string}) { + return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1; +} + +function makeId(title: string) { + return title.toLowerCase().replace(/\s+/g, '-'); +} diff --git a/aio/src/app/navigation/navigation.service.spec.ts b/aio/src/app/navigation/navigation.service.spec.ts index 167bfc2a8a..9f48a9aa4c 100644 --- a/aio/src/app/navigation/navigation.service.spec.ts +++ b/aio/src/app/navigation/navigation.service.spec.ts @@ -51,7 +51,12 @@ describe('NavigationService', () => { expect(viewsEvents).toEqual([]); backend.connectionsArray[0].mockRespond(createResponse({ TopBar: [ { url: 'a' }] })); expect(viewsEvents).toEqual([{ TopBar: [ { url: 'a' }] }]); + }); + it('navigationViews observable should complete', () => { + let completed = false; + navService.navigationViews.subscribe(null, null, () => completed = true); + expect(true).toBe(true, 'observable completed'); }); it('should return the same object to all subscribers', () => { diff --git a/aio/src/app/shared/location.service.spec.ts b/aio/src/app/shared/location.service.spec.ts index cd03ddd353..4add823ab0 100644 --- a/aio/src/app/shared/location.service.spec.ts +++ b/aio/src/app/shared/location.service.spec.ts @@ -354,7 +354,7 @@ describe('LocationService', () => { }); it('should call locationChanged with initial URL', () => { - const initialUrl = location.path().replace(/^\/+/, ''); + const initialUrl = location.path().replace(/^\/+/, ''); // strip leading slashes expect(gaLocationChanged.calls.count()).toBe(1, 'gaService.locationChanged'); const args = gaLocationChanged.calls.first().args; diff --git a/aio/src/styles/1-layouts/_marketing-layout.scss b/aio/src/styles/1-layouts/_marketing-layout.scss index 58575854cb..d70fc26b02 100644 --- a/aio/src/styles/1-layouts/_marketing-layout.scss +++ b/aio/src/styles/1-layouts/_marketing-layout.scss @@ -125,4 +125,8 @@ header.bckground-sky.l-relative { } } } -} \ No newline at end of file +} + +.text-uppercase { + text-transform: uppercase; +} diff --git a/aio/src/styles/2-modules/_modules-dir.scss b/aio/src/styles/2-modules/_modules-dir.scss index b9afb37424..52a4adb9c9 100644 --- a/aio/src/styles/2-modules/_modules-dir.scss +++ b/aio/src/styles/2-modules/_modules-dir.scss @@ -21,4 +21,5 @@ @import 'hr'; @import 'live-example'; @import 'scrollbar'; - @import 'callout'; \ No newline at end of file + @import 'callout'; + @import 'resources'; \ No newline at end of file diff --git a/aio/src/styles/2-modules/_resources.scss b/aio/src/styles/2-modules/_resources.scss new file mode 100644 index 0000000000..2ea2e2bd6e --- /dev/null +++ b/aio/src/styles/2-modules/_resources.scss @@ -0,0 +1,238 @@ +.text-headline { + margin: 0px 0px ($unit * 2) 0px; + font-size: 24px; + font-weight: 400; + line-height: 32px; +} + +.grid-fixed { + margin: 0 auto; + *zoom: 1; + width: 960px; +} + +.grid-fixed .c3, .grid-fixed .c8, { + display: inline; + margin-left: 10px; + margin-right: 10px; +} + +.grid-fixed:after, .grid-fixed:before { + content: '.'; + clear: both; + display: block; + overflow: hidden; + visibility: hidden; + font-size: 0; + line-height: 0; + width: 0; + height: 0; +} +.grid-fixed .c3 { + width: 220px; +} +.grid-fixed .c8 { + width: 620px; +} + +@media handheld and (max-width: 480px), screen and (max-width: 480px), screen and (max-width: 900px) { + .grid-fixed { + width: auto; + } +} + +@media handheld and (max-width: 480px), screen and (max-width: 480px), screen and (max-width: 900px) { + .grid-fixed .c3, .grid-fixed .c8 { + margin-left: 20px; + margin-right: 20px; + float: none; + display: block; + width: auto; + } +} + +@media handheld and (max-width: 480px), screen and (max-width: 480px), screen and (max-width: 480px) { + .grid-fixed .c3, .grid-fixed .c8 { + margin-left: 0px; + margin-right: 0px; + float: none; + display: block; + width: auto; + } +} + +@media handheld and (max-width: 900px), screen and (max-width: 900px) { + /* line 6, ../scss/_responsive.scss */ + .grid-fixed{ + margin: 0 auto; + *zoom: 1; + } + .grid-fixed:after, .grid-fixed:before, { + content: '.'; + clear: both; + display: block; + overflow: hidden; + visibility: hidden; + font-size: 0; + line-height: 0; + width: 0; + height: 0; + } +} + +@media handheld and (max-width: 480px), screen and (max-width: 480px) { + /* line 6, ../scss/_responsive.scss */ + .grid-fixed { + margin: 0 auto; + *zoom: 1; + } + .grid-fixed:after, .grid-fixed:before { + content: '.'; + clear: both; + display: block; + overflow: hidden; + visibility: hidden; + font-size: 0; + line-height: 0; + width: 0; + height: 0; + } +} + +.resources { + + .shadow-1 { + transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 1px 4px 0 rgba($black, 0.37); + } + + .showcase { + margin-bottom: $unit * 6; + border-radius: 4px; + } + + .h-affix { + position: fixed; + } + + .affix-top { + top: 150px; + } + + .c-resource { + h4 { + margin: 0; + line-height: 24px; + } + + p { + margin: 0; + } + } + + .c-resource-nav { + margin-top: 48px; + width: $unit * 20; + z-index: 1; + background-color: #fff; + border-radius: 2px; + + a { + color: #373E41; + text-decoration: none; + } + + .category { + padding: 10px 0; + + .category-link { + display: block; + margin: 2px 0; + padding: 3px 14px; + font-size: 18px !important; + + &:hover { + background: #edf0f2; + color: #2B85E7; + } + } + } + + .subcategory { + .subcategory-link { + display: block; + margin: 2px 0; + padding: 4px 14px; + + &:hover { + background: #edf0f2; + color: #2B85E7; + } + } + } + } + + .h-anchor-offset { + display: block; + position: relative; + top: -20px; + visibility: hidden; + } + + .l-flex--column { + display: flex; + flex-direction: column; + } + + .c-resource-header { + margin-bottom: 16px; + } + + .c-contribute { + margin-bottom: 24px; + } + + .c-resource-header h2 { + margin: 0; + } + + .subcategory-title { + padding: 16px 23px; + margin: 0; + background-color: $mist; + color: #373E41; + } + + .h-capitalize { + text-transform: capitalize; + } + + .h-hide { + display: none; + } + + .resource-row-link { + color: #1a2326; + border: transparent solid 1px; + margin: 0; + padding: 16px 23px 16px 23px; + position: relative; + text-decoration: none; + transition: all .3s; + } + + .resource-row-link:hover { + color: #1a2326; + text-decoration: none; + border-color: #2B85E7; + border-radius: 4px; + box-shadow: 0 8px 8px rgba(1, 67, 163, .24), 0 0 8px rgba(1, 67, 163, .12), 0 6px 18px rgba(43, 133, 231, .12); + transform: translate3d(0, -2px, 0); + } + + @media(max-width: 900px) { + .c-resource-nav { + display: none; + } + } +} diff --git a/aio/src/styles/_constants.scss b/aio/src/styles/_constants.scss index 24f8f8372c..8aa6b1a17b 100755 --- a/aio/src/styles/_constants.scss +++ b/aio/src/styles/_constants.scss @@ -14,6 +14,7 @@ $white: #FFFFFF; $offwhite: #FAFAFA; $backgroundgray: #F1F1F1; $lightgray: #DBDBDB; +$mist: #ECEFF1; $mediumgray: #7E7E7E; $darkgray: #333; $black: #0A1014; diff --git a/aio/transforms/angular.io-package/index.js b/aio/transforms/angular.io-package/index.js index e866fe3e0a..ce30fbe17e 100644 --- a/aio/transforms/angular.io-package/index.js +++ b/aio/transforms/angular.io-package/index.js @@ -153,6 +153,11 @@ module.exports = include: CONTENTS_PATH + '/marketing/contributors.json', fileReader: 'jsonFileReader' }, + { + basePath: CONTENTS_PATH, + include: CONTENTS_PATH + '/marketing/resources.json', + fileReader: 'jsonFileReader' + }, ]; collectExamples.exampleFolders = ['examples', 'examples']; @@ -278,7 +283,8 @@ module.exports = outputPathTemplate: '${path}.json' }, {docTypes: ['navigation-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'}, - {docTypes: ['contributors-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'} + {docTypes: ['contributors-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'}, + {docTypes: ['resources-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'} ]; }) From eda2a7b2dca1d2ce9cd67bec20091e7ca710a162 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Sat, 15 Apr 2017 19:01:30 +0300 Subject: [PATCH 0052/1039] ci(aio): do not fail if PWA score testing fails on staging Currently, running `yarn test-pwa-score` right after deploying to staging fails with the error: ``` { Error: Unable to load the page: timeout reached ... code: 'PAGE_LOAD_ERROR' } ``` As a temporary fix, this commit prevents the build from failing because of PWA score errors (until we identify the cause and fix it). --- aio/scripts/deploy-staging.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aio/scripts/deploy-staging.sh b/aio/scripts/deploy-staging.sh index bb8dc47d59..0792e037de 100755 --- a/aio/scripts/deploy-staging.sh +++ b/aio/scripts/deploy-staging.sh @@ -17,6 +17,8 @@ firebase use "$FIREBASE_PROJECT_ID" --token "$FIREBASE_TOKEN" firebase deploy --message "Commit: $TRAVIS_COMMIT" --non-interactive --token "$FIREBASE_TOKEN" # Run PWA-score tests -yarn test-pwa-score -- "$DEPLOYED_URL" "$MIN_PWA_SCORE_STAGING" +# TODO(gkalpak): Figure out why this fails and re-enable. +sleep 10 +yarn test-pwa-score -- "$DEPLOYED_URL" "$MIN_PWA_SCORE_STAGING" || true cd - From 7433da546fb4605920c07021fd319263e85442a7 Mon Sep 17 00:00:00 2001 From: Jesus Rodriguez Date: Sat, 15 Apr 2017 23:09:25 +0200 Subject: [PATCH 0053/1039] build(aio): give intellisense to the examples --- aio/content/examples/tsconfig.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 aio/content/examples/tsconfig.json diff --git a/aio/content/examples/tsconfig.json b/aio/content/examples/tsconfig.json new file mode 100644 index 0000000000..5ad933f26e --- /dev/null +++ b/aio/content/examples/tsconfig.json @@ -0,0 +1,21 @@ +// this tsconfig is used to give intellisense to +// all the examples in this folder +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../tools/examples/shared/node_modules/@types" + ] + }, + "include": [ + "*/e2e-spec.ts" + ] +} From 3f307ff0610712ecc2293b669ade08a1e582a95d Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sat, 15 Apr 2017 21:51:00 +0100 Subject: [PATCH 0054/1039] style(aio): fix indentation on location service spec Closes #15589 --- aio/src/app/shared/location.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aio/src/app/shared/location.service.spec.ts b/aio/src/app/shared/location.service.spec.ts index 4add823ab0..d6d0dc94fb 100644 --- a/aio/src/app/shared/location.service.spec.ts +++ b/aio/src/app/shared/location.service.spec.ts @@ -369,8 +369,8 @@ describe('LocationService', () => { }); it('should call locationChanged when window history changes', () => { - const locationStrategy: MockLocationStrategy = injector.get(LocationStrategy); - locationStrategy.simulatePopState('/next-url'); + const locationStrategy: MockLocationStrategy = injector.get(LocationStrategy); + locationStrategy.simulatePopState('/next-url'); expect(gaLocationChanged.calls.count()).toBe(2, 'gaService.locationChanged'); const args = gaLocationChanged.calls.argsFor(1); From 24670667f1b880c6883cb18261e2c8b5f90012b1 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Mon, 10 Apr 2017 18:10:38 -0700 Subject: [PATCH 0055/1039] feat(aio): detect mobile device and tell user live-example is not available Adds DeviceService to detect mobile device and listen for window resize events. --- aio/src/app/app.component.spec.ts | 11 +++++ aio/src/app/app.component.ts | 10 ++--- aio/src/app/app.module.ts | 2 + .../live-example/live-example.component.html | 29 +++++++------ .../live-example.component.spec.ts | 42 +++++++++++++++++-- .../live-example/live-example.component.ts | 18 +++++--- aio/src/app/shared/device.service.ts | 41 ++++++++++++++++++ 7 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 aio/src/app/shared/device.service.ts diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index fcd33ff5e4..e6e3055f85 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -3,10 +3,12 @@ import { APP_BASE_HREF } from '@angular/common'; import { Http } from '@angular/http'; import { By } from '@angular/platform-browser'; +import { BehaviorSubject} from 'rxjs/BehaviorSubject'; import { of } from 'rxjs/observable/of'; import { AppComponent } from './app.component'; import { AppModule } from './app.module'; +import { DeviceService } from 'app/shared/device.service'; import { GaService } from 'app/shared/ga.service'; import { SearchResultsComponent } from 'app/search/search-results/search-results.component'; import { SearchBoxComponent } from 'app/search/search-box/search-box.component'; @@ -36,6 +38,7 @@ describe('AppComponent', () => { imports: [ AppModule ], providers: [ { provide: APP_BASE_HREF, useValue: '/' }, + { provide: DeviceService, useClass: TestDeviceService }, { provide: GaService, useClass: TestGaService }, { provide: Http, useClass: TestHttp }, { provide: LocationService, useFactory: () => new MockLocationService(initialUrl) }, @@ -330,6 +333,14 @@ describe('AppComponent', () => { //// test helpers //// +class TestDeviceService { + // Show sidenav next to the main doc when display width on current device is greater than this. + readonly sideBySideWidth = 1032; + // Default to "wide", desktop browser. + displayWidth = new BehaviorSubject(this.sideBySideWidth + 1); + isMobile = false; +} + class TestGaService { locationChanged = jasmine.createSpy('locationChanged'); } diff --git a/aio/src/app/app.component.ts b/aio/src/app/app.component.ts index 1891d46792..97eb970a54 100644 --- a/aio/src/app/app.component.ts +++ b/aio/src/app/app.component.ts @@ -4,6 +4,7 @@ import { MdSidenav } from '@angular/material'; import { AutoScrollService } from 'app/shared/auto-scroll.service'; import { CurrentNode, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service'; +import { DeviceService } from 'app/shared/device.service'; import { DocumentService, DocumentContents } from 'app/documents/document.service'; import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component'; import { LocationService } from 'app/shared/location.service'; @@ -11,7 +12,6 @@ import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component'; import { SearchResultsComponent } from 'app/search/search-results/search-results.component'; import { SwUpdateNotificationsService } from 'app/sw-updates/sw-update-notifications.service'; - const sideNavView = 'SideNav'; @Component({ @@ -28,8 +28,6 @@ export class AppComponent implements OnInit { isSideBySide = false; private isSideNavDoc = false; private previousNavView: string; - // Set to 1032 to account for computed html window size - private readonly sideBySideWidth = 1032; sideNavNodes: NavigationNode[]; topMenuNodes: NavigationNode[]; @@ -58,6 +56,7 @@ export class AppComponent implements OnInit { constructor( private autoScrollService: AutoScrollService, + private deviceService: DeviceService, private documentService: DocumentService, private locationService: LocationService, private navigationService: NavigationService, @@ -93,7 +92,7 @@ export class AppComponent implements OnInit { this.swUpdateNotifications.enable(); - this.onResize(window.innerWidth); + this.deviceService.displayWidth.subscribe(width => this.onResize(width)); } // Scroll to the anchor in the hash fragment. @@ -107,9 +106,8 @@ export class AppComponent implements OnInit { this.autoScroll(); } - @HostListener('window:resize', ['$event.target.innerWidth']) onResize(width) { - this.isSideBySide = width > this.sideBySideWidth; + this.isSideBySide = width > this.deviceService.sideBySideWidth; } @HostListener('click', ['$event.target', '$event.button', '$event.ctrlKey', '$event.metaKey', '$event.altKey']) diff --git a/aio/src/app/app.module.ts b/aio/src/app/app.module.ts index 2e57d85970..6c1168a3d7 100644 --- a/aio/src/app/app.module.ts +++ b/aio/src/app/app.module.ts @@ -15,6 +15,7 @@ import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module'; import { AppComponent } from 'app/app.component'; import { ApiService } from 'app/embedded/api/api.service'; +import { DeviceService } from 'app/shared/device.service'; import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component'; import { DtComponent } from 'app/layout/doc-viewer/dt.component'; import { EmbeddedModule } from 'app/embedded/embedded.module'; @@ -59,6 +60,7 @@ import { AutoScrollService } from 'app/shared/auto-scroll.service'; ], providers: [ ApiService, + DeviceService, GaService, Logger, Location, diff --git a/aio/src/app/embedded/live-example/live-example.component.html b/aio/src/app/embedded/live-example/live-example.component.html index 33a53fabb4..dafc538393 100644 --- a/aio/src/app/embedded/live-example/live-example.component.html +++ b/aio/src/app/embedded/live-example/live-example.component.html @@ -1,15 +1,18 @@ - - {{title}} - - / download example + + {{title}} (not available on mobile devices) + +
+ +
+ {{title}} +

+ You can also download this example. +

+
+ + {{title}} + + / download example +
-
-
- -
- {{title}} -

- You can also download this example. -

-
diff --git a/aio/src/app/embedded/live-example/live-example.component.spec.ts b/aio/src/app/embedded/live-example/live-example.component.spec.ts index e966e231a6..e07fac9889 100644 --- a/aio/src/app/embedded/live-example/live-example.component.spec.ts +++ b/aio/src/app/embedded/live-example/live-example.component.spec.ts @@ -1,9 +1,9 @@ -/* tslint:disable:no-unused-variable */ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Component, DebugElement, ElementRef } from '@angular/core'; import { Location } from '@angular/common'; +import { DeviceService } from 'app/shared/device.service'; import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example.component'; const defaultTestPath = '/test'; @@ -17,6 +17,13 @@ describe('LiveExampleComponent', () => { let liveExampleContent: string; //////// test helpers //////// + class TestDeviceService { + isMobile = false; + } + + class TestMobileDeviceService { + isMobile = true; + } @Component({ selector: 'aio-host-comp', @@ -54,7 +61,10 @@ describe('LiveExampleComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [ HostComponent, LiveExampleComponent, EmbeddedPlunkerComponent ], - providers: [ {provide: Location, useClass: TestLocation }] + providers: [ + { provide: DeviceService, useClass: TestDeviceService }, + { provide: Location, useClass: TestLocation } + ] }) // Disable the