Angular jasmine fixture.detectChanges如何触发directive的set方法

120 阅读1分钟

测试代码:

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的原创文章,尽在:“汪子熙”: