5
5
isStandalone ,
6
6
NgZone ,
7
7
OnChanges ,
8
+ OutputRef ,
9
+ OutputRefSubscription ,
8
10
SimpleChange ,
9
11
SimpleChanges ,
10
12
Type ,
@@ -25,9 +27,17 @@ import {
25
27
waitForOptions as dtlWaitForOptions ,
26
28
within as dtlWithin ,
27
29
} from '@testing-library/dom' ;
28
- import { ComponentOverride , RenderComponentOptions , RenderResult , RenderTemplateOptions } from './models' ;
30
+ import {
31
+ ComponentOverride ,
32
+ RenderComponentOptions ,
33
+ RenderResult ,
34
+ RenderTemplateOptions ,
35
+ OutputRefKeysWithCallback ,
36
+ } from './models' ;
29
37
import { getConfig } from './config' ;
30
38
39
+ type SubscribedOutput < T > = readonly [ key : keyof T , callback : ( v : any ) => void , subscription : OutputRefSubscription ] ;
40
+
31
41
const mountedFixtures = new Set < ComponentFixture < any > > ( ) ;
32
42
const safeInject = TestBed . inject || TestBed . get ;
33
43
@@ -57,6 +67,7 @@ export async function render<SutType, WrapperType = SutType>(
57
67
componentProperties = { } ,
58
68
componentInputs = { } ,
59
69
componentOutputs = { } ,
70
+ on = { } ,
60
71
componentProviders = [ ] ,
61
72
childComponentOverrides = [ ] ,
62
73
componentImports : componentImports ,
@@ -165,7 +176,55 @@ export async function render<SutType, WrapperType = SutType>(
165
176
166
177
let detectChanges : ( ) => void ;
167
178
168
- const fixture = await renderFixture ( componentProperties , componentInputs , componentOutputs ) ;
179
+ let renderedPropKeys = Object . keys ( componentProperties ) ;
180
+ let renderedInputKeys = Object . keys ( componentInputs ) ;
181
+ let renderedOutputKeys = Object . keys ( componentOutputs ) ;
182
+ let subscribedOutputs : SubscribedOutput < SutType > [ ] = [ ] ;
183
+
184
+ const renderFixture = async (
185
+ properties : Partial < SutType > ,
186
+ inputs : Partial < SutType > ,
187
+ outputs : Partial < SutType > ,
188
+ subscribeTo : OutputRefKeysWithCallback < SutType > ,
189
+ ) : Promise < ComponentFixture < SutType > > => {
190
+ const createdFixture : ComponentFixture < SutType > = await createComponent ( componentContainer ) ;
191
+ setComponentProperties ( createdFixture , properties ) ;
192
+ setComponentInputs ( createdFixture , inputs ) ;
193
+ setComponentOutputs ( createdFixture , outputs ) ;
194
+ subscribedOutputs = subscribeToComponentOutputs ( createdFixture , subscribeTo ) ;
195
+
196
+ if ( removeAngularAttributes ) {
197
+ createdFixture . nativeElement . removeAttribute ( 'ng-version' ) ;
198
+ const idAttribute = createdFixture . nativeElement . getAttribute ( 'id' ) ;
199
+ if ( idAttribute && idAttribute . startsWith ( 'root' ) ) {
200
+ createdFixture . nativeElement . removeAttribute ( 'id' ) ;
201
+ }
202
+ }
203
+
204
+ mountedFixtures . add ( createdFixture ) ;
205
+
206
+ let isAlive = true ;
207
+ createdFixture . componentRef . onDestroy ( ( ) => ( isAlive = false ) ) ;
208
+
209
+ if ( hasOnChangesHook ( createdFixture . componentInstance ) && Object . keys ( properties ) . length > 0 ) {
210
+ const changes = getChangesObj ( null , componentProperties ) ;
211
+ createdFixture . componentInstance . ngOnChanges ( changes ) ;
212
+ }
213
+
214
+ detectChanges = ( ) => {
215
+ if ( isAlive ) {
216
+ createdFixture . detectChanges ( ) ;
217
+ }
218
+ } ;
219
+
220
+ if ( detectChangesOnRender ) {
221
+ detectChanges ( ) ;
222
+ }
223
+
224
+ return createdFixture ;
225
+ } ;
226
+
227
+ const fixture = await renderFixture ( componentProperties , componentInputs , componentOutputs , on ) ;
169
228
170
229
if ( deferBlockStates ) {
171
230
if ( Array . isArray ( deferBlockStates ) ) {
@@ -177,13 +236,10 @@ export async function render<SutType, WrapperType = SutType>(
177
236
}
178
237
}
179
238
180
- let renderedPropKeys = Object . keys ( componentProperties ) ;
181
- let renderedInputKeys = Object . keys ( componentInputs ) ;
182
- let renderedOutputKeys = Object . keys ( componentOutputs ) ;
183
239
const rerender = async (
184
240
properties ?: Pick <
185
241
RenderTemplateOptions < SutType > ,
186
- 'componentProperties' | 'componentInputs' | 'componentOutputs' | 'detectChangesOnRender'
242
+ 'componentProperties' | 'componentInputs' | 'componentOutputs' | 'on' | ' detectChangesOnRender'
187
243
> & { partialUpdate ?: boolean } ,
188
244
) => {
189
245
const newComponentInputs = properties ?. componentInputs ?? { } ;
@@ -205,6 +261,22 @@ export async function render<SutType, WrapperType = SutType>(
205
261
setComponentOutputs ( fixture , newComponentOutputs ) ;
206
262
renderedOutputKeys = Object . keys ( newComponentOutputs ) ;
207
263
264
+ // first unsubscribe the no longer available or changed callback-fns
265
+ const newObservableSubscriptions : OutputRefKeysWithCallback < SutType > = properties ?. on ?? { } ;
266
+ for ( const [ key , cb , subscription ] of subscribedOutputs ) {
267
+ // when no longer provided or when the callback has changed
268
+ if ( ! ( key in newObservableSubscriptions ) || cb !== ( newObservableSubscriptions as any ) [ key ] ) {
269
+ subscription . unsubscribe ( ) ;
270
+ }
271
+ }
272
+ // then subscribe the new callback-fns
273
+ subscribedOutputs = Object . entries ( newObservableSubscriptions ) . map ( ( [ key , cb ] ) => {
274
+ const existing = subscribedOutputs . find ( ( [ k ] ) => k === key ) ;
275
+ return existing && existing [ 1 ] === cb
276
+ ? existing // nothing to do
277
+ : subscribeToComponentOutput ( fixture , key as keyof SutType , cb as ( v : any ) => void ) ;
278
+ } ) ;
279
+
208
280
const newComponentProps = properties ?. componentProperties ?? { } ;
209
281
const changesInComponentProps = update (
210
282
fixture ,
@@ -249,47 +321,6 @@ export async function render<SutType, WrapperType = SutType>(
249
321
: console . log ( dtlPrettyDOM ( element , maxLength , options ) ) ,
250
322
...replaceFindWithFindAndDetectChanges ( dtlGetQueriesForElement ( fixture . nativeElement , queries ) ) ,
251
323
} ;
252
-
253
- async function renderFixture (
254
- properties : Partial < SutType > ,
255
- inputs : Partial < SutType > ,
256
- outputs : Partial < SutType > ,
257
- ) : Promise < ComponentFixture < SutType > > {
258
- const createdFixture = await createComponent ( componentContainer ) ;
259
- setComponentProperties ( createdFixture , properties ) ;
260
- setComponentInputs ( createdFixture , inputs ) ;
261
- setComponentOutputs ( createdFixture , outputs ) ;
262
-
263
- if ( removeAngularAttributes ) {
264
- createdFixture . nativeElement . removeAttribute ( 'ng-version' ) ;
265
- const idAttribute = createdFixture . nativeElement . getAttribute ( 'id' ) ;
266
- if ( idAttribute && idAttribute . startsWith ( 'root' ) ) {
267
- createdFixture . nativeElement . removeAttribute ( 'id' ) ;
268
- }
269
- }
270
-
271
- mountedFixtures . add ( createdFixture ) ;
272
-
273
- let isAlive = true ;
274
- createdFixture . componentRef . onDestroy ( ( ) => ( isAlive = false ) ) ;
275
-
276
- if ( hasOnChangesHook ( createdFixture . componentInstance ) && Object . keys ( properties ) . length > 0 ) {
277
- const changes = getChangesObj ( null , componentProperties ) ;
278
- createdFixture . componentInstance . ngOnChanges ( changes ) ;
279
- }
280
-
281
- detectChanges = ( ) => {
282
- if ( isAlive ) {
283
- createdFixture . detectChanges ( ) ;
284
- }
285
- } ;
286
-
287
- if ( detectChangesOnRender ) {
288
- detectChanges ( ) ;
289
- }
290
-
291
- return createdFixture ;
292
- }
293
324
}
294
325
295
326
async function createComponent < SutType > ( component : Type < SutType > ) : Promise < ComponentFixture < SutType > > {
@@ -355,6 +386,27 @@ function setComponentInputs<SutType>(
355
386
}
356
387
}
357
388
389
+ function subscribeToComponentOutputs < SutType > (
390
+ fixture : ComponentFixture < SutType > ,
391
+ listeners : OutputRefKeysWithCallback < SutType > ,
392
+ ) : SubscribedOutput < SutType > [ ] {
393
+ // with Object.entries we lose the type information of the key and callback, therefore we need to cast them
394
+ return Object . entries ( listeners ) . map ( ( [ key , cb ] ) =>
395
+ subscribeToComponentOutput ( fixture , key as keyof SutType , cb as ( v : any ) => void ) ,
396
+ ) ;
397
+ }
398
+
399
+ function subscribeToComponentOutput < SutType > (
400
+ fixture : ComponentFixture < SutType > ,
401
+ key : keyof SutType ,
402
+ cb : ( val : any ) => void ,
403
+ ) : SubscribedOutput < SutType > {
404
+ const eventEmitter = ( fixture . componentInstance as any ) [ key ] as OutputRef < any > ;
405
+ const subscription = eventEmitter . subscribe ( cb ) ;
406
+ fixture . componentRef . onDestroy ( subscription . unsubscribe . bind ( subscription ) ) ;
407
+ return [ key , cb , subscription ] ;
408
+ }
409
+
358
410
function overrideComponentImports < SutType > ( sut : Type < SutType > | string , imports : ( Type < any > | any [ ] ) [ ] | undefined ) {
359
411
if ( imports ) {
360
412
if ( typeof sut === 'function' && isStandalone ( sut ) ) {
0 commit comments