ObaidUrRehman / ng-drag-drop

Drag & Drop for Angular - based on HTML5 with no external dependencies. :tada:
MIT License
239 stars 123 forks source link

Error in ngOnDestroy() method of Droppable directive while doing unit test #69

Open OutOfNutella opened 7 years ago

OutOfNutella commented 7 years ago

Hello,

I'm using your excellent ng2-drag-drop module in an application. Today I started to create some tests in my Angular 2 app.

I have a panel which is draggable from a left sidebar to the main panel is the center.

But, when it comes to test my component containing droppable div, the command "ng test" try to run the default test "should create" of my component and it stopped with the following errors :

Chrome 59.0.3071 (Windows 10 0.0.0) ChartPanelToolbarComponent should create FAILED
        TypeError: Cannot read property 'unsubscribe' of undefined
            at Droppable.ngOnDestroy (webpack:///~/ng2-drag-drop/src/directives/droppable.directive.js:57:0 <- src/test.ts:110419:35)
            at Wrapper_Droppable.ngOnDestroy (/Ng2DragDropModule/Droppable/wrapper.ngfactory.js:17:16)
            at CompiledTemplate.proxyViewClass.View_GridPanelComponent0.destroyInternal (/GridPanelModule/GridPanelComponent/component.ngfactory.js:185:23)
            at CompiledTemplate.proxyViewClass.AppView.destroy (webpack:///~/@angular/core/src/linker/view.js:203:0 <- src/test.ts:93603:14)
            at CompiledTemplate.proxyViewClass.DebugAppView.destroy (webpack:///~/@angular/core/src/linker/view.js:606:0 <- src/test.ts:94006:38)
            at CompiledTemplate.proxyViewClass.View_AppComponent0.destroyInternal (/DynamicTestModule/AppComponent/component.ngfactory.js:302:20)
            at CompiledTemplate.proxyViewClass.AppView.destroy (webpack:///~/@angular/core/src/linker/view.js:203:0 <- src/test.ts:93603:14)
            at CompiledTemplate.proxyViewClass.DebugAppView.destroy (webpack:///~/@angular/core/src/linker/view.js:606:0 <- src/test.ts:94006:38)
            at CompiledTemplate.proxyViewClass.View_AppComponent_Host0.destroyInternal (/DynamicTestModule/AppComponent/host.ngfactory.js:33:19)
            at CompiledTemplate.proxyViewClass.AppView.destroy (webpack:///~/@angular/core/src/linker/view.js:203:0 <- src/test.ts:93603:14)
            at CompiledTemplate.proxyViewClass.DebugAppView.destroy (webpack:///~/@angular/core/src/linker/view.js:606:0 <- src/test.ts:94006:38)
            at CompiledTemplate.proxyViewClass.AppView.detachAndDestroy (webpack:///~/@angular/core/src/linker/view.js:187:0 <- src/test.ts:93587:14)
            at ComponentRef_.destroy (webpack:///~/@angular/core/src/linker/component_factory.js:147:51 <- src/test.ts:42620:70)
            at ComponentFixture.destroy (webpack:///~/@angular/core/bundles/core-testing.umd.js:263:0 <- src/test.ts:12171:35)
            at webpack:///~/@angular/core/bundles/core-testing.umd.js:728:61 <- src/test.ts:12636:78

When I remove the "droppable" attribute in my div, the test pass.

I'm using Angular CLI 1.0.0-beta.28.3 and ng2-drag-drop 2.0.1

You can reproduce it with the following steps :

npm install -g angular-cli@1.0.0-beta.28.3
ng new ng2-drag-drop-test-app
cd ng2-drag-drop-test-app
npm install ng2-drag-drop --save
ng generate module subcomponent
ng generate component subcomponent
ng generate module sidebar
ng generate component sidebar

Then edit some files :

src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { SubcomponentModule } from './subcomponent/subcomponent.module';
import { SidebarModule } from './sidebar/sidebar.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    SubcomponentModule,
    SidebarModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class AppModule { }

src/app/app.component.html

<app-sidebar></app-sidebar>
<app-subcomponent></app-subcomponent>

src/app/app.component.spec.ts

/* tslint:disable:no-unused-variable */
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { SubcomponentModule } from './subcomponent/subcomponent.module';
import { SidebarModule } from './sidebar/sidebar.module';

describe('AppComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      imports: [
        SubcomponentModule,
        SidebarModule
      ],
      schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
    });
    TestBed.compileComponents();
  });

  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));
});

src/app/sidebar/sidebar.module.ts

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Ng2DragDropModule } from 'ng2-drag-drop';

import { SidebarComponent } from './sidebar.component';

@NgModule({
  imports: [
    CommonModule,
    Ng2DragDropModule
  ],
  exports: [
    SidebarComponent
  ],
  declarations: [SidebarComponent],
  schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class SidebarModule { }

src/app/sidebar/sidebar.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-sidebar',
  templateUrl: './sidebar.component.html',
  styleUrls: ['./sidebar.component.css']
})
export class SidebarComponent implements OnInit {
  items = [{name: 'Apple', type: 'fruit'},
          {name: 'Carrot', type: 'vegetable'},
          {name: 'Orange', type: 'fruit'}];

  constructor() { }

  ngOnInit() {
  }

}

src/app/sidebar/sidebar.component.html

<div class="col-sm-3">
    <ul class="list-group">
        <li draggable *ngFor="let item of items" [dragData]="item" class="list-group-item">{{item.name}}</li>
    </ul>
</div>

src/app/sidebar/sidebar.component.spec.ts

/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { SidebarComponent } from './sidebar.component';
import { Ng2DragDropModule } from 'ng2-drag-drop';

describe('SidebarComponent', () => {
  let component: SidebarComponent;
  let fixture: ComponentFixture<SidebarComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ SidebarComponent ],
      imports: [
        Ng2DragDropModule
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(SidebarComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

src/app/subcomponent/subcomponent.module.ts

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Ng2DragDropModule } from 'ng2-drag-drop';

import { SubcomponentComponent } from './subcomponent.component';

@NgModule({
  imports: [
    CommonModule,
    Ng2DragDropModule
  ],
  exports: [
    SubcomponentComponent
  ],
  declarations: [SubcomponentComponent],
  schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class SubcomponentModule { }

src/app/subcomponent/subcomponent.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-subcomponent',
  templateUrl: './subcomponent.component.html',
  styleUrls: ['./subcomponent.component.css']
})
export class SubcomponentComponent implements OnInit {
  droppedItems = [];

  constructor() { }

  ngOnInit() {
  }

  onItemDrop(e: any) {
      // Get the dropped data here
      this.droppedItems.push(e.dragData);
  }
}

src/app/subcomponent/subcomponent.component.spec.ts

/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { SubcomponentComponent } from './subcomponent.component';
import { Ng2DragDropModule } from 'ng2-drag-drop';

describe('SubcomponentComponent', () => {
  let component: SubcomponentComponent;
  let fixture: ComponentFixture<SubcomponentComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ SubcomponentComponent ],
      imports: [
        Ng2DragDropModule
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(SubcomponentComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

src/app/subcomponent/subcomponent.html.ts

<div class="panel panel-default" droppable (onDrop)="onItemDrop($event)">
  <div class="panel-heading">Drop Items here</div>
  <div class="panel-body">
      <li *ngFor="let item of droppedItems" class="list-group-item">{{item.name}}</li>
  </div>
</div>

Then run ng test and see the errors :) I have a different error with this test I've created for this posts, but it's still the same about the droppable div. If i remove "droppable" attribute from the div inside the subcomponent component, then the test passes.

Sorry for the long post, i didn't find the way to do these tests on plunker