6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { DOCUMENT , ɵgetDOM as getDOM } from '@angular/common ' ;
10
- import { Component , destroyPlatform , ErrorHandler , Inject , Injectable , InjectionToken , NgModule } from '@angular/core' ;
11
- import { inject } from '@angular/core /testing' ;
9
+ import { Component , ErrorHandler , Inject , Injectable , InjectionToken , NgModule , PlatformRef } from '@angular/core ' ;
10
+ import { R3Injector } from '@angular/core/src/di/r3_injector ' ;
11
+ import { withBody } from '@angular/private /testing' ;
12
12
13
13
import { bootstrapApplication , BrowserModule } from '../../src/browser' ;
14
14
15
15
describe ( 'bootstrapApplication for standalone components' , ( ) => {
16
- let rootEl : HTMLUnknownElement ;
17
- beforeEach ( inject ( [ DOCUMENT ] , ( doc : any ) => {
18
- rootEl = getDOM ( ) . createElement ( 'test-app' , doc ) ;
19
- getDOM ( ) . getDefaultDocument ( ) . body . appendChild ( rootEl ) ;
20
- } ) ) ;
21
-
22
- afterEach ( ( ) => {
23
- destroyPlatform ( ) ;
24
- rootEl ?. remove ( ) ;
25
- } ) ;
26
-
27
16
class SilentErrorHandler extends ErrorHandler {
28
17
override handleError ( ) {
29
18
// the error is already re-thrown by the application ref.
30
19
// we don't want to print it, but instead catch it in tests.
31
20
}
32
21
}
33
22
34
- it ( 'should create injector where ambient providers shadow explicit providers' , async ( ) => {
35
- const testToken = new InjectionToken ( 'test token' ) ;
23
+ it ( 'should create injector where ambient providers shadow explicit providers' ,
24
+ withBody ( '<test-app></test-app>' , async ( ) => {
25
+ const testToken = new InjectionToken ( 'test token' ) ;
36
26
37
- @NgModule ( {
38
- providers : [
39
- { provide : testToken , useValue : 'Ambient' } ,
40
- ]
41
- } )
42
- class AmbientModule {
43
- }
27
+ @NgModule ( {
28
+ providers : [
29
+ { provide : testToken , useValue : 'Ambient' } ,
30
+ ]
31
+ } )
32
+ class AmbientModule {
33
+ }
44
34
45
- @Component ( {
46
- selector : 'test-app' ,
47
- standalone : true ,
48
- template : `({{testToken}})` ,
49
- imports : [ AmbientModule ]
50
- } )
51
- class StandaloneCmp {
52
- constructor ( @Inject ( testToken ) readonly testToken : String ) { }
53
- }
35
+ @Component ( {
36
+ selector : 'test-app' ,
37
+ standalone : true ,
38
+ template : `({{testToken}})` ,
39
+ imports : [ AmbientModule ]
40
+ } )
41
+ class StandaloneCmp {
42
+ constructor ( @Inject ( testToken ) readonly testToken : String ) { }
43
+ }
54
44
55
- const appRef = await bootstrapApplication ( StandaloneCmp , {
56
- providers : [
57
- { provide : testToken , useValue : 'Bootstrap' } ,
58
- ]
59
- } ) ;
45
+ class SilentErrorHandler extends ErrorHandler {
46
+ override handleError ( ) {
47
+ // the error is already re-thrown by the application ref.
48
+ // we don't want to print it, but instead catch it in tests.
49
+ }
50
+ }
60
51
61
- appRef . tick ( ) ;
52
+ const appRef = await bootstrapApplication ( StandaloneCmp , {
53
+ providers : [
54
+ { provide : testToken , useValue : 'Bootstrap' } ,
55
+ ]
56
+ } ) ;
62
57
63
- // make sure that ambient providers "shadow" ones explicitly provided during bootstrap
64
- expect ( rootEl . textContent ) . toBe ( '(Ambient)' ) ;
65
- } ) ;
58
+ appRef . tick ( ) ;
59
+
60
+ // make sure that ambient providers "shadow" ones explicitly provided during bootstrap
61
+ expect ( document . body . textContent ) . toBe ( '(Ambient)' ) ;
62
+ } ) ) ;
66
63
67
64
/*
68
65
This test verifies that ambient providers for the standalone component being bootstrapped
@@ -74,7 +71,7 @@ describe('bootstrapApplication for standalone components', () => {
74
71
- standalone injector (ambient providers go here);
75
72
*/
76
73
it ( 'should create a standalone injector for standalone components with ambient providers' ,
77
- async ( ) => {
74
+ withBody ( '<test-app></test-app>' , async ( ) => {
78
75
const ambientToken = new InjectionToken ( 'ambient token' ) ;
79
76
80
77
@NgModule ( {
@@ -119,10 +116,10 @@ describe('bootstrapApplication for standalone components', () => {
119
116
expect ( e ) . toBeInstanceOf ( Error ) ;
120
117
expect ( ( e as Error ) . message ) . toContain ( 'No provider for InjectionToken ambient token!' ) ;
121
118
}
122
- } ) ;
119
+ } ) ) ;
123
120
124
121
it ( 'should throw if `BrowserModule` is imported in the standalone bootstrap scenario' ,
125
- async ( ) => {
122
+ withBody ( '<test-app></test-app>' , async ( ) => {
126
123
@Component ( {
127
124
selector : 'test-app' ,
128
125
template : '...' ,
@@ -145,10 +142,10 @@ describe('bootstrapApplication for standalone components', () => {
145
142
expect ( ( e as Error ) . message )
146
143
. toContain ( 'Providers from the `BrowserModule` have already been loaded.' ) ;
147
144
}
148
- } ) ;
145
+ } ) ) ;
149
146
150
147
it ( 'should throw if `BrowserModule` is imported indirectly in the standalone bootstrap scenario' ,
151
- async ( ) => {
148
+ withBody ( '<test-app></test-app>' , async ( ) => {
152
149
@NgModule ( {
153
150
imports : [ BrowserModule ] ,
154
151
} )
@@ -177,5 +174,49 @@ describe('bootstrapApplication for standalone components', () => {
177
174
expect ( ( e as Error ) . message )
178
175
. toContain ( 'Providers from the `BrowserModule` have already been loaded.' ) ;
179
176
}
180
- } ) ;
177
+ } ) ) ;
178
+
179
+ it ( 'should trigger an app destroy when a platform is destroyed' ,
180
+ withBody ( '<test-app></test-app>' , async ( ) => {
181
+ let compOnDestroyCalled = false ;
182
+ let serviceOnDestroyCalled = false ;
183
+ let injectorOnDestroyCalled = false ;
184
+
185
+ @Injectable ( { providedIn : 'root' } )
186
+ class ServiceWithOnDestroy {
187
+ ngOnDestroy ( ) {
188
+ serviceOnDestroyCalled = true ;
189
+ }
190
+ }
191
+
192
+ @Component ( {
193
+ selector : 'test-app' ,
194
+ standalone : true ,
195
+ template : 'Hello' ,
196
+ } )
197
+ class ComponentWithOnDestroy {
198
+ constructor ( service : ServiceWithOnDestroy ) { }
199
+
200
+ ngOnDestroy ( ) {
201
+ compOnDestroyCalled = true ;
202
+ }
203
+ }
204
+
205
+ const appRef = await bootstrapApplication ( ComponentWithOnDestroy ) ;
206
+ const injector = ( appRef as unknown as { injector : R3Injector } ) . injector ;
207
+ injector . onDestroy ( ( ) => injectorOnDestroyCalled = true ) ;
208
+
209
+ expect ( document . body . textContent ) . toBe ( 'Hello' ) ;
210
+
211
+ const platformRef = injector . get ( PlatformRef ) ;
212
+ platformRef . destroy ( ) ;
213
+
214
+ // Verify the callbacks were invoked.
215
+ expect ( compOnDestroyCalled ) . toBe ( true ) ;
216
+ expect ( serviceOnDestroyCalled ) . toBe ( true ) ;
217
+ expect ( injectorOnDestroyCalled ) . toBe ( true ) ;
218
+
219
+ // Make sure the DOM has been cleaned up as well.
220
+ expect ( document . body . textContent ) . toBe ( '' ) ;
221
+ } ) ) ;
181
222
} ) ;
0 commit comments