Skip to content

Commit 9bbbb80

Browse files
crisbetoandrewseguin
authored andcommitted
fix(drag-drop): dragged styling not being removed when exiting component with OnPush (#15266)
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 d646266 commit 9bbbb80

File tree

2 files changed

+175
-69
lines changed

2 files changed

+175
-69
lines changed

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

Lines changed: 174 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,66 @@
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';
1423
import {TestBed, ComponentFixture, fakeAsync, flush, tick} from '@angular/core/testing';
1524
import {DOCUMENT} from '@angular/common';
16-
import {DragDropModule} from '../drag-drop-module';
17-
import {
18-
createMouseEvent,
19-
dispatchEvent,
20-
dispatchMouseEvent,
21-
dispatchTouchEvent,
22-
createTouchEvent,
23-
} from '@angular/cdk/testing';
24-
import {Directionality} from '@angular/cdk/bidi';
2525
import {of as observableOf} from 'rxjs';
26-
import {CdkDrag, CDK_DRAG_CONFIG} from './drag';
26+
27+
import {DragDropModule} from '../drag-drop-module';
2728
import {CdkDragDrop} from '../drag-events';
29+
import {DragRefConfig, Point} from '../drag-ref';
30+
import {extendStyles} from '../drag-styling';
2831
import {moveItemInArray} from '../drag-utils';
29-
import {CdkDropList} from './drop-list';
32+
33+
import {CDK_DRAG_CONFIG, CdkDrag} from './drag';
3034
import {CdkDragHandle} from './drag-handle';
35+
import {CdkDropList} from './drop-list';
3136
import {CdkDropListGroup} from './drop-list-group';
32-
import {extendStyles} from '../drag-styling';
33-
import {DragRefConfig, Point} from '../drag-ref';
3437

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

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

5965
return TestBed.createComponent<T>(componentType);
6066
}
@@ -2928,55 +2934,69 @@ describe('CdkDrag', () => {
29282934
});
29292935
}));
29302936

2931-
it('should set a class when a container can receive an item', fakeAsync(() => {
2932-
const fixture = createComponent(ConnectedDropZones);
2933-
fixture.detectChanges();
2937+
it('should set a class when a container can receive an item', fakeAsync(() => {
2938+
const fixture = createComponent(ConnectedDropZones);
2939+
fixture.detectChanges();
29342940

2935-
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2936-
const item = fixture.componentInstance.groupedDragItems[0][1];
2941+
const dropZones =
2942+
fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2943+
const item = fixture.componentInstance.groupedDragItems[0][1];
29372944

2938-
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2939-
.toBe(true, 'Expected neither of the containers to have the class.');
2945+
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2946+
.toBe(true, 'Expected neither of the containers to have the class.');
29402947

2941-
startDraggingViaMouse(fixture, item.element.nativeElement);
2942-
fixture.detectChanges();
2948+
startDraggingViaMouse(fixture, item.element.nativeElement);
2949+
fixture.detectChanges();
29432950

2944-
expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving',
2945-
'Expected source container not to have the receiving class.');
2951+
expect(dropZones[0].classList)
2952+
.not.toContain(
2953+
'cdk-drop-list-receiving',
2954+
'Expected source container not to have the receiving class.');
29462955

2947-
expect(dropZones[1].classList).toContain('cdk-drop-list-receiving',
2948-
'Expected target container to have the receiving class.');
2949-
}));
2956+
expect(dropZones[1].classList)
2957+
.toContain(
2958+
'cdk-drop-list-receiving',
2959+
'Expected target container to have the receiving class.');
2960+
}));
29502961

2951-
it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => {
2952-
const fixture = createComponent(ConnectedDropZones);
2953-
fixture.detectChanges();
2962+
it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => {
2963+
const fixture = createComponent(ConnectedDropZones);
2964+
fixture.detectChanges();
29542965

2955-
const groups = fixture.componentInstance.groupedDragItems;
2956-
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2957-
const item = groups[0][1];
2958-
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
2966+
const groups = fixture.componentInstance.groupedDragItems;
2967+
const dropZones =
2968+
fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2969+
const item = groups[0][1];
2970+
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
29592971

2960-
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2961-
.toBe(true, 'Expected neither of the containers to have the class.');
2972+
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2973+
.toBe(true, 'Expected neither of the containers to have the class.');
29622974

2963-
startDraggingViaMouse(fixture, item.element.nativeElement);
2975+
startDraggingViaMouse(fixture, item.element.nativeElement);
29642976

2965-
expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving',
2966-
'Expected source container not to have the receiving class.');
2977+
expect(dropZones[0].classList)
2978+
.not.toContain(
2979+
'cdk-drop-list-receiving',
2980+
'Expected source container not to have the receiving class.');
29672981

2968-
expect(dropZones[1].classList).toContain('cdk-drop-list-receiving',
2969-
'Expected target container to have the receiving class.');
2982+
expect(dropZones[1].classList)
2983+
.toContain(
2984+
'cdk-drop-list-receiving',
2985+
'Expected target container to have the receiving class.');
29702986

2971-
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2972-
fixture.detectChanges();
2987+
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2988+
fixture.detectChanges();
29732989

2974-
expect(dropZones[0].classList).toContain('cdk-drop-list-receiving',
2975-
'Expected old container not to have the receiving class after exiting.');
2990+
expect(dropZones[0].classList)
2991+
.toContain(
2992+
'cdk-drop-list-receiving',
2993+
'Expected old container not to have the receiving class after exiting.');
29762994

2977-
expect(dropZones[1].classList).not.toContain('cdk-drop-list-receiving',
2978-
'Expected new container not to have the receiving class after entering.');
2979-
}));
2995+
expect(dropZones[1].classList)
2996+
.not.toContain(
2997+
'cdk-drop-list-receiving',
2998+
'Expected new container not to have the receiving class after entering.');
2999+
}));
29803000

