测试代码:
import { Component } from '@angular/core';
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { FocusDirective } from './focus.directive';
import { KeyboardFocusService } from './services';
import { By } from '@angular/platform-browser';
@Component({
selector: 'cx-host',
template: ` <div id="b" [cxFocus]="{autofocus: ':host'}" [cxRefreshFocusOn]="jerry"></div> `,
})
class MockComponent {
jerry = 1;
}
class MockKeyboardFocusService {
get() {}
set() {}
shouldFocus() {}
getPersistenceGroup() {}
findFirstFocusable(){}
hasPersistedFocus(){}
}
describe('FocusDirective', () => {
let fixture: ComponentFixture<MockComponent>;
let component: MockComponent;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [FocusDirective, MockComponent],
providers: [
{
provide: KeyboardFocusService,
useClass: MockKeyboardFocusService,
},
],
}).compileComponents();
fixture = TestBed.createComponent(MockComponent);
component = fixture.componentInstance;
fixture.detectChanges();
})
);
it('should focus itself', () => {
let service: KeyboardFocusService;
service = TestBed.inject(KeyboardFocusService);
const host = fixture.debugElement.query(By.css('#b'));
const el = host.nativeElement;
spyOn(service, 'findFirstFocusable').and.returnValue(el);
spyOn(el, 'focus').and.callThrough();
fixture.detectChanges();
const event = {
preventDefault: () => {},
stopPropagation: () => {},
};
host.triggerEventHandler('focus', event);
expect(el.focus).toHaveBeenCalled();
expect(service.findFirstFocusable).toHaveBeenCalled();
component.jerry = 2;
fixture.detectChanges();
});
});
在单元测试代码里显式修改Component的jerry属性为2:
detectChanges会触发ngZone的run方法,进而调用_tick函数:
以callback的方式回调_tick():
changeDetectorRef指向RootViewRef, 因此执行Root view的change detection策略:
最后执行到refreshView里的executeTemplate方法:
执行template function:
待更新template的属性名称:cxRefreshFocusOn, 属性值:2
function elementPropertyInternal(tView, tNode, lView, propName, value, renderer, sanitizer, nativeOnly) {
ngDevMode && assertNotSame(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
const element = getNativeByTNode(tNode, lView);
let inputData = tNode.inputs;
let dataValue;
if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
setInputsForProperty(tView, lView, dataValue, propName, value);
if (isComponentHost(tNode))
markDirtyIfOnPush(lView, tNode.index);
if (ngDevMode) {
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
}
}
这个33是element在LView中的索引值:
从lView里根据索引值33拿到directive实例:
最后设置该实例的值为2:
更多Jerry的原创文章,尽在:“汪子熙”: