Skip to content

Commit 0854ddc

Browse files
committed
fix(drag-drop): dragged styling not being removed when exiting component with OnPush
Fixes the dragged styling not being removed from a drop container, which is wrapped inside a component with `OnPush` change detection, when an item is dragged into a connected container. Fixes #15233.
1 parent bb69483 commit 0854ddc

File tree

2 files changed

+176
-70
lines changed

2 files changed

+176
-70
lines changed

src/cdk/drag-drop/directives/drag.spec.ts

Lines changed: 175 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,65 @@
1+
import {Directionality} from '@angular/cdk/bidi';
2+
import {
3+
createMouseEvent,
4+
createTouchEvent,
5+
dispatchEvent,
6+
dispatchMouseEvent,
7+
dispatchTouchEvent,
8+
} from '@angular/cdk/testing';
19
import {
210
AfterViewInit,
11+
ChangeDetectionStrategy,
312
Component,
413
ElementRef,
14+
Input,
515
NgZone,
616
Provider,
717
QueryList,
818
Type,
919
ViewChild,
1020
ViewChildren,
1121
ViewEncapsulation,
12-
ChangeDetectionStrategy,
1322
} from '@angular/core';
14-
import {TestBed, ComponentFixture, fakeAsync, flush, tick} from '@angular/core/testing';
15-
import {DragDropModule} from '../drag-drop-module';
16-
import {
17-
createMouseEvent,
18-
dispatchEvent,
19-
dispatchMouseEvent,
20-
dispatchTouchEvent,
21-
createTouchEvent,
22-
} from '@angular/cdk/testing';
23-
import {Directionality} from '@angular/cdk/bidi';
23+
import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing';
2424
import {of as observableOf} from 'rxjs';
25-
import {CdkDrag, CDK_DRAG_CONFIG} from './drag';
25+
26+
import {DragDropModule} from '../drag-drop-module';
2627
import {CdkDragDrop} from '../drag-events';
28+
import {DragRefConfig} from '../drag-ref';
29+
import {extendStyles} from '../drag-styling';
2730
import {moveItemInArray} from '../drag-utils';
28-
import {CdkDropList} from './drop-list';
31+
32+
import {CDK_DRAG_CONFIG, CdkDrag} from './drag';
2933
import {CdkDragHandle} from './drag-handle';
34+
import {CdkDropList} from './drop-list';
3035
import {CdkDropListGroup} from './drop-list-group';
31-
import {extendStyles} from '../drag-styling';
32-
import {DragRefConfig} from '../drag-ref';
3336

3437
const ITEM_HEIGHT = 25;
3538
const ITEM_WIDTH = 75;
3639

3740
describe('CdkDrag', () => {
38-
function createComponent<T>(componentType: Type<T>, providers: Provider[] = [], dragDistance = 0):
39-
ComponentFixture<T> {
40-
TestBed.configureTestingModule({
41-
imports: [DragDropModule],
42-
declarations: [componentType, PassthroughComponent],
43-
providers: [
44-
{
45-
provide: CDK_DRAG_CONFIG,
46-
useValue: {
47-
// We default the `dragDistance` to zero, because the majority of the tests
48-
// don't care about it and drags are a lot easier to simulate when we don't
49-
// have to deal with thresholds.
50-
dragStartThreshold: dragDistance,
51-
pointerDirectionChangeThreshold: 5
52-
} as DragRefConfig
53-
},
54-
...providers
55-
],
56-
}).compileComponents();
41+
function createComponent<T>(
42+
componentType: Type<T>, providers: Provider[] = [], dragDistance = 0,
43+
extraDeclarations: Type<any>[] = []): ComponentFixture<T> {
44+
TestBed
45+
.configureTestingModule({
46+
imports: [DragDropModule],
47+
declarations: [componentType, PassthroughComponent, ...extraDeclarations],
48+
providers: [
49+
{
50+
provide: CDK_DRAG_CONFIG,
51+
useValue: {
52+
// We default the `dragDistance` to zero, because the majority of the tests
53+
// don't care about it and drags are a lot easier to simulate when we don't
54+
// have to deal with thresholds.
55+
dragStartThreshold: dragDistance,
56+
pointerDirectionChangeThreshold: 5
57+
} as DragRefConfig
58+
},
59+
...providers
60+
],
61+
})
62+
.compileComponents();
5763

5864
return TestBed.createComponent<T>(componentType);
5965
}
@@ -2715,55 +2721,69 @@ describe('CdkDrag', () => {
27152721
});
27162722
}));
27172723

