I have implemented drag and drop components with angular and angular material
in HTML
<div cdkDropListGroup>
<div [ngClass]="itemClass"
*ngFor="let item of items; let i = index"
cdkDropList
[cdkDropListData]="i">
<div cdkDrag [cdkDragData]="i" (cdkDragEntered)="dragEntered($event)" >
<ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item }"></ng-container>
</div>
</div>
</div>
for the component itself
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);
}
}
}
the feature works fine but I am not able to unit test the dragentered()
method because I am not able to perform the drag event
In fact I tried more than ways to dispatch drag and drop event but it does not work
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();
}
are there any suggestion to be able to raise the event and cover the dragenter method in the unit test ?
in addition I tried another way to cover the method in the unit test but it also did not work
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);
}));
});
});
CodePudding user response:
I would do triggerEventHandler
for this.
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 */ });
Edit
sortedItems[0].triggerEventHandler('cdkDragEntered', {
item: {
data: {}
},
container: {
data: {
},
element: {
nativeElement: {}
}
}
});
Learn more about triggerEventHandler
here.