Skip to content

Commit c6b17c6

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 2a086ce commit c6b17c6

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} 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} 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
}
@@ -2883,55 +2889,69 @@ describe('CdkDrag', () => {
28832889
});
28842890
}));
28852891

2886-
it('should set a class when a container can receive an item', fakeAsync(() => {
2887-
const fixture = createComponent(ConnectedDropZones);
2888-
fixture.detectChanges();
2892+
it('should set a class when a container can receive an item', fakeAsync(() => {
2893+
const fixture = createComponent(ConnectedDropZones);
2894+
fixture.detectChanges();
28892895

2890-
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2891-
const item = fixture.componentInstance.groupedDragItems[0][1];
2896+
const dropZones =
2897+
fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2898+
const item = fixture.componentInstance.groupedDragItems[0][1];
28922899

2893-
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2894-
.toBe(true, 'Expected neither of the containers to have the class.');
2900+
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2901+
.toBe(true, 'Expected neither of the containers to have the class.');
28952902

2896-
startDraggingViaMouse(fixture, item.element.nativeElement);
2897-
fixture.detectChanges();
2903+
startDraggingViaMouse(fixture, item.element.nativeElement);
2904+
fixture.detectChanges();
28982905

2899-
expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving',
2900-
'Expected source container not to have the receiving class.');
2906+
expect(dropZones[0].classList)
2907+
.not.toContain(
2908+
'cdk-drop-list-receiving',
2909+
'Expected source container not to have the receiving class.');
29012910

2902-
expect(dropZones[1].classList).toContain('cdk-drop-list-receiving',
2903-
'Expected target container to have the receiving class.');
2904-
}));
2911+
expect(dropZones[1].classList)
2912+
.toContain(
2913+
'cdk-drop-list-receiving',
2914+
'Expected target container to have the receiving class.');
2915+
}));
29052916

2906-
it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => {
2907-
const fixture = createComponent(ConnectedDropZones);
2908-
fixture.detectChanges();
2917+
it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => {
2918+
const fixture = createComponent(ConnectedDropZones);
2919+
fixture.detectChanges();
29092920

2910-
const groups = fixture.componentInstance.groupedDragItems;
2911-
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2912-
const item = groups[0][1];
2913-
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
2921+
const groups = fixture.componentInstance.groupedDragItems;
2922+
const dropZones =
2923+
fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2924+
const item = groups[0][1];
2925+
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
29142926

2915-
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2916-
.toBe(true, 'Expected neither of the containers to have the class.');
2927+
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2928+
.toBe(true, 'Expected neither of the containers to have the class.');
29172929

2918-
startDraggingViaMouse(fixture, item.element.nativeElement);
2930+
startDraggingViaMouse(fixture, item.element.nativeElement);
29192931

2920-
expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving',
2921-
'Expected source container not to have the receiving class.');
2932+
expect(dropZones[0].classList)
2933+
.not.toContain(
2934+
'cdk-drop-list-receiving',
2935+
'Expected source container not to have the receiving class.');
29222936

2923-
expect(dropZones[1].classList).toContain('cdk-drop-list-receiving',
2924-
'Expected target container to have the receiving class.');
2937+
expect(dropZones[1].classList)
2938+
.toContain(
2939+
'cdk-drop-list-receiving',
2940+
'Expected target container to have the receiving class.');
29252941

2926-
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2927-
fixture.detectChanges();
2942+
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2943+
fixture.detectChanges();
29282944

2929-
expect(dropZones[0].classList).toContain('cdk-drop-list-receiving',
2930-
'Expected old container not to have the receiving class after exiting.');
2945+
expect(dropZones[0].classList)
2946+
.toContain(
2947+
'cdk-drop-list-receiving',
2948+
'Expected old container not to have the receiving class after exiting.');
29312949

2932-
expect(dropZones[1].classList).not.toContain('cdk-drop-list-receiving',
2933-
'Expected new container not to have the receiving class after entering.');
2934-
}));
2950+
expect(dropZones[1].classList)
2951+
.not.toContain(
2952+
'cdk-drop-list-receiving',
2953+
'Expected new container not to have the receiving class after entering.');
2954+
}));
29352955

