import { } from 'jasmine' ; import * as td from 'testdouble' ; import { TestBed , inject , async , ComponentFixture , fakeAsync , tick } from '@angular/core/testing' ; import { DragulaDirective } from '../components/dragula.directive' ; import { DragulaService } from '../components/dragula.service' ; import { DrakeWithModels } from '../DrakeWithModels' ; import { Group } from '../Group' ; import { DrakeFactory } from '../DrakeFactory' ; import { EventTypes } from '../EventTypes' ; import { MockDrake , MockDrakeFactory } from '../MockDrake' ; import { Component , ElementRef } from "@angular/core" ; import { TestHostComponent , TwoWay , Asynchronous } from './test-host.component' ; import { Subject , BehaviorSubject , Observable , empty } from 'rxjs' ; import { DragulaOptions } from '../DragulaOptions' ; import { StaticService } from './StaticService' ; const GROUP = "GROUP" ; type SimpleDrake = Partial < DrakeWithModels > ; describe ( 'DragulaDirective' , ( ) => { let fixture ; let component ; let service ; beforeEach ( ( ) => { service = new StaticService ( ) ; TestBed . configureTestingModule ( { declarations : [ DragulaDirective , TestHostComponent , TwoWay , Asynchronous ] , providers : [ { provide : DrakeFactory , useValue : MockDrakeFactory } , { provide : DragulaService , useValue : service } ] } ) . compileComponents ( ) ; fixture = TestBed . createComponent ( TestHostComponent ) ; component = fixture . componentInstance ; component . group = GROUP ; } ) ; afterEach ( ( ) => { fixture . destroy ( ) ; td . reset ( ) ; service . clear ( ) ; } ) ; const mockMultipleDrakes = ( ... pairs ) => { const find = td . function ( ) ; pairs . forEach ( ( [ drake , name ] ) => { let group = new Group ( name , drake as any as DrakeWithModels , { } ) ; td . when ( find ( name ) ) . thenReturn ( group ) ; } ) ; td . replace ( service , 'find' , find ) ; return find ; } ; const mockDrake = ( drake , name = GROUP ) => { return mockMultipleDrakes ( [ drake , name ] ) ; } ; const simpleDrake = ( containers = [ ] , models = [ ] ) => { return { containers , models } as SimpleDrake ; } ; const expectFindsInSequence = ( find , seq ) => { let captor = td . matchers . captor ( ) ; td . verify ( find ( captor . capture ( ) ) ) ; let i = 0 ; expect ( captor . values . length ) . toBe ( seq . length ) ; seq . forEach ( val => expect ( captor . values [ i ++ ] ) . toBe ( val ) ) ; } ; it ( 'should initialize with new drake and call DragulaService.createGroup' , ( ) => { component . group = GROUP ; component . model = [ ] ; fixture . detectChanges ( ) ; let group = service . find ( GROUP ) ; expect ( group ) . toBeTruthy ( 'group not created' ) ; expect ( group . drake . models ) . toBeTruthy ( 'group.drake should have models' ) ; } ) ; it ( 'should not setup with drake.models when dragulaModel == null' , ( ) => { component . group = GROUP ; component . model = null ; fixture . detectChanges ( ) ; let group = service . find ( GROUP ) ; expect ( group . drake . models ) . toBeFalsy ( ) ; } ) ; it ( 'should initialize and add to existing drake' , ( ) => { let theirs = [ { someVar : 'theirs' } ] ; let mine = [ { someVar : 'mine' } ] ; let drake = simpleDrake ( [ document . createElement ( 'div' ) ] , [ theirs ] ) ; mockDrake ( drake ) ; component . model = mine ; fixture . detectChanges ( ) ; expect ( drake . containers . length ) . toBe ( 2 ) ; expect ( drake . models . length ) . toBe ( 2 ) ; } ) ; it ( 'should update the model value on existing drake.models' , ( ) => { let myModel = [ 'something' ] ; let newModel = [ 'something new' ] ; let drake = simpleDrake ( [ document . createElement ( 'div' ) ] , [ myModel ] ) ; mockDrake ( drake ) ; component . model = myModel ; fixture . detectChanges ( ) ; component . model = newModel ; fixture . detectChanges ( ) ; expect ( drake . models [ 0 ] ) . toEqual ( newModel ) ; } ) ; it ( 'should update the model value on an existing drake, with no models' , ( ) => { let drake = simpleDrake ( ) ; mockDrake ( drake ) ; let myModel = [ "something" ] ; let newModel = [ "something new" ] ; component . model = myModel ; fixture . detectChanges ( ) ; component . model = newModel ; fixture . detectChanges ( ) ; expect ( drake . models [ 0 ] ) . toEqual ( newModel ) ; } ) ; it ( 'should add a container and a model on init, take 2' , ( ) => { let theirModel = [ "someone else's model" ] ; let myModel = [ "something" ] ; let drake = simpleDrake ( [ document . createElement ( 'div' ) ] , [ theirModel ] ) ; mockDrake ( drake ) ; component . model = myModel ; fixture . detectChanges ( ) ; expect ( drake . containers . length ) . toBe ( 2 ) ; expect ( drake . containers ) . toContain ( component . host . nativeElement ) ; expect ( drake . models ) . toContain ( myModel ) ; } ) ; it ( 'should do nothing if there is no bag name' , ( ) => { let drake = simpleDrake ( ) ; let find = mockDrake ( drake ) ; component . group = null ; component . model = [ ] ; fixture . detectChanges ( ) ; expect ( ) . toVerify ( { called : find ( ) , times : 0 , ignoreExtraArgs : true } ) ; } ) ; it ( 'should cleanly move to another drake when bag name changes' , ( ) => { let CAT = "CAT" , DOG = "DOG" ; let catDrake = simpleDrake ( ) ; let dogDrake = simpleDrake ( ) ; let find = mockMultipleDrakes ( [ catDrake , CAT ] , [ dogDrake , DOG ] ) ; component . group = CAT ; component . model = [ { animal : 'generic four-legged creature' } ] ; fixture . detectChanges ( ) ; component . group = DOG ; fixture . detectChanges ( ) ; expectFindsInSequence ( find , [ CAT , CAT , DOG ] ) ; expect ( catDrake . models . length ) . toBe ( 0 ) ; expect ( catDrake . containers . length ) . toBe ( 0 ) ; expect ( dogDrake . models . length ) . toBe ( 1 ) ; expect ( dogDrake . models ) . toContain ( component . model ) ; expect ( dogDrake . containers . length ) . toBe ( 1 ) ; expect ( dogDrake . containers ) . toContain ( component . host . nativeElement ) ; } ) ; it ( 'should clean up when un-setting bag name' , ( ) => { let drake = simpleDrake ( ) ; let find = mockDrake ( drake ) ; component . group = GROUP ; component . model = [ ] ; fixture . detectChanges ( ) ; component . group = null ; fixture . detectChanges ( ) ; expectFindsInSequence ( find , [ GROUP , GROUP ] ) ; expect ( drake . models . length ) . toBe ( 0 ) ; expect ( drake . containers . length ) . toBe ( 0 ) ; } ) ; const testUnsettingModel = ( drake ) => { let find = mockDrake ( drake ) ; const initialContainers = drake . containers . length ; const initialModels = drake . models . length ; let firstModel = [ { first : 'model' } ] ; let nextModel = [ { next : 'model' } ] ; component . group = GROUP ; component . model = firstModel ; fixture . detectChanges ( ) ; component . model = null ; fixture . detectChanges ( ) ; expectFindsInSequence ( find , [ GROUP ] ) ; expect ( drake . models ) . not . toContain ( firstModel , 'old model not removed' ) ; expect ( drake . containers ) . toContain ( component . host . nativeElement , 'newly added container should still be there' ) ; component . model = nextModel ; fixture . detectChanges ( ) ; expect ( drake . models ) . not . toContain ( firstModel , 'old model not removed' ) ; expect ( drake . models ) . toContain ( nextModel , 'new model not inserted' ) ; } ; it ( 'should clean up when un-setting the model, with no other members' , ( ) => { const drake = simpleDrake ( ) ; testUnsettingModel ( drake ) ; } ) ; it ( 'should clean up when un-setting the model, with other active models in the drake' , ( ) => { const existingContainer = document . createElement ( 'div' ) ; const existingModel = [ { existing : 'model' } ] ; const drake = simpleDrake ( [ existingContainer ] , [ existingModel ] ) ; testUnsettingModel ( drake ) ; expect ( drake . containers ) . toContain ( existingContainer ) ; expect ( drake . models ) . toContain ( existingModel ) ; } ) ; const mockServiceEvent = ( eventName ) => { let fn = td . function ( ) ; let evts = new Subject ( ) ; td . when ( fn ( GROUP ) ) . thenReturn ( evts ) ; td . replace ( service , 'dropModel' , fn ) ; return evts ; } ; it ( 'should fire dragulaModelChange on dropModel' , ( ) => { let dropModel = mockServiceEvent ( EventTypes . DropModel ) ; let modelChange = td . replace ( component , 'modelChange' ) ; let item = { a : 'dragged' } ; let myModel = [ { a : 'static' } ] ; let theirModel = [ item ] ; component . group = GROUP ; component . model = myModel ; fixture . detectChanges ( ) ; let source = document . createElement ( 'ul' ) ; let target = component . host . nativeElement ; let myNewModel = myModel . slice ( 0 ) ; myNewModel . push ( item ) ; let theirNewModel = [ ] ; dropModel . next ( { name : GROUP , source , target , sourceModel : theirNewModel , targetModel : myNewModel } ) ; expect ( ) . toVerify ( modelChange ( myNewModel ) ) ; } ) ; it ( 'should fire dragulaModelChange on removeModel' , ( ) => { let removeModel = mockServiceEvent ( EventTypes . RemoveModel ) ; let modelChange = td . replace ( component , 'modelChange' ) ; let item = { a : 'to be removed' } ; let myModel = [ item ] ; component . group = GROUP ; component . model = myModel ; fixture . detectChanges ( ) ; let source = component . host . nativeElement ; let myNewModel = [ ] ; removeModel . next ( { name : GROUP , item , source , sourceModel : myNewModel } ) ; expect ( ) . toVerify ( modelChange ( myNewModel ) ) ; } ) ; const testModelChange = < T extends TestHostComponent | TwoWay | Asynchronous > ( componentClass , saveToComponent = true ) => { let fixture = TestBed . createComponent ( componentClass ) ; let component = fixture . componentInstance ; let removeModel = mockServiceEvent ( EventTypes . RemoveModel ) ; let same = { a : 'same' } ; let same2 = { same2 : '2' } ; let item = { a : 'to be removed' } ; let myModel = [ same , item , same2 ] ; component . group = GROUP ; component . model = myModel ; fixture . detectChanges ( ) ; let bag = service . find ( GROUP ) ; expect ( bag ) . toBeTruthy ( 'bag not truthy' ) ; expect ( bag . drake . models ) . toBeTruthy ( 'bag.drake.models not truthy' ) ; expect ( bag . drake . models && bag . drake . models [ 0 ] ) . toBe ( myModel ) ; let source = component . host . nativeElement ; let myNewModel = [ same , same2 ] ; bag . drake . models [ 0 ] = myNewModel ; removeModel . next ( { name : GROUP , item , source , sourceModel : myNewModel } ) ; if ( saveToComponent ) { expect ( component . model ) . toBe ( myNewModel , "[(dragulaModel)] didn't save model to component" ) ; } let setup = td . replace ( component . directive , 'setup' ) ; let teardown = td . replace ( component . directive , 'teardown' ) ; expect ( component . directive . dragulaModel ) . not . toBe ( myNewModel , "directive didn't save the new model" ) ; fixture . detectChanges ( ) ; if ( saveToComponent ) { expect ( component . model ) . toBe ( myNewModel , "component didn't save model" ) ; } expect ( component . directive . dragulaModel ) . toBe ( myNewModel ) ; expect ( ) . toVerify ( { called : teardown ( ) , times : 0 , ignoreExtraArgs : true } ) ; expect ( ) . toVerify ( { called : setup ( ) , times : 0 , ignoreExtraArgs : true } ) ; expect ( bag . drake . models [ 0 ] ) . toBe ( myNewModel ) ; } ; describe ( '(dragulaModelChange)' , ( ) => { it ( 'should work with (event) binding' , ( ) => { testModelChange ( TestHostComponent ) ; } ) ; it ( 'should work with two-way binding' , ( ) => { testModelChange ( TwoWay ) ; } ) ; it ( 'should work with an async pipe' , ( ) => { testModelChange ( Asynchronous , false ) ; } ) ; } ) ; } ) ;