Тестовый компонент с директивой, что с использованием popper. js получить не удалось - PullRequest
1 голос
/ 02 марта 2020

Я создал директиву обертки над 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();
  }));

Что я тут не так делаю?

1 Ответ

1 голос
/ 02 марта 2020

Я думаю, что ваша фактическая реализация TooltipDirective находится в вашем SharedModule, и вы импортируете ее, а затем Angular использует это как фактическую реализацию.

Я бы сделал ставку на NO_ERRORS_SCHEMA, где игнорируются директивы и Angular элементы, которые не объявлены в TestBed.configureTestingModule.

Примерно так:

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. Кроме того, с удалением SharedModule в imports ваш модульный тест должен быть быстрее.

...