使用 popper.js 失败的指令测试组件

Test component with directive that using popper.js get failed

我确实在 Popper.js 上创建了一个包装指令,如下所示:

import { Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import Popper, { Placement, PopperOptions } from "popper.js";
import { Subject, merge, fromEvent } from 'rxjs';
import { filter, pluck, takeUntil } from 'rxjs/operators';

@Directive({
  selector: "[espTooltip]"
})
export class TooltipDirective implements OnInit, OnDestroy {

  // The hint to display
  @Input() target: HTMLElement;
  // Its positioning (check docs for available options)
  @Input() placement?: Placement;
  // Optional hint target if you desire using other element than specified one
  @Input() appPopper?: HTMLElement;

  @Input() text = '';

  // The popper instance
  private popper: Popper;
  private readonly defaultConfig: PopperOptions = {
    placement: "bottom",
    removeOnDestroy: true,
    modifiers: {
      arrow: {
        element: ".popper__arrow"
      }
    },
    eventsEnabled: false
  };
  private readonly destroy$ = new Subject<void>();

  constructor(
    private readonly el: ElementRef,
    private readonly renderer: Renderer2
  ) { }

  ngOnInit(): void {
    // An element to position the hint relative to
    const reference = this.appPopper ? this.appPopper : this.el.nativeElement;

    this.popper = new Popper(reference, this.target, {
      ...this.defaultConfig,
      placement: this.placement || this.defaultConfig.placement
    });

    this.renderer.setStyle(this.target, "display", "none");

    merge(
      fromEvent(reference, "mouseenter"),
      fromEvent(reference, "mouseleave")
    )
      .pipe(
        filter(() => this.popper != null),
        pluck("type"),
        takeUntil(this.destroy$)
      )
      .subscribe((e: any) => this.mouseHoverHandler(e));
  }

  ngOnDestroy(): void {
    if (!this.popper) {
      return;
    }

    this.popper.destroy();

    this.destroy$.next();
    this.destroy$.complete();
  }

  private mouseHoverHandler(e: string): void {
    if (e === "mouseenter") {
      this.renderer.removeStyle(this.target, "display");
      this.popper.enableEventListeners();
      this.popper.scheduleUpdate();
    } else {
      this.renderer.setStyle(this.target, "display", "none");
      this.popper.disableEventListeners();
    }
  }
}

然后在组件中使用了指令,之后组件测试失败并出现此错误:

TypeError: popper_js_1.default is not a constructor

      41 |     const reference = this.appPopper ? this.appPopper : this.el.nat
iveElement;
      42 | 
    > 43 |     this.popper = new Popper(reference, this.target, {
         |                   ^
      44 |       ...this.defaultConfig,
      45 |       placement: this.placement || this.defaultConfig.placement
      46 |     });

测试设置:

@Directive({
  selector: '[espTooltip]'
})
export class MockTooltipDirective {
  @Input() target: HTMLElement;
  // Its positioning (check docs for available options)
  @Input() placement?: Popper.Placement;
  // Optional hint target if you desire using other element than specified one
  @Input() appPopper?: HTMLElement;

  @Input() text = '';

  private popper: any;

  ngOnInit(): void {
    // An element to position the hint relative to
    this.popper = new Popper({clientHeight: 0, clientWidth: 0, getBoundingClientRect: jest.fn()}, null, {});
  }
}


beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        SharedModule
      ],
      declarations: [
        MockTooltipDirective,
        PanelComponent,
        ActiveJobsListComponent
      ],
    })
      .compileComponents();
  }));

我哪里做错了?

我认为 TooltipDirective 的实际实现是在 SharedModule 中,您正在导入它,然后 Angular 使用它作为实际实现。

我会做的是在 NO_ERRORS_SCHEMA 上进行存储,它会忽略未在 TestBed.configureTestingModule.

中声明的指令和 Angular 元素

像这样:

import { NO_ERRORS_SCHEMA } from '@angular/core';
....
beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        // SharedModule is removed
      ],
      declarations: [
        // MockTooltipDirective is removed
        PanelComponent,
        ActiveJobsListComponent,
        // Add all components and directives that your component relies on for the unit test
        // For instance, are you going to click on a button nested inside of a child component of this component's unit test
      ],
      Providers: [
        // Add all providers from SharedModule here, I would mock them if I were you
      ],
      schemas: [NO_ERRORS_SCHEMA],
    })
      .compileComponents();
  }));

通过这种方式,您可以删除 MockTooltipDirective(大赢),然后只押注 NO_ERRORS_SCHEMA。 Angular 将忽略所有 espTooltip 绑定。此外,随着 importsSharedModule 的删除,您的单元测试应该会更快。