29813001
it('should be able to move the item over an intermediate container before ' +
29823002
'dropping it into the final one', fakeAsync(() => {
@@ -3083,8 +3103,50 @@ describe('CdkDrag', () => {
30833103
expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled();
30843104
}));
30853105

3106+
it('should toggle a class when dragging an item inside a wrapper component component ' +
3107+
'with OnPush change detection',
3108+
fakeAsync(() => {
3109+
const fixture =
3110+
createComponent(ConnectedWrappedDropZones, [], 0, [WrappedDropContainerComponent]);
3111+
fixture.detectChanges();
3112+
3113+
const [startZone, targetZone] = fixture.nativeElement.querySelectorAll('.cdk-drop-list');
3114+
const item = startZone.querySelector('.cdk-drag');
3115+
const targetRect = targetZone.getBoundingClientRect();
3116+
3117+
expect(startZone.classList)
3118+
.not.toContain(
3119+
'cdk-drop-list-dragging',
3120+
'Expected start not to have dragging class on init.');
3121+
expect(targetZone.classList)
3122+
.not.toContain(
3123+
'cdk-drop-list-dragging',
3124+
'Expected target not to have dragging class on init.');
3125+
3126+
startDraggingViaMouse(fixture, item);
3127+
3128+
expect(startZone.classList)
3129+
.toContain(
3130+
'cdk-drop-list-dragging',
3131+
'Expected start to have dragging class after dragging has started.');
3132+
expect(targetZone.classList)
3133+
.not.toContain(
3134+
'cdk-drop-list-dragging',
3135+
'Expected target not to have dragging class after dragging has started.');
3136+
3137+
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
3138+
fixture.detectChanges();
3139+
3140+
expect(startZone.classList)
3141+
.not.toContain(
3142+
'cdk-drop-list-dragging',
3143+
'Expected start not to have dragging class once item has been moved over.');
3144+
expect(targetZone.classList)
3145+
.toContain(
3146+
'cdk-drop-list-dragging',
3147+
'Expected target to have dragging class once item has been moved over.');
3148+
}));
30863149
});
3087-
30883150
});
30893151

30903152
@Component({
@@ -3568,6 +3630,34 @@ class DraggableInDropZoneWithoutEvents {
35683630
];
35693631
}
35703632

3633+
@Component({
3634+
encapsulation: ViewEncapsulation.None,
3635+
styles: [`
3636+
.cdk-drop-list {
3637+
display: block;
3638+
width: 100px;
3639+
min-height: ${ITEM_HEIGHT}px;
3640+
background: hotpink;
3641+
}
3642+
3643+
.cdk-drag {
3644+
display: block;
3645+
height: ${ITEM_HEIGHT}px;
3646+
background: red;
3647+
}
3648+
`],
3649+
template: `
3650+
<div cdkDropListGroup>
3651+
<wrapped-drop-container [items]="todo"></wrapped-drop-container>
3652+
<wrapped-drop-container [items]="done"></wrapped-drop-container>
3653+
</div>
3654+
`
3655+
})
3656+
class ConnectedWrappedDropZones {
3657+
todo = ['Zero', 'One', 'Two', 'Three'];
3658+
done = ['Four', 'Five', 'Six'];
3659+
}
3660+
35713661
/**
35723662
* Component that passes through whatever content is projected into it.
35733663
* Used to test having drag elements being projected into a component.
@@ -3578,6 +3668,21 @@ class DraggableInDropZoneWithoutEvents {
35783668
})
35793669
class PassthroughComponent {}
35803670

3671+
3672+
/** Component that wraps a drop container and uses OnPush change detection. */
3673+
@Component({
3674+
selector: 'wrapped-drop-container',
3675+
template: `
3676+
<div cdkDropList [cdkDropListData]="items">
3677+
<div *ngFor="let item of items" cdkDrag>{{item}}</div>
3678+
</div>
3679+
`,
3680+
changeDetection: ChangeDetectionStrategy.OnPush
3681+
})
3682+
class WrappedDropContainerComponent {
3683+
@Input() items: string[];
3684+
}
3685+
35813686
/**
35823687
* Drags an element to a position on the page using the mouse.
35833688
* @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
@@ -342,6 +342,7 @@ export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentI
342342
container: this,
343343
item: event.item.data
344344
});
345+
this._changeDetectorRef.markForCheck();
345346
});
346347

347348
ref.sorted.subscribe(event => {

0 commit comments

Comments
 (0)