2718-
it('should set a class when a container can receive an item', fakeAsync(() => {
2719-
const fixture = createComponent(ConnectedDropZones);
2720-
fixture.detectChanges();
2724+
it('should set a class when a container can receive an item', fakeAsync(() => {
2725+
const fixture = createComponent(ConnectedDropZones);
2726+
fixture.detectChanges();
27212727

2722-
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2723-
const item = fixture.componentInstance.groupedDragItems[0][1];
2728+
const dropZones =
2729+
fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2730+
const item = fixture.componentInstance.groupedDragItems[0][1];
27242731

2725-
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2726-
.toBe(true, 'Expected neither of the containers to have the class.');
2732+
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2733+
.toBe(true, 'Expected neither of the containers to have the class.');
27272734

2728-
startDraggingViaMouse(fixture, item.element.nativeElement);
2729-
fixture.detectChanges();
2735+
startDraggingViaMouse(fixture, item.element.nativeElement);
2736+
fixture.detectChanges();
27302737

2731-
expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving',
2732-
'Expected source container not to have the receiving class.');
2738+
expect(dropZones[0].classList)
2739+
.not.toContain(
2740+
'cdk-drop-list-receiving',
2741+
'Expected source container not to have the receiving class.');
27332742

2734-
expect(dropZones[1].classList).toContain('cdk-drop-list-receiving',
2735-
'Expected target container to have the receiving class.');
2736-
}));
2743+
expect(dropZones[1].classList)
2744+
.toContain(
2745+
'cdk-drop-list-receiving',
2746+
'Expected target container to have the receiving class.');
2747+
}));
27372748

2738-
it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => {
2739-
const fixture = createComponent(ConnectedDropZones);
2740-
fixture.detectChanges();
2749+
it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => {
2750+
const fixture = createComponent(ConnectedDropZones);
2751+
fixture.detectChanges();
27412752

2742-
const groups = fixture.componentInstance.groupedDragItems;
2743-
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2744-
const item = groups[0][1];
2745-
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
2753+
const groups = fixture.componentInstance.groupedDragItems;
2754+
const dropZones =
2755+
fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2756+
const item = groups[0][1];
2757+
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
27462758

2747-
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2748-
.toBe(true, 'Expected neither of the containers to have the class.');
2759+
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2760+
.toBe(true, 'Expected neither of the containers to have the class.');
27492761

2750-
startDraggingViaMouse(fixture, item.element.nativeElement);
2762+
startDraggingViaMouse(fixture, item.element.nativeElement);
27512763

2752-
expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving',
2753-
'Expected source container not to have the receiving class.');
2764+
expect(dropZones[0].classList)
2765+
.not.toContain(
2766+
'cdk-drop-list-receiving',
2767+
'Expected source container not to have the receiving class.');
27542768

2755-
expect(dropZones[1].classList).toContain('cdk-drop-list-receiving',
2756-
'Expected target container to have the receiving class.');
2769+
expect(dropZones[1].classList)
2770+
.toContain(
2771+
'cdk-drop-list-receiving',
2772+
'Expected target container to have the receiving class.');
27572773

2758-
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2759-
fixture.detectChanges();
2774+
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2775+
fixture.detectChanges();
27602776

2761-
expect(dropZones[0].classList).toContain('cdk-drop-list-receiving',
2762-
'Expected old container not to have the receiving class after exiting.');
2777+
expect(dropZones[0].classList)
2778+
.toContain(
2779+
'cdk-drop-list-receiving',
2780+
'Expected old container not to have the receiving class after exiting.');
27632781

2764-
expect(dropZones[1].classList).not.toContain('cdk-drop-list-receiving',
2765-
'Expected new container not to have the receiving class after entering.');
2766-
}));
2782+
expect(dropZones[1].classList)
2783+
.not.toContain(
2784+
'cdk-drop-list-receiving',
2785+
'Expected new container not to have the receiving class after entering.');
2786+
}));
27672787

