2018-02-28 12:45:11 -05:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2018-02-28 12:45:11 -05:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2020-11-04 13:46:59 -05:00
|
|
|
import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, Injector, NgModuleRef, NgZone, SimpleChange, SimpleChanges, Type} from '@angular/core';
|
2018-03-01 14:26:29 -05:00
|
|
|
import {fakeAsync, tick} from '@angular/core/testing';
|
2018-02-27 17:06:06 -05:00
|
|
|
import {Subject} from 'rxjs';
|
2018-02-28 12:45:11 -05:00
|
|
|
|
2018-03-06 17:02:25 -05:00
|
|
|
import {ComponentNgElementStrategy, ComponentNgElementStrategyFactory} from '../src/component-factory-strategy';
|
2018-03-01 14:26:29 -05:00
|
|
|
import {NgElementStrategyEvent} from '../src/element-strategy';
|
2018-02-28 12:45:11 -05:00
|
|
|
|
|
|
|
describe('ComponentFactoryNgElementStrategy', () => {
|
2020-11-04 13:45:32 -05:00
|
|
|
let factory: FakeComponentFactory<typeof FakeComponent>;
|
2018-03-06 17:02:25 -05:00
|
|
|
let strategy: ComponentNgElementStrategy;
|
2018-03-01 14:26:29 -05:00
|
|
|
|
|
|
|
let injector: any;
|
|
|
|
let componentRef: any;
|
|
|
|
let applicationRef: any;
|
2020-06-28 20:18:19 -04:00
|
|
|
let ngZone: any;
|
|
|
|
|
|
|
|
let injectables: Map<unknown, unknown>;
|
2018-02-28 12:45:11 -05:00
|
|
|
|
|
|
|
beforeEach(() => {
|
2020-11-04 13:45:32 -05:00
|
|
|
factory = new FakeComponentFactory(FakeComponent);
|
2018-03-01 14:26:29 -05:00
|
|
|
componentRef = factory.componentRef;
|
2018-02-28 12:45:11 -05:00
|
|
|
|
2018-03-01 14:26:29 -05:00
|
|
|
applicationRef = jasmine.createSpyObj('applicationRef', ['attachView']);
|
2020-06-28 20:18:19 -04:00
|
|
|
|
|
|
|
ngZone = jasmine.createSpyObj('ngZone', ['run']);
|
|
|
|
ngZone.run.and.callFake((fn: () => unknown) => fn());
|
|
|
|
|
2018-07-06 02:13:25 -04:00
|
|
|
injector = jasmine.createSpyObj('injector', ['get']);
|
2020-06-28 20:18:19 -04:00
|
|
|
injector.get.and.callFake((token: unknown) => {
|
|
|
|
if (!injectables.has(token)) {
|
|
|
|
throw new Error(`Failed to get injectable from mock injector: ${token}`);
|
|
|
|
}
|
|
|
|
return injectables.get(token);
|
|
|
|
});
|
|
|
|
|
|
|
|
injectables = new Map<unknown, unknown>([
|
|
|
|
[ApplicationRef, applicationRef],
|
|
|
|
[NgZone, ngZone],
|
|
|
|
]);
|
2018-02-28 12:45:11 -05:00
|
|
|
|
2018-03-06 17:02:25 -05:00
|
|
|
strategy = new ComponentNgElementStrategy(factory, injector);
|
2020-06-28 20:18:19 -04:00
|
|
|
ngZone.run.calls.reset();
|
2018-02-28 12:45:11 -05:00
|
|
|
});
|
|
|
|
|
2018-03-01 14:26:29 -05:00
|
|
|
it('should create a new strategy from the factory', () => {
|
2018-03-06 17:02:25 -05:00
|
|
|
const factoryResolver = jasmine.createSpyObj('factoryResolver', ['resolveComponentFactory']);
|
|
|
|
factoryResolver.resolveComponentFactory.and.returnValue(factory);
|
2020-06-28 20:18:19 -04:00
|
|
|
injectables.set(ComponentFactoryResolver, factoryResolver);
|
2018-03-06 17:02:25 -05:00
|
|
|
|
|
|
|
const strategyFactory = new ComponentNgElementStrategyFactory(FakeComponent, injector);
|
|
|
|
expect(strategyFactory.create(injector)).toBeTruthy();
|
2018-03-01 14:26:29 -05:00
|
|
|
});
|
|
|
|
|
2020-06-13 04:06:35 -04:00
|
|
|
describe('before connected', () => {
|
|
|
|
it('should allow subscribing to output events', () => {
|
|
|
|
const events: NgElementStrategyEvent[] = [];
|
|
|
|
strategy.events.subscribe(e => events.push(e));
|
|
|
|
|
|
|
|
// No events before connecting (since `componentRef` is not even on the strategy yet).
|
|
|
|
componentRef.instance.output1.next('output-1a');
|
|
|
|
componentRef.instance.output1.next('output-1b');
|
|
|
|
componentRef.instance.output2.next('output-2a');
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
|
|
|
|
// No events upon connecting (since events are not cached/played back).
|
|
|
|
strategy.connect(document.createElement('div'));
|
|
|
|
expect(events).toEqual([]);
|
|
|
|
|
|
|
|
// Events emitted once connected.
|
|
|
|
componentRef.instance.output1.next('output-1c');
|
|
|
|
componentRef.instance.output1.next('output-1d');
|
|
|
|
componentRef.instance.output2.next('output-2b');
|
|
|
|
expect(events).toEqual([
|
|
|
|
{name: 'templateOutput1', value: 'output-1c'},
|
|
|
|
{name: 'templateOutput1', value: 'output-1d'},
|
|
|
|
{name: 'templateOutput2', value: 'output-2b'},
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-03-01 14:26:29 -05:00
|
|
|
describe('after connected', () => {
|
2018-02-28 12:45:11 -05:00
|
|
|
beforeEach(() => {
|
2018-03-01 14:26:29 -05:00
|
|
|
// Set up an initial value to make sure it is passed to the component
|
2018-03-02 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
2019-07-17 07:39:54 -04:00
|
|
|
strategy.setInputValue('falsyUndefined', undefined);
|
|
|
|
strategy.setInputValue('falsyNull', null);
|
|
|
|
strategy.setInputValue('falsyEmpty', '');
|
|
|
|
strategy.setInputValue('falsyFalse', false);
|
|
|
|
strategy.setInputValue('falsyZero', 0);
|
2018-03-01 14:26:29 -05:00
|
|
|
strategy.connect(document.createElement('div'));
|
|
|
|
});
|
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
it('should attach the component to the view', () => {
|
|
|
|
expect(applicationRef.attachView).toHaveBeenCalledWith(componentRef.hostView);
|
|
|
|
});
|
2018-03-01 14:26:29 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
it('should detect changes', () => {
|
|
|
|
expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalled();
|
|
|
|
});
|
2018-03-01 14:26:29 -05:00
|
|
|
|
|
|
|
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 13:08:16 -05:00
|
|
|
expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1');
|
2018-03-01 14:26:29 -05:00
|
|
|
expect(componentRef.instance.fooFoo).toBe('fooFoo-1');
|
|
|
|
});
|
|
|
|
|
2019-07-17 07:39:54 -04:00
|
|
|
it('should initialize the component with falsy initial values', () => {
|
|
|
|
expect(strategy.getInputValue('falsyUndefined')).toEqual(undefined);
|
|
|
|
expect(componentRef.instance.falsyUndefined).toEqual(undefined);
|
|
|
|
expect(strategy.getInputValue('falsyNull')).toEqual(null);
|
|
|
|
expect(componentRef.instance.falsyNull).toEqual(null);
|
|
|
|
expect(strategy.getInputValue('falsyEmpty')).toEqual('');
|
|
|
|
expect(componentRef.instance.falsyEmpty).toEqual('');
|
|
|
|
expect(strategy.getInputValue('falsyFalse')).toEqual(false);
|
|
|
|
expect(componentRef.instance.falsyFalse).toEqual(false);
|
|
|
|
expect(strategy.getInputValue('falsyZero')).toEqual(0);
|
|
|
|
expect(componentRef.instance.falsyZero).toEqual(0);
|
|
|
|
});
|
|
|
|
|
2018-03-01 14:26:29 -05:00
|
|
|
it('should call ngOnChanges with the change', () => {
|
2019-07-17 07:39:54 -04:00
|
|
|
expectSimpleChanges(componentRef.instance.simpleChanges[0], {
|
2020-03-19 07:50:25 -04:00
|
|
|
fooFoo: new SimpleChange(undefined, 'fooFoo-1', true),
|
2020-03-19 07:57:55 -04:00
|
|
|
falsyUndefined: new SimpleChange(undefined, undefined, true),
|
2020-03-19 07:50:25 -04:00
|
|
|
falsyNull: new SimpleChange(undefined, null, true),
|
|
|
|
falsyEmpty: new SimpleChange(undefined, '', true),
|
|
|
|
falsyFalse: new SimpleChange(undefined, false, true),
|
|
|
|
falsyZero: new SimpleChange(undefined, 0, true),
|
2019-07-17 07:39:54 -04:00
|
|
|
});
|
2018-02-28 12:45:11 -05:00
|
|
|
});
|
2019-07-17 07:39:54 -04:00
|
|
|
|
|
|
|
it('should call ngOnChanges with proper firstChange value', fakeAsync(() => {
|
2020-03-19 07:50:25 -04:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-2');
|
2019-07-17 07:39:54 -04:00
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
2020-03-19 07:57:55 -04:00
|
|
|
strategy.setInputValue('falsyUndefined', 'notanymore');
|
2019-07-17 07:39:54 -04:00
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
(strategy as any).detectChanges();
|
|
|
|
expectSimpleChanges(componentRef.instance.simpleChanges[1], {
|
2020-03-19 07:50:25 -04:00
|
|
|
fooFoo: new SimpleChange('fooFoo-1', 'fooFoo-2', false),
|
2019-07-17 07:39:54 -04:00
|
|
|
barBar: new SimpleChange(undefined, 'barBar-1', true),
|
2020-03-19 07:57:55 -04:00
|
|
|
falsyUndefined: new SimpleChange(undefined, 'notanymore', false),
|
2019-07-17 07:39:54 -04:00
|
|
|
});
|
|
|
|
}));
|
2018-03-01 14:26:29 -05:00
|
|
|
});
|
2018-02-28 12:45:11 -05:00
|
|
|
|
2018-03-01 14:26:29 -05:00
|
|
|
describe('when inputs change and not connected', () => {
|
|
|
|
it('should cache the value', () => {
|
2018-03-02 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1');
|
2018-03-01 14:26:29 -05: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 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
2018-03-01 14:26:29 -05:00
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
2018-03-06 17:02:25 -05:00
|
|
|
expect(componentRef.changeDetectorRef.detectChanges).not.toHaveBeenCalled();
|
2018-03-01 14:26:29 -05:00
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when inputs change and is connected', () => {
|
2020-11-04 13:46:59 -05:00
|
|
|
let viewChangeDetectorRef: ChangeDetectorRef;
|
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
beforeEach(() => {
|
|
|
|
strategy.connect(document.createElement('div'));
|
2020-11-04 13:46:59 -05:00
|
|
|
viewChangeDetectorRef = componentRef.injector.get(ChangeDetectorRef);
|
2020-04-13 19:40:21 -04:00
|
|
|
});
|
2018-03-01 14:26:29 -05:00
|
|
|
|
|
|
|
it('should be set on the component instance', () => {
|
2018-03-02 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
2018-03-01 14:26:29 -05:00
|
|
|
expect(componentRef.instance.fooFoo).toBe('fooFoo-1');
|
2018-03-02 13:08:16 -05:00
|
|
|
expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1');
|
2018-03-01 14:26:29 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should detect changes', fakeAsync(() => {
|
|
|
|
// Connect detected changes automatically
|
|
|
|
expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(1);
|
|
|
|
|
2018-03-02 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
2018-03-01 14:26:29 -05: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 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
2018-03-01 14:26:29 -05:00
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(2);
|
|
|
|
}));
|
|
|
|
|
2020-11-04 13:46:59 -05:00
|
|
|
it('should not detect changes if the input is set to the same value', fakeAsync(() => {
|
|
|
|
(componentRef.changeDetectorRef.detectChanges as jasmine.Spy).calls.reset();
|
|
|
|
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
(componentRef.changeDetectorRef.detectChanges as jasmine.Spy).calls.reset();
|
|
|
|
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
expect(componentRef.changeDetectorRef.detectChanges).not.toHaveBeenCalled();
|
|
|
|
}));
|
|
|
|
|
2018-03-01 14:26:29 -05:00
|
|
|
it('should call ngOnChanges', fakeAsync(() => {
|
2018-03-02 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
2018-03-01 14:26:29 -05: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 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
2018-03-01 14:26:29 -05: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 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
2018-03-01 14:26:29 -05: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 13:08:16 -05:00
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-2');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-2');
|
2018-03-01 14:26:29 -05: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)
|
|
|
|
});
|
|
|
|
}));
|
2020-11-04 13:45:32 -05:00
|
|
|
|
2020-11-04 13:46:59 -05:00
|
|
|
it('should not call ngOnChanges if the inout is set to the same value', fakeAsync(() => {
|
|
|
|
const ngOnChangesSpy = spyOn(componentRef.instance, 'ngOnChanges');
|
|
|
|
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
expect(ngOnChangesSpy).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
ngOnChangesSpy.calls.reset();
|
|
|
|
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
expect(ngOnChangesSpy).not.toHaveBeenCalled();
|
|
|
|
}));
|
|
|
|
|
2020-11-04 13:45:32 -05:00
|
|
|
it('should not try to call ngOnChanges if not present on the component', fakeAsync(() => {
|
|
|
|
const factory2 = new FakeComponentFactory(FakeComponentWithoutNgOnChanges);
|
|
|
|
const strategy2 = new ComponentNgElementStrategy(factory2, injector);
|
|
|
|
const changeDetectorRef2 = factory2.componentRef.changeDetectorRef;
|
|
|
|
|
|
|
|
strategy2.connect(document.createElement('div'));
|
|
|
|
changeDetectorRef2.detectChanges.calls.reset();
|
|
|
|
|
|
|
|
strategy2.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
expect(() => tick(16)).not.toThrow(); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
|
|
|
|
// If the strategy would have tried to call `component.ngOnChanges()`, an error would have
|
|
|
|
// been thrown and `changeDetectorRef2.detectChanges()` would not have been called.
|
|
|
|
expect(changeDetectorRef2.detectChanges).toHaveBeenCalledTimes(1);
|
|
|
|
}));
|
2020-11-04 13:46:59 -05:00
|
|
|
|
|
|
|
it('should mark the view for check', fakeAsync(() => {
|
|
|
|
expect(viewChangeDetectorRef.markForCheck).not.toHaveBeenCalled();
|
|
|
|
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
|
|
|
|
expect(viewChangeDetectorRef.markForCheck).toHaveBeenCalledTimes(1);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should mark the view for check once for multiple input changes', fakeAsync(() => {
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
|
|
|
|
expect(viewChangeDetectorRef.markForCheck).toHaveBeenCalledTimes(1);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should mark the view for check twice for changes in different rounds with previous values',
|
|
|
|
fakeAsync(() => {
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
|
|
|
|
expect(viewChangeDetectorRef.markForCheck).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-2');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-2');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
|
|
|
|
expect(viewChangeDetectorRef.markForCheck).toHaveBeenCalledTimes(2);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should mark the view for check even if ngOnChanges is not present on the component',
|
|
|
|
fakeAsync(() => {
|
|
|
|
const factory2 = new FakeComponentFactory(FakeComponentWithoutNgOnChanges);
|
|
|
|
const strategy2 = new ComponentNgElementStrategy(factory2, injector);
|
|
|
|
const viewChangeDetectorRef2 = factory2.componentRef.injector.get(ChangeDetectorRef);
|
|
|
|
|
|
|
|
strategy2.connect(document.createElement('div'));
|
|
|
|
(viewChangeDetectorRef2.markForCheck as jasmine.Spy).calls.reset();
|
|
|
|
|
|
|
|
strategy2.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
expect(() => tick(16)).not.toThrow(); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
|
|
|
|
// If the strategy would have tried to call `component.ngOnChanges()`, an error would have
|
|
|
|
// been thrown and `viewChangeDetectorRef2.markForCheck()` would not have been called.
|
|
|
|
expect(viewChangeDetectorRef2.markForCheck).toHaveBeenCalledTimes(1);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should not mark the view for check if the input is set to the same value', fakeAsync(() => {
|
|
|
|
(viewChangeDetectorRef.markForCheck as jasmine.Spy).calls.reset();
|
|
|
|
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
expect(viewChangeDetectorRef.markForCheck).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
(viewChangeDetectorRef.markForCheck as jasmine.Spy).calls.reset();
|
|
|
|
|
|
|
|
strategy.setInputValue('fooFoo', 'fooFoo-1');
|
|
|
|
strategy.setInputValue('barBar', 'barBar-1');
|
|
|
|
tick(16); // scheduler waits 16ms if RAF is unavailable
|
|
|
|
expect(viewChangeDetectorRef.markForCheck).not.toHaveBeenCalled();
|
|
|
|
}));
|
2018-03-01 14:26:29 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
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 12:45:11 -05:00
|
|
|
});
|
2020-06-28 20:18:19 -04:00
|
|
|
|
|
|
|
describe('runInZone', () => {
|
|
|
|
const param = 'foofoo';
|
|
|
|
const fn = () => param;
|
|
|
|
|
|
|
|
it('should run the callback directly when invoked in element\'s zone', () => {
|
|
|
|
expect(strategy['runInZone'](fn)).toEqual('foofoo');
|
|
|
|
expect(ngZone.run).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should run the callback inside the element\'s zone when invoked in a different zone',
|
|
|
|
() => {
|
|
|
|
expect(Zone.root.run(() => (strategy['runInZone'](fn)))).toEqual('foofoo');
|
|
|
|
expect(ngZone.run).toHaveBeenCalledWith(fn);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should run the callback directly when called without zone.js loaded', () => {
|
|
|
|
// simulate no zone.js loaded
|
|
|
|
(strategy as any)['elementZone'] = null;
|
|
|
|
|
|
|
|
expect(Zone.root.run(() => (strategy['runInZone'](fn)))).toEqual('foofoo');
|
|
|
|
expect(ngZone.run).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
2018-02-28 12:45:11 -05:00
|
|
|
});
|
|
|
|
|
2018-03-01 14:26:29 -05:00
|
|
|
export class FakeComponentWithoutNgOnChanges {
|
|
|
|
output1 = new Subject();
|
|
|
|
output2 = new Subject();
|
|
|
|
}
|
|
|
|
|
2018-02-28 12:45:11 -05:00
|
|
|
export class FakeComponent {
|
|
|
|
output1 = new Subject();
|
|
|
|
output2 = new Subject();
|
2018-03-01 14:26:29 -05:00
|
|
|
|
|
|
|
// Keep track of the simple changes passed to ngOnChanges
|
|
|
|
simpleChanges: SimpleChanges[] = [];
|
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
ngOnChanges(simpleChanges: SimpleChanges) {
|
|
|
|
this.simpleChanges.push(simpleChanges);
|
|
|
|
}
|
2018-02-28 12:45:11 -05:00
|
|
|
}
|
|
|
|
|
2020-11-04 13:45:32 -05:00
|
|
|
export class FakeComponentFactory<T extends Type<any>> extends ComponentFactory<T> {
|
2018-03-01 14:26:29 -05:00
|
|
|
componentRef: any = jasmine.createSpyObj(
|
2020-11-04 13:45:32 -05:00
|
|
|
'componentRef',
|
|
|
|
// Method spies.
|
|
|
|
['destroy'],
|
|
|
|
// Property spies.
|
|
|
|
{
|
|
|
|
changeDetectorRef: jasmine.createSpyObj('changeDetectorRef', ['detectChanges']),
|
|
|
|
hostView: {},
|
2020-11-04 13:46:59 -05:00
|
|
|
injector: jasmine.createSpyObj('injector', {
|
|
|
|
get: jasmine.createSpyObj('viewChangeDetectorRef', ['markForCheck']),
|
|
|
|
}),
|
2020-11-04 13:45:32 -05:00
|
|
|
instance: new this.ComponentClass(),
|
2020-11-04 13:45:32 -05:00
|
|
|
});
|
2018-02-28 12:45:11 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
get selector(): string {
|
|
|
|
return 'fake-component';
|
|
|
|
}
|
|
|
|
get componentType(): Type<any> {
|
2020-11-04 13:45:32 -05:00
|
|
|
return this.ComponentClass;
|
2020-04-13 19:40:21 -04:00
|
|
|
}
|
|
|
|
get ngContentSelectors(): string[] {
|
|
|
|
return ['content-1', 'content-2'];
|
|
|
|
}
|
2018-02-28 12:45:11 -05:00
|
|
|
get inputs(): {propName: string; templateName: string}[] {
|
|
|
|
return [
|
2018-03-01 14:26:29 -05:00
|
|
|
{propName: 'fooFoo', templateName: 'fooFoo'},
|
|
|
|
{propName: 'barBar', templateName: 'my-bar-bar'},
|
2019-07-17 07:39:54 -04:00
|
|
|
{propName: 'falsyUndefined', templateName: 'falsyUndefined'},
|
|
|
|
{propName: 'falsyNull', templateName: 'falsyNull'},
|
|
|
|
{propName: 'falsyEmpty', templateName: 'falsyEmpty'},
|
|
|
|
{propName: 'falsyFalse', templateName: 'falsyFalse'},
|
|
|
|
{propName: 'falsyZero', templateName: 'falsyZero'},
|
2018-02-28 12:45:11 -05:00
|
|
|
];
|
|
|
|
}
|
|
|
|
get outputs(): {propName: string; templateName: string}[] {
|
|
|
|
return [
|
|
|
|
{propName: 'output1', templateName: 'templateOutput1'},
|
|
|
|
{propName: 'output2', templateName: 'templateOutput2'},
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2020-11-04 13:45:32 -05:00
|
|
|
constructor(private ComponentClass: T) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
2018-02-28 12:45:11 -05:00
|
|
|
create(
|
|
|
|
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,
|
|
|
|
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
|
|
|
return this.componentRef;
|
|
|
|
}
|
|
|
|
}
|
2018-03-01 14:26:29 -05:00
|
|
|
|
|
|
|
function expectSimpleChanges(actual: SimpleChanges, expected: SimpleChanges) {
|
2020-04-13 19:40:21 -04:00
|
|
|
Object.keys(actual).forEach(key => {
|
|
|
|
expect(expected[key]).toBeTruthy(`Change included additional key ${key}`);
|
|
|
|
});
|
2018-03-01 14:26:29 -05:00
|
|
|
|
|
|
|
Object.keys(expected).forEach(key => {
|
|
|
|
expect(actual[key]).toBeTruthy(`Change should have included key ${key}`);
|
|
|
|
if (actual[key]) {
|
2020-03-19 07:50:25 -04:00
|
|
|
expect(actual[key].previousValue).toBe(expected[key].previousValue, `${key}.previousValue`);
|
|
|
|
expect(actual[key].currentValue).toBe(expected[key].currentValue, `${key}.currentValue`);
|
|
|
|
expect(actual[key].firstChange).toBe(expected[key].firstChange, `${key}.firstChange`);
|
2018-03-01 14:26:29 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|