【问题标题】:How to test transcluded content in Angular?如何在 Angular 中测试嵌入的内容?
【发布时间】:2018-02-10 09:21:13
【问题描述】:

在使用 <ng-content> 测试具有嵌入槽的 Angular 组件时,我们没有 显式意味着检查嵌入的内容是否按预期放置在组件内。 例如:

// base-button.component.ts
@Component({
  selector: 'base-button',
  template: `<button [type]="type">
    <ng-content></ng-content>
  </button>`,
})
export class BaseButtonComponent {
  @Input() type = 'button';
}

基本上,在 spec 文件中创建组件实例时,我们会这样做:

// base-button.component.spec.ts
it('should reflect the `type` property into the "type" attribute of the button', () => {
  const fixture = TestBed.createComponent(BaseButtonComponent);
  fixture.detectChanges();

  const { componentInstance, nativeElement } = fixture;
  componentInstance.type = 'reset';

  const button = nativeElement.querySelector('button');
  expect(button.type === 'reset');
});

我们可以对组件的每个属性和方法都这样做,但是 嵌入的内容?一种解决方法是为测试目的创建一个主机组件:

// base-button.component.spec.ts
...
@Component({
  template: `<base-button>Foo bar</base-button>`
})
export class BaseButtonHostComponent {}
...

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ BaseButtonComponent, BaseButtonHostComponent ]
    })
    .compileComponents();
  }));

  it('should transclude the content correctly', () => {
    const hostFixture = TestBed.createComponent(BaseButtonHostComponent);
    hostFixture.detectChanges();
    const button = hostFixture.nativeElement.querySelector('button');
    expect(button.textContent === 'Foo bar');
  });
...

但是,正如您可以想象的那样,这很不方便,还因为必须这样做 对于每个具有嵌入内容的组件,并且可能对于每个 &lt;ng-content&gt; 元素 在其模板中。有没有其他方法可以做到这一点?

【问题讨论】:

    标签: javascript angular unit-testing testing


    【解决方案1】:

    确实有一种相当晦涩的方法来做到这一点。基本上,TestBed.createComponent 调用 组件的工厂create 方法,也支持可投影的DOM节点 插入到嵌入槽中。

    // @angular/core/testing.js
    createComponent(component) {
      ...
      const componentFactory = this._compiler.getComponentFactory(component);
      ...
      const componentRef = componentFactory.create(Injector.NULL, [], `#${rootElId}`, this._moduleRef);
      ...
    }
    

    我们必须这样做,诀窍如下:

    // base-button.component.spec.ts
    describe('BaseButtonComponent', () => {
      let factory: ComponentFactory<BaseButtonComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ BaseButtonComponent ]
        })
        .overrideModule(BrowserDynamicTestingModule, {
          set: {
            entryComponents: [ BaseButtonComponent ]
          }
        })
        .compileComponents();
    
        const resolver = <ComponentFactoryResolver>TestBed.get(ComponentFactoryResolver, null);
        factory = resolver.resolveComponentFactory(BaseButtonComponent);
      }));
    
      it('should transclude the provided nodes into the button', () => {
        const tnode = document.createTextNode('Foo bar');
        const componentRef = factory.create(Injector.NULL, [[ tnode ]]);
        const button = componentRef.location.nativeElement.querySelector('button');
        expect(button.textContent === 'Foo bar');
      });
    });
    

    TestBed.get 允许我们检索ComponentFactoryResolver 服务。为了找回 但是,组件的工厂,组件的类必须在模块的entryComponents 中列出 财产。有问题的模块是 BrowserDynamicTestingModuleTestBed 暴露了一个方便的 方法来改变它的属性。

    一旦你有了工厂,诀窍就来了。唯一烦人的部分是生成所有 手动投影节点,因此您可以为此创建一个实用函数:

    function createComponentWithContents(factory, ...contents) {
      const template = document.createElement('template');
      const projectableNodes = contents.map(html => {
        template.innerHTML = html;
        return [ ...template.content.childNodes ];
      });
      return factory.create(Injector.NULL, projectableNodes);
    }
    
    const componentRef = createComponentWithContents(factory, '<i class="fa fa-star"></i> Win!');
    

    很遗憾TestBed.createComponent 不允许立即这样做。

    【讨论】:

    猜你喜欢
    • 2018-01-26
    • 1970-01-01
    • 2018-06-04
    • 2011-11-14
    • 2014-02-26
    • 2022-01-02
    • 2015-11-07
    • 1970-01-01
    • 2017-11-07
    相关资源
    最近更新 更多