| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  | import {ComponentFactory, ComponentRef, Injector, NgModuleRef, SimpleChange, SimpleChanges, Type} from '@angular/core'; | 
					
						
							|  |  |  | import {fakeAsync, tick} from '@angular/core/testing'; | 
					
						
							| 
									
										
										
										
											2018-02-27 17:06:06 -05:00
										 |  |  | import {Subject} from 'rxjs'; | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-06 14:02:25 -08:00
										 |  |  | import {ComponentNgElementStrategy, ComponentNgElementStrategyFactory} from '../src/component-factory-strategy'; | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  | import {NgElementStrategyEvent} from '../src/element-strategy'; | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('ComponentFactoryNgElementStrategy', () => { | 
					
						
							|  |  |  |   let factory: FakeComponentFactory; | 
					
						
							| 
									
										
										
										
											2018-03-06 14:02:25 -08:00
										 |  |  |   let strategy: ComponentNgElementStrategy; | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   let injector: any; | 
					
						
							|  |  |  |   let componentRef: any; | 
					
						
							|  |  |  |   let applicationRef: any; | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   beforeEach(() => { | 
					
						
							|  |  |  |     factory = new FakeComponentFactory(); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |     componentRef = factory.componentRef; | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |     applicationRef = jasmine.createSpyObj('applicationRef', ['attachView']); | 
					
						
							| 
									
										
										
										
											2018-07-06 09:13:25 +03:00
										 |  |  |     injector = jasmine.createSpyObj('injector', ['get']); | 
					
						
							|  |  |  |     injector.get.and.returnValue(applicationRef); | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-06 14:02:25 -08:00
										 |  |  |     strategy = new ComponentNgElementStrategy(factory, injector); | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |   it('should create a new strategy from the factory', () => { | 
					
						
							| 
									
										
										
										
											2018-03-06 14:02:25 -08:00
										 |  |  |     const factoryResolver = jasmine.createSpyObj('factoryResolver', ['resolveComponentFactory']); | 
					
						
							|  |  |  |     factoryResolver.resolveComponentFactory.and.returnValue(factory); | 
					
						
							|  |  |  |     injector.get.and.returnValue(factoryResolver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const strategyFactory = new ComponentNgElementStrategyFactory(FakeComponent, injector); | 
					
						
							|  |  |  |     expect(strategyFactory.create(injector)).toBeTruthy(); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('after connected', () => { | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  |     beforeEach(() => { | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |       // Set up an initial value to make sure it is passed to the component
 | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |       strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |       strategy.connect(document.createElement('div')); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should attach the component to the view', | 
					
						
							|  |  |  |        () => { expect(applicationRef.attachView).toHaveBeenCalledWith(componentRef.hostView); }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should detect changes', | 
					
						
							|  |  |  |        () => { expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalled(); }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should listen to output events', () => { | 
					
						
							|  |  |  |       const events: NgElementStrategyEvent[] = []; | 
					
						
							|  |  |  |       strategy.events.subscribe(e => events.push(e)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       componentRef.instance.output1.next('output-1a'); | 
					
						
							|  |  |  |       componentRef.instance.output1.next('output-1b'); | 
					
						
							|  |  |  |       componentRef.instance.output2.next('output-2a'); | 
					
						
							|  |  |  |       expect(events).toEqual([ | 
					
						
							|  |  |  |         {name: 'templateOutput1', value: 'output-1a'}, | 
					
						
							|  |  |  |         {name: 'templateOutput1', value: 'output-1b'}, | 
					
						
							|  |  |  |         {name: 'templateOutput2', value: 'output-2a'}, | 
					
						
							|  |  |  |       ]); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should initialize the component with initial values', () => { | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |       expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |       expect(componentRef.instance.fooFoo).toBe('fooFoo-1'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should call ngOnChanges with the change', () => { | 
					
						
							|  |  |  |       expectSimpleChanges( | 
					
						
							|  |  |  |           componentRef.instance.simpleChanges[0], | 
					
						
							|  |  |  |           {fooFoo: new SimpleChange(undefined, 'fooFoo-1', false)}); | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |   it('should not call ngOnChanges if not present on the component', () => { | 
					
						
							|  |  |  |     factory.componentRef.instance = new FakeComponentWithoutNgOnChanges(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Should simply succeed without problems (did not try to call ngOnChanges)
 | 
					
						
							|  |  |  |     strategy.connect(document.createElement('div')); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('when inputs change and not connected', () => { | 
					
						
							|  |  |  |     it('should cache the value', () => { | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |       strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							|  |  |  |       expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Sanity check: componentRef isn't changed since its not even on the strategy
 | 
					
						
							|  |  |  |       expect(componentRef.instance.fooFoo).toBe(undefined); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should not detect changes', fakeAsync(() => { | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |          strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |          tick(16);  // scheduler waits 16ms if RAF is unavailable
 | 
					
						
							| 
									
										
										
										
											2018-03-06 14:02:25 -08:00
										 |  |  |          expect(componentRef.changeDetectorRef.detectChanges).not.toHaveBeenCalled(); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |        })); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('when inputs change and is connected', () => { | 
					
						
							|  |  |  |     beforeEach(() => { strategy.connect(document.createElement('div')); }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should be set on the component instance', () => { | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |       strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |       expect(componentRef.instance.fooFoo).toBe('fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |       expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should detect changes', fakeAsync(() => { | 
					
						
							|  |  |  |          // Connect detected changes automatically
 | 
					
						
							|  |  |  |          expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(1); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |          strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |          tick(16);  // scheduler waits 16ms if RAF is unavailable
 | 
					
						
							|  |  |  |          expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(2); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should detect changes once for multiple input changes', fakeAsync(() => { | 
					
						
							|  |  |  |          // Connect detected changes automatically
 | 
					
						
							|  |  |  |          expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(1); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |          strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							|  |  |  |          strategy.setInputValue('barBar', 'barBar-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |          tick(16);  // scheduler waits 16ms if RAF is unavailable
 | 
					
						
							|  |  |  |          expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(2); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should call ngOnChanges', fakeAsync(() => { | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |          strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |          tick(16);  // scheduler waits 16ms if RAF is unavailable
 | 
					
						
							|  |  |  |          expectSimpleChanges( | 
					
						
							|  |  |  |              componentRef.instance.simpleChanges[0], | 
					
						
							|  |  |  |              {fooFoo: new SimpleChange(undefined, 'fooFoo-1', true)}); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should call ngOnChanges once for multiple input changes', fakeAsync(() => { | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |          strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							|  |  |  |          strategy.setInputValue('barBar', 'barBar-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |          tick(16);  // scheduler waits 16ms if RAF is unavailable
 | 
					
						
							|  |  |  |          expectSimpleChanges(componentRef.instance.simpleChanges[0], { | 
					
						
							|  |  |  |            fooFoo: new SimpleChange(undefined, 'fooFoo-1', true), | 
					
						
							|  |  |  |            barBar: new SimpleChange(undefined, 'barBar-1', true) | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should call ngOnChanges twice for changes in different rounds with previous values', | 
					
						
							|  |  |  |        fakeAsync(() => { | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |          strategy.setInputValue('fooFoo', 'fooFoo-1'); | 
					
						
							|  |  |  |          strategy.setInputValue('barBar', 'barBar-1'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |          tick(16);  // scheduler waits 16ms if RAF is unavailable
 | 
					
						
							|  |  |  |          expectSimpleChanges(componentRef.instance.simpleChanges[0], { | 
					
						
							|  |  |  |            fooFoo: new SimpleChange(undefined, 'fooFoo-1', true), | 
					
						
							|  |  |  |            barBar: new SimpleChange(undefined, 'barBar-1', true) | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-02 10:08:16 -08:00
										 |  |  |          strategy.setInputValue('fooFoo', 'fooFoo-2'); | 
					
						
							|  |  |  |          strategy.setInputValue('barBar', 'barBar-2'); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |          tick(16);  // scheduler waits 16ms if RAF is unavailable
 | 
					
						
							|  |  |  |          expectSimpleChanges(componentRef.instance.simpleChanges[1], { | 
					
						
							|  |  |  |            fooFoo: new SimpleChange('fooFoo-1', 'fooFoo-2', false), | 
					
						
							|  |  |  |            barBar: new SimpleChange('barBar-1', 'barBar-2', false) | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('disconnect', () => { | 
					
						
							|  |  |  |     it('should be able to call if not connected', fakeAsync(() => { | 
					
						
							|  |  |  |          strategy.disconnect(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          // Sanity check: the strategy doesn't have an instance of the componentRef anyways
 | 
					
						
							|  |  |  |          expect(componentRef.destroy).not.toHaveBeenCalled(); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should destroy the component after the destroy delay', fakeAsync(() => { | 
					
						
							|  |  |  |          strategy.connect(document.createElement('div')); | 
					
						
							|  |  |  |          strategy.disconnect(); | 
					
						
							|  |  |  |          expect(componentRef.destroy).not.toHaveBeenCalled(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          tick(10); | 
					
						
							|  |  |  |          expect(componentRef.destroy).toHaveBeenCalledTimes(1); | 
					
						
							|  |  |  |        })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should be able to call it multiple times but only destroy once', fakeAsync(() => { | 
					
						
							|  |  |  |          strategy.connect(document.createElement('div')); | 
					
						
							|  |  |  |          strategy.disconnect(); | 
					
						
							|  |  |  |          strategy.disconnect(); | 
					
						
							|  |  |  |          expect(componentRef.destroy).not.toHaveBeenCalled(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          tick(10); | 
					
						
							|  |  |  |          expect(componentRef.destroy).toHaveBeenCalledTimes(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          strategy.disconnect(); | 
					
						
							|  |  |  |          expect(componentRef.destroy).toHaveBeenCalledTimes(1); | 
					
						
							|  |  |  |        })); | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  | export class FakeComponentWithoutNgOnChanges { | 
					
						
							|  |  |  |   output1 = new Subject(); | 
					
						
							|  |  |  |   output2 = new Subject(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | export class FakeComponent { | 
					
						
							|  |  |  |   output1 = new Subject(); | 
					
						
							|  |  |  |   output2 = new Subject(); | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // Keep track of the simple changes passed to ngOnChanges
 | 
					
						
							|  |  |  |   simpleChanges: SimpleChanges[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ngOnChanges(simpleChanges: SimpleChanges) { this.simpleChanges.push(simpleChanges); } | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class FakeComponentFactory extends ComponentFactory<any> { | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |   componentRef: any = jasmine.createSpyObj( | 
					
						
							|  |  |  |       'componentRef', ['instance', 'changeDetectorRef', 'hostView', 'destroy']); | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   constructor() { | 
					
						
							|  |  |  |     super(); | 
					
						
							|  |  |  |     this.componentRef.instance = new FakeComponent(); | 
					
						
							|  |  |  |     this.componentRef.changeDetectorRef = | 
					
						
							|  |  |  |         jasmine.createSpyObj('changeDetectorRef', ['detectChanges']); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   get selector(): string { return 'fake-component'; } | 
					
						
							|  |  |  |   get componentType(): Type<any> { return FakeComponent; } | 
					
						
							|  |  |  |   get ngContentSelectors(): string[] { return ['content-1', 'content-2']; } | 
					
						
							|  |  |  |   get inputs(): {propName: string; templateName: string}[] { | 
					
						
							|  |  |  |     return [ | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  |       {propName: 'fooFoo', templateName: 'fooFoo'}, | 
					
						
							|  |  |  |       {propName: 'barBar', templateName: 'my-bar-bar'}, | 
					
						
							| 
									
										
										
										
											2018-02-28 09:45:11 -08:00
										 |  |  |     ]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   get outputs(): {propName: string; templateName: string}[] { | 
					
						
							|  |  |  |     return [ | 
					
						
							|  |  |  |       {propName: 'output1', templateName: 'templateOutput1'}, | 
					
						
							|  |  |  |       {propName: 'output2', templateName: 'templateOutput2'}, | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   create( | 
					
						
							|  |  |  |       injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any, | 
					
						
							|  |  |  |       ngModule?: NgModuleRef<any>): ComponentRef<any> { | 
					
						
							|  |  |  |     return this.componentRef; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-03-01 11:26:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | function expectSimpleChanges(actual: SimpleChanges, expected: SimpleChanges) { | 
					
						
							|  |  |  |   Object.keys(actual).forEach( | 
					
						
							|  |  |  |       key => { expect(expected[key]).toBeTruthy(`Change included additional key ${key}`); }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Object.keys(expected).forEach(key => { | 
					
						
							|  |  |  |     expect(actual[key]).toBeTruthy(`Change should have included key ${key}`); | 
					
						
							|  |  |  |     if (actual[key]) { | 
					
						
							|  |  |  |       expect(actual[key].previousValue).toBe(expected[key].previousValue); | 
					
						
							|  |  |  |       expect(actual[key].currentValue).toBe(expected[key].currentValue); | 
					
						
							|  |  |  |       expect(actual[key].firstChange).toBe(expected[key].firstChange); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } |