angular单元测试cdkDragEntered与茉莉花

angular Unit test cdkDragEntered with jasmine

我已经使用 angular 和 angular material

实现了拖放组件

在HTML

<div class="sortable-list-view-container" cdkDropListGroup>
  <div [ngClass]="itemClass"
    *ngFor="let item of items; let i = index"
    cdkDropList
    [cdkDropListData]="i">
    <div cdkDrag [cdkDragData]="i" (cdkDragEntered)="dragEntered($event)" class="sortable-list-view-box" >
    <ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item }"></ng-container>
    </div>
  </div>
</div>

对于组件本身

import {  CdkDragEnter, moveItemInArray } from '@angular/cdk/drag-drop';
import { Component,
  ViewEncapsulation,
  Input,
  ContentChild,
  TemplateRef,
  Output,
  EventEmitter,
} from '@angular/core';

@Component({
    selector: 'sortable-list-view',
    templateUrl: './sortable-list-view.component.html',
    styleUrls: ['./sortable-list-view.component.scss'],
    encapsulation: ViewEncapsulation.None
})

export class SortableListViewComponent {
    @Input('items')
    public items: any[] = [];

    @Input('item-class')
    public itemClass: string;

    @Output('sort')
    public sort: EventEmitter<any> = new EventEmitter();

    @ContentChild(TemplateRef)
    public itemTemplate: TemplateRef<any>;

    private phElement: HTMLElement;

    public dragEntered(event: CdkDragEnter<number>) {
      const drag = event.item;
      const dropList = event.container;
      const dragIndex = drag.data;
      const dropIndex = dropList.data;
      const phContainer = dropList.element.nativeElement;

      this.phElement = phContainer.querySelector('.cdk-drag-placeholder');

      if( this.phElement != null){
        this.phElement.style.width = (phContainer.clientWidth - 2) + 'px';
        this.phElement.style.height = phContainer.clientHeight + 'px';
        phContainer.removeChild(this.phElement);
        phContainer.parentElement.insertBefore( this.phElement, phContainer);
      }
      if(dropIndex !== dragIndex){
        moveItemInArray(this.items, dragIndex, dropIndex);
        this.sort.emit(this.items);
      }
    }
    }

该功能运行良好,但我无法对 dragentered() 方法进行单元测试,因为我无法执行拖动事件

事实上,我尝试了多种方法来调度拖放事件,但它不起作用

import {ComponentFixture, TestBed , fakeAsync, flush} from '@angular/core/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import { SortableListViewComponent } from './sortable-list-view.component';
import { SortableListViewModule } from './sortable-list-view.module';
// import { ViewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Component, ViewChild } from '@angular/core';
// import { By } from '@angular/platform-browser';

@Component({
  selector: 'test-component',
  template: `<sortable-list-view [items]="items">
  <ng-template let-item>{{item}}</ng-template>
</sortable-list-view>`
})
class TestSortableListViewComponent {
  @ViewChild(SortableListViewComponent) public sortableList: SortableListViewComponent;
  public items = [1, 2, 3, 4, 5];
}

fdescribe('SortableListViewComponent', () => {
  let component: TestSortableListViewComponent;
  let fixture: ComponentFixture<TestSortableListViewComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [TestSortableListViewComponent],
      imports: [NoopAnimationsModule, AmmSortableListViewModule]
    });
    fixture = TestBed.createComponent(TestSortableListViewComponent);
    component = fixture.componentInstance;

    fixture.detectChanges();

    // spyOn(component.sortableList.sort, 'emit');
  });

  fdescribe('dragEntered()', () => {

    it('should dispatch an event and call dragEntered()', fakeAsync(() => {
      const spyOnDrag = spyOn(component.sortableList ,'dragEntered');
    //   component.items = [
    //     { id: 1, name: 'IronMan', strength: 8 },
    //     { id: 2, name: 'Batman', strength: 8 },
    //     { id: 3, name: 'CaptainAmerica', strength: 7 },
    //     { id: 4, name: 'SuperMan', strength: 9 }
    // ];

      fixture.detectChanges();
      const sortedItems = fixture.debugElement.queryAll(By.css('.sortable-list-view-container .sortable-list-view-box'));
      // const drag = sortedItems[0].nativeElement;
      // const drop = sortedItems[1].nativeElement;
      const dargEvent = new DragEvent('drag');
      const dropEvent = new DragEvent('drop');
      sortedItems[0].nativeElement.dispatchEvent(dargEvent);
      fixture.detectChanges();
      sortedItems[0].nativeElement.dispatchEvent(dropEvent);
      fixture.detectChanges();

      // sortedItems[0].nativeElement.dispatchEvent(new MouseEvent('mousemove', { clientX: 10 , clientY: 20}));
      // sortedItems[1].nativeElement.dispatchEvent(new MouseEvent('mouseup', { clientX: 10 , clientY: 20}));
      // sortedItems[0].triggerEventHandler('mousemove',{pageX:60, pageY: 50});
      // sortedItems[0].triggerEventHandler('mouseup',{pageX:10, pageY: 10});

      // dragElement(fixture,drag,drop);
      // fixture.detectChanges();

      expect(spyOnDrag).toHaveBeenCalled();

    }));
  });
});



