使用 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
绑定。此外,随着 imports
中 SharedModule
的删除,您的单元测试应该会更快。
我确实在 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
.
像这样:
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
绑定。此外,随着 imports
中 SharedModule
的删除,您的单元测试应该会更快。