如何在 Angular 中测试嵌入的内容? [英] How to test transcluded content in Angular?

查看:38
本文介绍了如何在 Angular 中测试嵌入的内容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在测试具有带有 的嵌入槽的 Angular 组件时,我们没有明确表示检查嵌入的内容是否按预期放置在组件内.例如:

While testing an Angular component that has transclusion slots with <ng-content>, we have no explicit means to check if the transcluded content is placed as intended inside the component. For example:

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

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

Basically, when creating a component instance in the spec file, we do this:

// 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');
});

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

We can do this for every property and method of the component, but what about the transcluded content? A workaround would be creating a host component for test purposes:

// 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');
  });
...

但是,正如你想象的那样,这是相当不方便的,也是因为必须这样做对于每个包含嵌入内容的组件,可能还有每个 元素在其模板中.还有其他方法可以做到这一点吗?

But, as you could imagine, this is rather inconvenient, also because this has to be done for every component with transcluded content, and possibly for every <ng-content> element in its template. Is there another way to do this?

推荐答案

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

There's indeed a rather obscure way to do it. Basically, TestBed.createComponent invokes the component's factory create method, which also supports projectable DOM nodes to be inserted into transclusion slots.

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

我们必须做同样的事情,这就是诀窍:

We have to do the same, and here's the trick:

// 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 公开了一个方便的改变其属性的方法.

TestBed.get allows us to retrieve the ComponentFactoryResolver service. In order to retrieve the component's factory, though, the component's class must be listed in the module's entryComponents property. The module in question is BrowserDynamicTestingModule and TestBed exposes a handy method to alter its properties.

一旦你有了工厂,诀窍就派上用场了.唯一令人讨厌的部分是生成所有手动可投影节点,因此您可以为此创建实用程序:

Once you have the factory, the trick is served. The only annoying part is generating all the projectable nodes by hand, so you can create a utility function for that:

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 不允许立即这样做.

It's a shame that TestBed.createComponent doesn't allow to do that right away.

这篇关于如何在 Angular 中测试嵌入的内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