66 * found in the LICENSE file at https://angular.io/license
77 */
88import { DefaultTimeout , runTargetSpec } from '@angular-devkit/architect/testing' ;
9- import { normalize , virtualFs } from '@angular-devkit/core' ;
10- import { tap } from 'rxjs/operators' ;
11- import { host } from '../utils' ;
9+ import { getSystemPath , join , normalize , virtualFs } from '@angular-devkit/core' ;
10+ import * as express from 'express' ; // tslint:disable-line:no-implicit-dependencies
11+ import { Server } from 'http' ;
12+ import { concatMap , tap } from 'rxjs/operators' ;
13+ import { host , protractorTargetSpec } from '../utils' ;
1214
1315
1416describe ( 'AppShell Builder' , ( ) => {
1517 beforeEach ( done => host . initialize ( ) . toPromise ( ) . then ( done , done . fail ) ) ;
1618 afterEach ( done => host . restore ( ) . toPromise ( ) . then ( done , done . fail ) ) ;
1719
1820 const targetSpec = { project : 'app' , target : 'app-shell' } ;
21+ const appShellRouteFiles = {
22+ 'src/app/app-shell/app-shell.component.html' : `
23+ <p>
24+ app-shell works!
25+ </p>
26+ ` ,
27+ 'src/app/app-shell/app-shell.component.ts' : `
28+ import { Component, OnInit } from '@angular/core';
29+
30+ @Component({
31+ selector: 'app-app-shell',
32+ templateUrl: './app-shell.component.html',
33+ })
34+ export class AppShellComponent implements OnInit {
35+
36+ constructor() { }
37+
38+ ngOnInit() {
39+ }
40+
41+ }
42+ ` ,
43+ 'src/app/app.module.ts' : `
44+ import { BrowserModule } from '@angular/platform-browser';
45+ import { NgModule } from '@angular/core';
46+
47+ import { AppRoutingModule } from './app-routing.module';
48+ import { AppComponent } from './app.component';
49+ import { environment } from '../environments/environment';
50+ import { RouterModule } from '@angular/router';
51+
52+ @NgModule({
53+ declarations: [
54+ AppComponent
55+ ],
56+ imports: [
57+ BrowserModule.withServerTransition({ appId: 'serverApp' }),
58+ AppRoutingModule,
59+ RouterModule
60+ ],
61+ providers: [],
62+ bootstrap: [AppComponent]
63+ })
64+ export class AppModule { }
65+ ` ,
66+ 'src/app/app.server.module.ts' : `
67+ import { NgModule } from '@angular/core';
68+ import { ServerModule } from '@angular/platform-server';
69+
70+ import { AppModule } from './app.module';
71+ import { AppComponent } from './app.component';
72+ import { Routes, RouterModule } from '@angular/router';
73+ import { AppShellComponent } from './app-shell/app-shell.component';
74+
75+ const routes: Routes = [ { path: 'shell', component: AppShellComponent }];
76+
77+ @NgModule({
78+ imports: [
79+ AppModule,
80+ ServerModule,
81+ RouterModule.forRoot(routes),
82+ ],
83+ bootstrap: [AppComponent],
84+ declarations: [AppShellComponent],
85+ })
86+ export class AppServerModule {}
87+ ` ,
88+ 'src/main.ts' : `
89+ import { enableProdMode } from '@angular/core';
90+ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
91+
92+ import { AppModule } from './app/app.module';
93+ import { environment } from './environments/environment';
94+
95+ if (environment.production) {
96+ enableProdMode();
97+ }
98+
99+ document.addEventListener('DOMContentLoaded', () => {
100+ platformBrowserDynamic().bootstrapModule(AppModule)
101+ .catch(err => console.log(err));
102+ });
103+ ` ,
104+ 'src/app/app-routing.module.ts' : `
105+ import { NgModule } from '@angular/core';
106+ import { Routes, RouterModule } from '@angular/router';
107+
108+ const routes: Routes = [];
109+
110+ @NgModule({
111+ imports: [RouterModule.forRoot(routes)],
112+ exports: [RouterModule]
113+ })
114+ export class AppRoutingModule { }
115+ ` ,
116+ 'src/app/app.component.html' : `
117+ <router-outlet></router-outlet>
118+ ` ,
119+ } ;
19120
20121 it ( 'works (basic)' , done => {
21122 host . replaceInFile ( 'src/app/app.module.ts' , / B r o w s e r M o d u l e / , `
@@ -33,26 +134,46 @@ describe('AppShell Builder', () => {
33134 } ) ;
34135
35136 it ( 'works with route' , done => {
36- host . writeMultipleFiles ( {
37- 'src/app/app-shell/app-shell.component.html' : `
38- <p>
39- app-shell works!
40- </p>
41- ` ,
42- 'src/app/app-shell/app-shell.component.ts' : `
43- import { Component, OnInit } from '@angular/core';
44-
45- @Component({
46- selector: 'app-app-shell',
47- templateUrl: './app-shell.component.html',
48- })
49- export class AppShellComponent implements OnInit {
50-
51- constructor() { }
137+ host . writeMultipleFiles ( appShellRouteFiles ) ;
138+ const overrides = { route : 'shell' } ;
52139
53- ngOnInit() {
54- }
140+ runTargetSpec ( host , targetSpec , overrides , DefaultTimeout * 2 ) . pipe (
141+ tap ( ( buildEvent ) => expect ( buildEvent . success ) . toBe ( true ) ) ,
142+ tap ( ( ) => {
143+ const fileName = 'dist/index.html' ;
144+ const content = virtualFs . fileBufferToString ( host . scopedSync ( ) . read ( normalize ( fileName ) ) ) ;
145+ expect ( content ) . toContain ( 'app-shell works!' ) ;
146+ } ) ,
147+ ) . toPromise ( ) . then ( done , done . fail ) ;
148+ } ) ;
55149
150+ it ( 'works with route and service-worker' , done => {
151+ host . writeMultipleFiles ( appShellRouteFiles ) ;
152+ host . writeMultipleFiles ( {
153+ 'src/ngsw-config.json' : `
154+ {
155+ "index": "/index.html",
156+ "assetGroups": [{
157+ "name": "app",
158+ "installMode": "prefetch",
159+ "resources": {
160+ "files": [
161+ "/favicon.ico",
162+ "/index.html",
163+ "/*.css",
164+ "/*.js"
165+ ]
166+ }
167+ }, {
168+ "name": "assets",
169+ "installMode": "lazy",
170+ "updateMode": "prefetch",
171+ "resources": {
172+ "files": [
173+ "/assets/**"
174+ ]
175+ }
176+ }]
56177 }
57178 ` ,
58179 'src/app/app.module.ts' : `
@@ -61,6 +182,7 @@ describe('AppShell Builder', () => {
61182
62183 import { AppRoutingModule } from './app-routing.module';
63184 import { AppComponent } from './app.component';
185+ import { ServiceWorkerModule } from '@angular/service-worker';
64186 import { environment } from '../environments/environment';
65187 import { RouterModule } from '@angular/router';
66188
@@ -71,77 +193,59 @@ describe('AppShell Builder', () => {
71193 imports: [
72194 BrowserModule.withServerTransition({ appId: 'serverApp' }),
73195 AppRoutingModule,
196+ ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production }),
74197 RouterModule
75198 ],
76199 providers: [],
77200 bootstrap: [AppComponent]
78201 })
79202 export class AppModule { }
80203 ` ,
81- 'src/app/app.server.module.ts' : `
82- import { NgModule } from '@angular/core';
83- import { ServerModule } from '@angular/platform-server';
84-
85- import { AppModule } from './app.module';
86- import { AppComponent } from './app.component';
87- import { Routes, RouterModule } from '@angular/router';
88- import { AppShellComponent } from './app-shell/app-shell.component';
89-
90- const routes: Routes = [ { path: 'shell', component: AppShellComponent }];
91-
92- @NgModule({
93- imports: [
94- AppModule,
95- ServerModule,
96- RouterModule.forRoot(routes),
97- ],
98- bootstrap: [AppComponent],
99- declarations: [AppShellComponent],
100- })
101- export class AppServerModule {}
102- ` ,
103- 'src/main.ts' : `
104- import { enableProdMode } from '@angular/core';
105- import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
106-
107- import { AppModule } from './app/app.module';
108- import { environment } from './environments/environment';
109-
110- if (environment.production) {
111- enableProdMode();
112- }
113-
114- document.addEventListener('DOMContentLoaded', () => {
115- platformBrowserDynamic().bootstrapModule(AppModule)
116- .catch(err => console.log(err));
204+ 'e2e/app.e2e-spec.ts' : `
205+ import { browser, by, element } from 'protractor';
206+
207+ it('should have ngsw in normal state', () => {
208+ browser.get('/');
209+ // Wait for service worker to load.
210+ browser.sleep(2000);
211+ browser.waitForAngularEnabled(false);
212+ browser.get('/ngsw/state');
213+ // Should have updated, and be in normal state.
214+ expect(element(by.css('pre')).getText()).not.toContain('Last update check: never');
215+ expect(element(by.css('pre')).getText()).toContain('Driver state: NORMAL');
117216 });
118217 ` ,
119- 'src/app/app-routing.module.ts' : `
120- import { NgModule } from '@angular/core';
121- import { Routes, RouterModule } from '@angular/router';
122-
123- const routes: Routes = [];
124-
125- @NgModule({
126- imports: [RouterModule.forRoot(routes)],
127- exports: [RouterModule]
128- })
129- export class AppRoutingModule { }
130- ` ,
131- 'src/app/app.component.html' : `
132- <router-outlet></router-outlet>
133- ` ,
134218 } ) ;
219+ // This should match the browser target prod config.
220+ host . replaceInFile (
221+ 'angular.json' ,
222+ '"buildOptimizer": true' ,
223+ '"buildOptimizer": true, "serviceWorker": true' ,
224+ ) ;
135225
136226 const overrides = { route : 'shell' } ;
227+ const prodTargetSpec = { ...targetSpec , configuration : 'production' } ;
228+ let server : Server ;
137229
138- runTargetSpec ( host , targetSpec , overrides , DefaultTimeout * 2 ) . pipe (
230+ // Build the app shell.
231+ runTargetSpec ( host , prodTargetSpec , overrides , DefaultTimeout * 2 ) . pipe (
139232 tap ( ( buildEvent ) => expect ( buildEvent . success ) . toBe ( true ) ) ,
233+ // Make sure the index is pre-rendering the route.
140234 tap ( ( ) => {
141235 const fileName = 'dist/index.html' ;
142236 const content = virtualFs . fileBufferToString ( host . scopedSync ( ) . read ( normalize ( fileName ) ) ) ;
143237 expect ( content ) . toContain ( 'app-shell works!' ) ;
144238 } ) ,
239+ tap ( ( ) => {
240+ // Serve the app using a simple static server.
241+ const app = express ( ) ;
242+ app . use ( '/' , express . static ( getSystemPath ( join ( host . root ( ) , 'dist' ) ) + '/' ) ) ;
243+ server = app . listen ( 4200 ) ;
244+ } ) ,
245+ // Load app in protractor, then check service worker status.
246+ concatMap ( ( ) => runTargetSpec ( host , protractorTargetSpec , { devServerTarget : undefined } ) ) ,
247+ // Close the express server.
248+ tap ( ( ) => server . close ( ) ) ,
145249 ) . toPromise ( ) . then ( done , done . fail ) ;
146250 } ) ;
147251} ) ;
0 commit comments