如何从 jasmine 测试中触发文档级事件 Angular 2/4

How to trigger document level events from a jasmine test Angular 2/4

根据 Angular 测试文档,要从测试中触发事件,我们在调试元素上使用 triggerEventHandler() 方法。此方法采用 event nameobject。现在,如果我们使用 HostListener 添加事件,这将起作用。例如:@HostListener('mousemove', ['$event']) 或添加 document 关卡事件,我们可以这样做 @HostListener('document:mousemove', ['$event']).

在我当前的指令实现中,由于无法嵌套 HostListeners,我使用 HostListener.
中的 document.addEventListener 添加了 document 级别事件 代码如下:

@HostListener('mousedown', ['$event'])
callMouseDown(event){
  if (something) {
   document.addEventListener('mousemove', this.callMouseMove.bind(this));
  }
}

callMouseMove(event){
 // do some computations.
}

现在,我想触发在我的测试中添加到 document 级别的 mousemove 事件。 triggerEventHandler() 的当前实现不起作用,即在测试中未触发侦听器。

我怎样才能让它工作?谁能帮我一些指导。

编辑:添加测试:

it('test spec', inject([MyService], (service: MyService) =>{
   x = [];
  //service calls the method
  service.evtEmit.subscribe(e => {
   x.push(e);
  });
  triggerEventHandler("mousedown", {pageX: 200, pageY: 300});
  triggerEventHandler("document:mousemove", {pageX: 250, pageY: 320});
  // the above statement won't work.
  expect(arr).toEqual({x:50, y: 20});
}));

您描述的类似问题似乎在 angular 存储库上记录为问题 here

您可以使用组件实例访问指令并可以访问该方法(即,在您的情况下是 callMouseMove)。 以下方法应该有效:

it('test spec', inject([MyService], (service: MyService) => {
   x = [];
  //service calls the method
  service.evtEmit.subscribe(e => {
   x.push(e);
  });
  triggerEventHandler("mousedown", {pageX: 200, pageY: 300});
  component.directive.callMouseMove({pageX: 250, pageY: 320});
  expect(arr).toEqual({x:50, y: 20});
}));

希望这对您有所帮助。

更新

我意识到如果你的指令中有私有方法,我的方法就不起作用了。最好以编程方式创建自定义事件或在这种情况下创建鼠标事件,并在文档级别或元素本身触发事件。 yurzui 提到了这个方法。多亏了他。

你可以这样做:

function programmaticTrigger(eventType: string, evtObj: any) {
   let evt = new MouseEvent(eventType, evtObj);
   document.dispatchEvent(evt);
}

您可以从您的规范中调用此函数:

programmaticTrigger("mousemove", {clientX: 250, clientY: 320});

请注意,在这种情况下,我没有传递 pageXpageY,因为它们是 readonly,但它们是根据 clientXclientY 值。

如果你想创建一个自定义事件并传入任何你想要的值,你可以这样做:

function programmaticTrigger(eventType: string, evtObj: any) {
       let evt: any;
       evt = Event(eventType, evtObj);
       document.dispatchEvent(evt);
    }

调用如下:

programmaticTrigger("mousemove", {pageX: 250, pageY: 320});

希望对您有所帮助。

我有解决这个问题的替代方法。如果您想知道为什么 ShellZero 的答案对我不起作用,请参阅下文。

我的解决方案是创建一个可以 return document 的可注入“包装器”服务。这样,在生产中它像往常一样获取文档,但在测试中,我可以模拟包装器并提供我自己的“文档”并向其发送事件。

文档-wrapper.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DocumentWrapperService {

  constructor() { }

  public getDocument() {
    return document;
  }
}

在我的组件中,我可以在构造函数中注入此服务并从中获取 document。就我而言,我在 ngOnInit 方法中使用它。

some.component.ts

import { DocumentWrapperService } from './../../../services/document-wrapper/document-wrapper.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'some-component',
  template: '<div id="container"><!-- Your HTML here --></div>',
  styleUrls: ['./some.component.css']
})
export class SomeComponent implements OnInit {
constructor(private documentWrapper: DocumentWrapperService) { }

  ngOnInit() {
    const _document = this.documentWrapper.getDocument();

    _document.addEventListener('mouseup', ($event: MouseEvent) => {
      // do something on mouse-up
    });

}

这需要在测试中做一些额外的工作。我必须制作一个模拟包装器并注入它。默认情况下,我的模拟 returns document 因此现有测试不会中断。

import { DocumentWrapperService } from './../../../services/document-wrapper/document-wrapper.service';
import * as td from 'testdouble';

describe('SomeComponent', () => {
  beforeEach(async(() => {
    // I use testdouble to make my mock, but you could roll your own or use some other library.
    mockDocumentWrapper = td.object(DocumentWrapperService.prototype);
    td.when(mockDocumentWrapper.getDocument()).thenReturn(document);

    TestBed.configureTestingModule({
      declarations: [SomeComponent],
      providers: [
        { provide: DocumentWrapperService, useValue: mockDocumentWrapper }
      ]
    })
      .compileComponents();
  }));

然后,在我测试事件处理的规范方法中,我必须将模拟设置为 return 不同的元素而不是 document。我发现的最好的事情是使用组件本身的最外层 div。因为我对addEventListener的调用在ngOnInit中,所以我也必须再次调用ngOnInit。一旦完成,我就可以自由地发送事件并做出我的期望。

it("should do something when the user releases the mouse button", () => {
  const rootDivElement = fixture.nativeElement.querySelector("#container");
  td.when(mockDocumentWrapper.getDocument()).thenReturn(rootDivElement);
  component.ngOnInit();

  rootDivElement.dispatchEvent(new MouseEvent('mouseup', { clientY: 100, clientX: 200 }));

  // expectations go here
});

虽然 ShellZero 的答案是我能找到的最好的答案,但我并不满意。在 Angular 组件上测试事件处理程序时,我认为调用组件本身的处理程序方法是不够的,因为它不能证明组件已经订阅了正确的事件。我更喜欢触发事件并期望组件正确反应。

ShellZero 直接回答中的“更新”部分在我实施时不起作用。我认为这是因为 Karma 将 Angular 组件放在一个 iFrame 中,它无法访问根文档。如果那是错误的,我很想知道。

我不喜欢我的解决方案的一件事是它添加了只需要进行测试的生产代码。我通常更愿意在我的测试中跳过很多环节,以避免为了测试而更改生产代码。在这种情况下,我看不到这样做的方法。