export function dispatchMouseEvent(source: Node, type: string, x: number, y: number) {
  const event = new MouseEvent(type,{
   screenX:x,
   screenY:y,
   clientX:x,
   clientY:y
  });

  source.dispatchEvent(event);
}

export function dragElement(fixture: ComponentFixture<TestSortableListViewComponent>, source: HTMLElement, target: HTMLElement) {
  console.log(fixture.nativeElement);
  const { left, top } = target.getBoundingClientRect();

  dispatchMouseEvent(source, 'mousedown', 0, 0);
  fixture.detectChanges();
  dispatchMouseEvent(document, 'mousemove', 0, 0);
  fixture.detectChanges();
  dispatchMouseEvent(document, 'mousemove', top + 1, left + 1);
  fixture.detectChanges();
  dispatchMouseEvent(document, 'mouseup', top + 1, left + 1);
  fixture.detectChanges();

  flush();
}

有什么建议可以引发事件并在单元测试中覆盖 dragenter 方法吗?

此外,我尝试了另一种方法来覆盖单元测试中的方法,但它也没有用

  import {ComponentFixture, fakeAsync, TestBed}from '@angular/core/testing';
    import {NoopAnimationsModule} from '@angular/platform-browser/animations';
    import { SortableListViewComponent } from './sortable-list-view.component';
    import { SortableListViewModule } from './sortable-list-view.module';
    import { By } from '@angular/platform-browser';
    import {  Component, ViewChild } from '@angular/core';
    import {   CdkDragEnter,CdkDropList } from '@angular/cdk/drag-drop';
    
    @Component({
      selector: 'sortable-test-component',
      template: `<amm-sortable-list-view [items]="items">
      <ng-template let-item>{{item}}</ng-template>
        </amm-sortable-list-view>`
    })
    class TestSortableListViewComponent  {
      @ViewChild(SortableListViewComponent) public sortableList: SortableListViewComponent;
      public items = [1, 2, 3, 4, 5];
    
    }
    
    describe('SortableListViewComponent', () => {
      let component: TestSortableListViewComponent;
      let fixture: ComponentFixture<TestSortableListViewComponent>;
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          declarations: [TestSortableListViewComponent],
          imports: [NoopAnimationsModule, AmmSortableListViewModule]
        });
        fixture = TestBed.createComponent(TestSortableListViewComponent);
        component = fixture.componentInstance;
    
        fixture.detectChanges();
    
         spyOn(component.sortableList.sort, 'emit');
      });
    
     fdescribe('dragEntered()', async () => {
    
        it('should dispatch an event and call dragEntered()', fakeAsync( () => {
    
        const sortedItems = fixture.debugElement.queryAll(By.css('.sortable-list-view-container .sortable-list-view-box'));
    
        const placeHolder = document.createElement('span');
        placeHolder.classList.add('cdk-drag-placeholder');
         sortedItems[0].parent.nativeElement.append(placeHolder);
         fixture.detectChanges();
    
           const drop = sortedItems[1].injector.get(CdkDropList);
          // when replace drop with  sortedItems[0].parent.nativeElement in dragEnter for container
          // it works but in  dragEntered() line 38 throw  error which dropList.element = null
          // (when we call spy with Args)
    
          const dragEnter =  {container:drop ,item: sortedItems[0].nativeElement} as CdkDragEnter<number>;
    
          // const dragEnter =  {container:sortedItems[0].parent.nativeElement ,item: sortedItems[0].nativeElement} as CdkDragEnter<number>;
    
    
          // this line works but no code coverage detected
          const spyOnDrag = spyOn(component.sortableList ,'dragEntered');
    
    
          // this line cause karma browser freezing show no error
          // const spyOnDrag = spyOn(component.sortableList ,'dragEntered').withArgs(dragEnter)
          //                                                                 .and.callThrough();
    
          component.sortableList.dragEntered(dragEnter);
    
          fixture.detectChanges();
    
          expect(spyOnDrag).toHaveBeenCalledWith(dragEnter);
    
        }));
    
      });
    });

我会为此做 triggerEventHandler

const sortedItems = fixture.debugElement.queryAll(By.css('.sortable-list-view-container .sortable-list-view-box'));
// the first string is the event you would like to trigger
// the 2nd argument is the $event to be passed down to the callback 
// And in this case, the call back is dragEntered
sortedItems[0].triggerEventHandler('cdkDragEntered', {/* mock $event here for dragEntered */ });

编辑

sortedItems[0].triggerEventHandler('cdkDragEntered', {
  item: {
    data: {}
  },
  container: {
    data: {
    },
    element: {
      nativeElement: {}
    }
  }
});

详细了解 triggerEventHandler here