Skip to content

Commit c09683f

Browse files
committed
fix(dialog): multiple dialogs not opening with disabled animations
With SHA 36f708c, second dialogs can be only opened if the first dialog finished animating. Due to the recent update to Angular 4.3, the animation trigger callbacks are firing in a wrong order (done callback fires before start), which causes the _isAnimating property to be set to true while the animation already finished. @crisbeto Not too happy how the tests ended up. Fixes #6719
1 parent 70bd5fc commit c09683f

File tree

3 files changed

+88
-79
lines changed

3 files changed

+88
-79
lines changed

src/lib/datepicker/datepicker.spec.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Component, ViewChild} from '@angular/core';
2-
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
2+
import {async, ComponentFixture, TestBed, inject, fakeAsync, flush} from '@angular/core/testing';
33
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
44
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
55
import {By} from '@angular/platform-browser';
@@ -152,7 +152,7 @@ describe('MdDatepicker', () => {
152152
.toBe(true, 'Expected default ESCAPE action to be prevented.');
153153
});
154154

155-
it('close should close dialog', () => {
155+
it('close should close dialog', fakeAsync(() => {
156156
testComponent.touch = true;
157157
fixture.detectChanges();
158158

@@ -163,13 +163,13 @@ describe('MdDatepicker', () => {
163163

164164
testComponent.datepicker.close();
165165
fixture.detectChanges();
166+
flush();
166167

167-
fixture.whenStable().then(() => {
168-
expect(document.querySelector('md-dialog-container')).toBeNull();
169-
});
170-
});
168+
expect(document.querySelector('md-dialog-container')).toBeNull();
169+
flush();
170+
}));
171171

172-
it('setting selected should update input and close calendar', () => {
172+
it('setting selected should update input and close calendar', fakeAsync(() => {
173173
testComponent.touch = true;
174174
fixture.detectChanges();
175175

@@ -182,12 +182,12 @@ describe('MdDatepicker', () => {
182182
let cells = document.querySelectorAll('.mat-calendar-body-cell');
183183
dispatchMouseEvent(cells[1], 'click');
184184
fixture.detectChanges();
185+
flush();
185186

186-
fixture.whenStable().then(() => {
187-
expect(document.querySelector('md-dialog-container')).toBeNull();
188-
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
189-
});
190-
});
187+
expect(document.querySelector('md-dialog-container')).toBeNull();
188+
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
189+
flush();
190+
}));
191191

192192
it('clicking the currently selected date should close the calendar ' +
193193
'without firing selectedChanged', () => {

src/lib/dialog/dialog-container.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,13 @@ export class MdDialogContainer extends BasePortalHost {
182182
this._restoreFocus();
183183
}
184184

185-
this._animationStateChanged.emit(event);
186-
this._isAnimating = false;
185+
// Note: as of Angular 4.3, the animations module seems to fire the `start` callback before
186+
// the end if animations are disabled. Make this call async to ensure that it still fires
187+
// at the appropriate time.
188+
Promise.resolve().then(() => {
189+
this._animationStateChanged.emit(event);
190+
this._isAnimating = false;
191+
});
187192
}
188193

189194
/** Callback, invoked when an animation on the host starts. */

src/lib/dialog/dialog.spec.ts

Lines changed: 69 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ComponentFixture,
77
TestBed,
88
tick,
9+
flush,
910
} from '@angular/core/testing';
1011
import {
1112
NgModule,
@@ -149,21 +150,21 @@ describe('MdDialog', () => {
149150
expect(dialogContainerElement.getAttribute('aria-describedby')).toBe('description-element');
150151
});
151152

152-
it('should close a dialog and get back a result', async(() => {
153+
it('should close a dialog and get back a result', fakeAsync(() => {
153154
let dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
154155
let afterCloseCallback = jasmine.createSpy('afterClose callback');
155156

156157
dialogRef.afterClosed().subscribe(afterCloseCallback);
157158
dialogRef.close('Charmander');
158159
viewContainerFixture.detectChanges();
160+
flush();
159161

160-
viewContainerFixture.whenStable().then(() => {
161-
expect(afterCloseCallback).toHaveBeenCalledWith('Charmander');
162-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
163-
});
162+
expect(afterCloseCallback).toHaveBeenCalledWith('Charmander');
163+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
164+
flush();
164165
}));
165166

166-
it('should close a dialog and get back a result before it is closed', async(() => {
167+
it('should close a dialog and get back a result before it is closed', fakeAsync(() => {
167168
const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef});
168169

169170
// beforeClose should emit before dialog container is destroyed
@@ -175,24 +176,24 @@ describe('MdDialog', () => {
175176
dialogRef.beforeClose().subscribe(beforeCloseHandler);
176177
dialogRef.close('Bulbasaurus');
177178
viewContainerFixture.detectChanges();
179+
flush();
178180

179-
viewContainerFixture.whenStable().then(() => {
180-
expect(beforeCloseHandler).toHaveBeenCalledWith('Bulbasaurus');
181-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
182-
});
181+
expect(beforeCloseHandler).toHaveBeenCalledWith('Bulbasaurus');
182+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
183+
flush();
183184
}));
184185

185-
it('should close a dialog via the escape key', async(() => {
186+
it('should close a dialog via the escape key', fakeAsync(() => {
186187
dialog.open(PizzaMsg, {
187188
viewContainerRef: testViewContainerRef
188189
});
189190

190191
dispatchKeyboardEvent(document, 'keydown', ESCAPE);
191192
viewContainerFixture.detectChanges();
193+
flush();
192194

193-
viewContainerFixture.whenStable().then(() => {
194-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
195-
});
195+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
196+
flush();
196197
}));
197198

198199
it('should close from a ViewContainerRef with OnPush change detection', fakeAsync(() => {
@@ -220,7 +221,7 @@ describe('MdDialog', () => {
220221
.toBe(0, 'Expected no open dialogs.');
221222
}));
222223

223-
it('should close when clicking on the overlay backdrop', async(() => {
224+
it('should close when clicking on the overlay backdrop', fakeAsync(() => {
224225
dialog.open(PizzaMsg, {
225226
viewContainerRef: testViewContainerRef
226227
});
@@ -231,13 +232,13 @@ describe('MdDialog', () => {
231232

232233
backdrop.click();
233234
viewContainerFixture.detectChanges();
235+
flush();
234236

235-
viewContainerFixture.whenStable().then(() => {
236-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
237-
});
237+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
238+
flush();
238239
}));
239240

240-
it('should emit the backdropClick stream when clicking on the overlay backdrop', async(() => {
241+
it('should emit the backdropClick stream when clicking on the overlay backdrop', fakeAsync(() => {
241242
const dialogRef = dialog.open(PizzaMsg, {
242243
viewContainerRef: testViewContainerRef
243244
});
@@ -253,12 +254,12 @@ describe('MdDialog', () => {
253254
expect(spy).toHaveBeenCalledTimes(1);
254255

255256
viewContainerFixture.detectChanges();
257+
flush();
256258

257-
viewContainerFixture.whenStable().then(() => {
258-
// Additional clicks after the dialog has closed should not be emitted
259-
backdrop.click();
260-
expect(spy).toHaveBeenCalledTimes(1);
261-
});
259+
// Additional clicks after the dialog has closed should not be emitted
260+
backdrop.click();
261+
expect(spy).toHaveBeenCalledTimes(1);
262+
flush();
262263
}));
263264

264265
it('should notify the observers if a dialog has been opened', () => {
@@ -269,7 +270,7 @@ describe('MdDialog', () => {
269270
});
270271
});
271272

272-
it('should notify the observers if all open dialogs have finished closing', async(() => {
273+
it('should notify the observers if all open dialogs have finished closing', fakeAsync(() => {
273274
const ref1 = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
274275
const ref2 = dialog.open(ContentElementDialog, { viewContainerRef: testViewContainerRef });
275276
const spy = jasmine.createSpy('afterAllClosed spy');
@@ -278,14 +279,16 @@ describe('MdDialog', () => {
278279

279280
ref1.close();
280281
viewContainerFixture.detectChanges();
282+
flush();
281283

282-
viewContainerFixture.whenStable().then(() => {
283-
expect(spy).not.toHaveBeenCalled();
284+
expect(spy).not.toHaveBeenCalled();
284285

285-
ref2.close();
286-
viewContainerFixture.detectChanges();
287-
viewContainerFixture.whenStable().then(() => expect(spy).toHaveBeenCalled());
288-
});
286+
ref2.close();
287+
viewContainerFixture.detectChanges();
288+
flush();
289+
290+
expect(spy).toHaveBeenCalled();
291+
flush();
289292
}));
290293

291294
it('should emit the afterAllClosed stream on subscribe if there are no open dialogs', () => {
@@ -418,7 +421,7 @@ describe('MdDialog', () => {
418421
expect(overlayPane.getAttribute('dir')).toBe('rtl');
419422
});
420423

421-
it('should close all of the dialogs', async(() => {
424+
it('should close all of the dialogs', fakeAsync(() => {
422425
dialog.open(PizzaMsg);
423426
dialog.open(PizzaMsg);
424427
dialog.open(PizzaMsg);
@@ -427,10 +430,10 @@ describe('MdDialog', () => {
427430

428431
dialog.closeAll();
429432
viewContainerFixture.detectChanges();
433+
flush();
430434

431-
viewContainerFixture.whenStable().then(() => {
432-
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
433-
});
435+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
436+
flush();
434437
}));
435438

436439
it('should set the proper animation states', () => {
@@ -445,32 +448,32 @@ describe('MdDialog', () => {
445448
expect(dialogContainer._state).toBe('exit');
446449
});
447450

448-
it('should close all dialogs when the user goes forwards/backwards in history', async(() => {
451+
it('should close all dialogs when the user goes forwards/backwards in history', fakeAsync(() => {
449452
dialog.open(PizzaMsg);
450453
dialog.open(PizzaMsg);
451454

452455
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(2);
453456

454457
mockLocation.simulateUrlPop('');
455458
viewContainerFixture.detectChanges();
459+
flush();
456460

457-
viewContainerFixture.whenStable().then(() => {
458-
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
459-
});
461+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
462+
flush();
460463
}));
461464

462-
it('should close all open dialogs when the location hash changes', async(() => {
465+
it('should close all open dialogs when the location hash changes', fakeAsync(() => {
463466
dialog.open(PizzaMsg);
464467
dialog.open(PizzaMsg);
465468

466469
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(2);
467470

468471
mockLocation.simulateHashChange('');
469472
viewContainerFixture.detectChanges();
473+
flush();
470474

471-
viewContainerFixture.whenStable().then(() => {
472-
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
473-
});
475+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
476+
flush();
474477
}));
475478

476479
it('should have the componentInstance available in the afterClosed callback', fakeAsync(() => {
@@ -519,17 +522,17 @@ describe('MdDialog', () => {
519522
});
520523
});
521524

522-
it('should not keep a reference to the component after the dialog is closed', async(() => {
525+
it('should not keep a reference to the component after the dialog is closed', fakeAsync(() => {
523526
let dialogRef = dialog.open(PizzaMsg);
524527

525528
expect(dialogRef.componentInstance).toBeTruthy();
526529

527530
dialogRef.close();
528531
viewContainerFixture.detectChanges();
532+
flush();
529533

530-
viewContainerFixture.whenStable().then(() => {
531-
expect(dialogRef.componentInstance).toBeFalsy('Expected reference to have been cleared.');
532-
});
534+
expect(dialogRef.componentInstance).toBeFalsy('Expected reference to have been cleared.');
535+
flush();
533536
}));
534537

535538
it('should assign a unique id to each dialog', () => {
@@ -583,7 +586,7 @@ describe('MdDialog', () => {
583586
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeTruthy();
584587
});
585588

586-
it('should allow for the disableClose option to be updated while open', async(() => {
589+
it('should allow for the disableClose option to be updated while open', fakeAsync(() => {
587590
let dialogRef = dialog.open(PizzaMsg, {
588591
disableClose: true,
589592
viewContainerRef: testViewContainerRef
@@ -600,9 +603,10 @@ describe('MdDialog', () => {
600603
backdrop.click();
601604

602605
viewContainerFixture.detectChanges();
603-
viewContainerFixture.whenStable().then(() => {
604-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
605-
});
606+
flush();
607+
608+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
609+
flush();
606610
}));
607611
});
608612

@@ -863,7 +867,7 @@ describe('MdDialog with a parent MdDialog', () => {
863867
});
864868

865869
it('should close dialogs opened by a parent when calling closeAll on a child MdDialog',
866-
async(() => {
870+
fakeAsync(() => {
867871
parentDialog.open(PizzaMsg);
868872
fixture.detectChanges();
869873

@@ -872,15 +876,15 @@ describe('MdDialog with a parent MdDialog', () => {
872876

873877
childDialog.closeAll();
874878
fixture.detectChanges();
879+
flush();
875880

876-
fixture.whenStable().then(() => {
877-
expect(overlayContainerElement.textContent!.trim())
878-
.toBe('', 'Expected closeAll on child MdDialog to close dialog opened by parent');
879-
});
881+
expect(overlayContainerElement.textContent!.trim())
882+
.toBe('', 'Expected closeAll on child MdDialog to close dialog opened by parent');
883+
flush();
880884
}));
881885

882886
it('should close dialogs opened by a child when calling closeAll on a parent MdDialog',
883-
async(() => {
887+
fakeAsync(() => {
884888
childDialog.open(PizzaMsg);
885889
fixture.detectChanges();
886890

@@ -889,22 +893,22 @@ describe('MdDialog with a parent MdDialog', () => {
889893

890894
parentDialog.closeAll();
891895
fixture.detectChanges();
896+
flush();
892897

893-
fixture.whenStable().then(() => {
894-
expect(overlayContainerElement.textContent!.trim())
895-
.toBe('', 'Expected closeAll on parent MdDialog to close dialog opened by child');
896-
});
898+
expect(overlayContainerElement.textContent!.trim())
899+
.toBe('', 'Expected closeAll on parent MdDialog to close dialog opened by child');
900+
flush();
897901
}));
898902

899-
it('should close the top dialog via the escape key', async(() => {
903+
it('should close the top dialog via the escape key', fakeAsync(() => {
900904
childDialog.open(PizzaMsg);
901905

902906
dispatchKeyboardEvent(document, 'keydown', ESCAPE);
903907
fixture.detectChanges();
908+
flush();
904909

905-
fixture.whenStable().then(() => {
906-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
907-
});
910+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
911+
flush();
908912
}));
909913
});
910914

0 commit comments

Comments
 (0)