27682788
it('should be able to move the item over an intermediate container before ' +
27692789
'dropping it into the final one', fakeAsync(() => {
@@ -2822,8 +2842,50 @@ describe('CdkDrag', () => {
28222842

28232843
}));
28242844

2845+
it('should toggle a class when dragging an item inside a wrapper component component ' +
2846+
'with OnPush change detection',
2847+
fakeAsync(() => {
2848+
const fixture =
2849+
createComponent(ConnectedWrappedDropZones, [], 0, [WrappedDropContainerComponent]);
2850+
fixture.detectChanges();
2851+
2852+
const [startZone, targetZone] = fixture.nativeElement.querySelectorAll('.cdk-drop-list');
2853+
const item = startZone.querySelector('.cdk-drag');
2854+
const targetRect = targetZone.getBoundingClientRect();
2855+
2856+
expect(startZone.classList)
2857+
.not.toContain(
2858+
'cdk-drop-list-dragging',
2859+
'Expected start not to have dragging class on init.');
2860+
expect(targetZone.classList)
2861+
.not.toContain(
2862+
'cdk-drop-list-dragging',
2863+
'Expected target not to have dragging class on init.');
2864+
2865+
startDraggingViaMouse(fixture, item);
2866+
2867+
expect(startZone.classList)
2868+
.toContain(
2869+
'cdk-drop-list-dragging',
2870+
'Expected start to have dragging class after dragging has started.');
2871+
expect(targetZone.classList)
2872+
.not.toContain(
2873+
'cdk-drop-list-dragging',
2874+
'Expected target not to have dragging class after dragging has started.');
2875+
2876+
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2877+
fixture.detectChanges();
2878+
2879+
expect(startZone.classList)
2880+
.not.toContain(
2881+
'cdk-drop-list-dragging',
2882+
'Expected start not to have dragging class once item has been moved over.');
2883+
expect(targetZone.classList)
2884+
.toContain(
2885+
'cdk-drop-list-dragging',
2886+
'Expected target to have dragging class once item has been moved over.');
2887+
}));
28252888
});
2826-
28272889
});
28282890

28292891
@Component({
@@ -3301,6 +3363,34 @@ class DraggableInDropZoneWithoutEvents {
33013363
];
33023364
}
33033365

3366+
@Component({
3367+
encapsulation: ViewEncapsulation.None,
3368+
styles: [`
3369+
.cdk-drop-list {
3370+
display: block;
3371+
width: 100px;
3372+
min-height: ${ITEM_HEIGHT}px;
3373+
background: hotpink;
3374+
}
3375+
3376+
.cdk-drag {
3377+
display: block;
3378+
height: ${ITEM_HEIGHT}px;
3379+
background: red;
3380+
}
3381+
`],
3382+
template: `
3383+
<div cdkDropListGroup>
3384+
<wrapped-drop-container [items]="todo"></wrapped-drop-container>
3385+
<wrapped-drop-container [items]="done"></wrapped-drop-container>
3386+
</div>
3387+
`
3388+
})
3389+
class ConnectedWrappedDropZones {
3390+
todo = ['Zero', 'One', 'Two', 'Three'];
3391+
done = ['Four', 'Five', 'Six'];
3392+
}
3393+
33043394
/**
33053395
* Component that passes through whatever content is projected into it.
33063396
* Used to test having drag elements being projected into a component.
@@ -3311,6 +3401,21 @@ class DraggableInDropZoneWithoutEvents {
33113401
})
33123402
class PassthroughComponent {}
33133403

3404+
3405+
/** Component that wraps a drop container and uses OnPush change detection. */
3406+
@Component({
3407+
selector: 'wrapped-drop-container',
3408+
template: `
3409+
<div cdkDropList [cdkDropListData]="items">
3410+
<div *ngFor="let item of items" cdkDrag>{{item}}</div>
3411+
</div>
3412+
`,
3413+
changeDetection: ChangeDetectionStrategy.OnPush
3414+
})
3415+
class WrappedDropContainerComponent {
3416+
@Input() items: string[];
3417+
}
3418+
33143419
/**
33153420
* Drags an element to a position on the page using the mouse.
33163421
* @param fixture Fixture on which to run change detection.

src/cdk/drag-drop/directives/drop-list.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentI
332332
container: this,
333333
item: event.item.data
334334
});
335+
this._changeDetectorRef.markForCheck();
335336
});
336337

337338
ref.sorted.subscribe(event => {

0 commit comments

Comments
 (0)