29362956
it('should be able to move the item over an intermediate container before ' +
29372957
'dropping it into the final one', fakeAsync(() => {
@@ -3038,8 +3058,50 @@ describe('CdkDrag', () => {
30383058
expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled();
30393059
}));
30403060

3061+
it('should toggle a class when dragging an item inside a wrapper component component ' +
3062+
'with OnPush change detection',
3063+
fakeAsync(() => {
3064+
const fixture =
3065+
createComponent(ConnectedWrappedDropZones, [], 0, [WrappedDropContainerComponent]);
3066+
fixture.detectChanges();
3067+
3068+
const [startZone, targetZone] = fixture.nativeElement.querySelectorAll('.cdk-drop-list');
3069+
const item = startZone.querySelector('.cdk-drag');
3070+
const targetRect = targetZone.getBoundingClientRect();
3071+
3072+
expect(startZone.classList)
3073+
.not.toContain(
3074+
'cdk-drop-list-dragging',
3075+
'Expected start not to have dragging class on init.');
3076+
expect(targetZone.classList)
3077+
.not.toContain(
3078+
'cdk-drop-list-dragging',
3079+
'Expected target not to have dragging class on init.');
3080+
3081+
startDraggingViaMouse(fixture, item);
3082+
3083+
expect(startZone.classList)
3084+
.toContain(
3085+
'cdk-drop-list-dragging',
3086+
'Expected start to have dragging class after dragging has started.');
3087+
expect(targetZone.classList)
3088+
.not.toContain(
3089+
'cdk-drop-list-dragging',
3090+
'Expected target not to have dragging class after dragging has started.');
3091+
3092+
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
3093+
fixture.detectChanges();
3094+
3095+
expect(startZone.classList)
3096+
.not.toContain(
3097+
'cdk-drop-list-dragging',
3098+
'Expected start not to have dragging class once item has been moved over.');
3099+
expect(targetZone.classList)
3100+
.toContain(
3101+
'cdk-drop-list-dragging',
3102+
'Expected target to have dragging class once item has been moved over.');
3103+
}));
30413104
});
3042-
30433105
});
30443106

30453107
@Component({
@@ -3519,6 +3581,34 @@ class DraggableInDropZoneWithoutEvents {
35193581
];
35203582
}
35213583

3584+
@Component({
3585+
encapsulation: ViewEncapsulation.None,
3586+
styles: [`
3587+
.cdk-drop-list {
3588+
display: block;
3589+
width: 100px;
3590+
min-height: ${ITEM_HEIGHT}px;
3591+
background: hotpink;
3592+
}
3593+
3594+
.cdk-drag {
3595+
display: block;
3596+
height: ${ITEM_HEIGHT}px;
3597+
background: red;
3598+
}
3599+
`],
3600+
template: `
3601+
<div cdkDropListGroup>
3602+
<wrapped-drop-container [items]="todo"></wrapped-drop-container>
3603+
<wrapped-drop-container [items]="done"></wrapped-drop-container>
3604+
</div>
3605+
`
3606+
})
3607+
class ConnectedWrappedDropZones {
3608+
todo = ['Zero', 'One', 'Two', 'Three'];
3609+
done = ['Four', 'Five', 'Six'];
3610+
}
3611+
35223612
/**
35233613
* Component that passes through whatever content is projected into it.
35243614
* Used to test having drag elements being projected into a component.
@@ -3529,6 +3619,21 @@ class DraggableInDropZoneWithoutEvents {
35293619
})
35303620
class PassthroughComponent {}
35313621

3622+
3623+
/** Component that wraps a drop container and uses OnPush change detection. */
3624+
@Component({
3625+
selector: 'wrapped-drop-container',
3626+
template: `
3627+
<div cdkDropList [cdkDropListData]="items">
3628+
<div *ngFor="let item of items" cdkDrag>{{item}}</div>
3629+
</div>
3630+
`,
3631+
changeDetection: ChangeDetectionStrategy.OnPush
3632+
})
3633+
class WrappedDropContainerComponent {
3634+
@Input() items: string[];
3635+
}
3636+
35323637
/**
35333638
* Drags an element to a position on the page using the mouse.
35343639
* @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)