Skip to content

Commit 01fae7a

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 34b5620 commit 01fae7a

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
@@ -7,7 +7,7 @@ import {
77
dispatchMouseEvent,
88
} from '@angular/cdk/testing';
99
import {Component, ViewChild} from '@angular/core';
10-
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
10+
import {async, ComponentFixture, inject, TestBed, fakeAsync, flush} from '@angular/core/testing';
1111
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
1212
import {
1313
DEC,
@@ -163,7 +163,7 @@ describe('MdDatepicker', () => {
163163
.toBe(true, 'Expected default ESCAPE action to be prevented.');
164164
});
165165

166-
it('close should close dialog', () => {
166+
it('close should close dialog', fakeAsync(() => {
167167
testComponent.touch = true;
168168
fixture.detectChanges();
169169

@@ -174,13 +174,13 @@ describe('MdDatepicker', () => {
174174

175175
testComponent.datepicker.close();
176176
fixture.detectChanges();
177+
flush();
177178

178-
fixture.whenStable().then(() => {
179-
expect(document.querySelector('md-dialog-container')).toBeNull();
180-
});
181-
});
179+
expect(document.querySelector('md-dialog-container')).toBeNull();
180+
flush();
181+
}));
182182

183-
it('setting selected should update input and close calendar', () => {
183+
it('setting selected should update input and close calendar', fakeAsync(() => {
184184
testComponent.touch = true;
185185
fixture.detectChanges();
186186

@@ -193,12 +193,12 @@ describe('MdDatepicker', () => {
193193
let cells = document.querySelectorAll('.mat-calendar-body-cell');
194194
dispatchMouseEvent(cells[1], 'click');
195195
fixture.detectChanges();
196+
flush();
196197

197-
fixture.whenStable().then(() => {
198-
expect(document.querySelector('md-dialog-container')).toBeNull();
199-
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
200-
});
201-
});
198+
expect(document.querySelector('md-dialog-container')).toBeNull();
199+
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
200+
flush();
201+
}));
202202

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

src/lib/dialog/dialog-container.ts

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

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

187192
/** 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,
@@ -164,21 +165,21 @@ describe('MdDialog', () => {
164165
expect(dialogContainerElement.getAttribute('aria-describedby')).toBe('description-element');
165166
});
166167

167-
it('should close a dialog and get back a result', async(() => {
168+
it('should close a dialog and get back a result', fakeAsync(() => {
168169
let dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
169170
let afterCloseCallback = jasmine.createSpy('afterClose callback');
170171

171172
dialogRef.afterClosed().subscribe(afterCloseCallback);
172173
dialogRef.close('Charmander');
173174
viewContainerFixture.detectChanges();
175+
flush();
174176

175-
viewContainerFixture.whenStable().then(() => {
176-
expect(afterCloseCallback).toHaveBeenCalledWith('Charmander');
177-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
178-
});
177+
expect(afterCloseCallback).toHaveBeenCalledWith('Charmander');
178+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
179+
flush();
179180
}));
180181

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

184185
// beforeClose should emit before dialog container is destroyed
@@ -190,24 +191,24 @@ describe('MdDialog', () => {
190191
dialogRef.beforeClose().subscribe(beforeCloseHandler);
191192
dialogRef.close('Bulbasaurus');
192193
viewContainerFixture.detectChanges();
194+
flush();
193195

194-
viewContainerFixture.whenStable().then(() => {
195-
expect(beforeCloseHandler).toHaveBeenCalledWith('Bulbasaurus');
196-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
197-
});
196+
expect(beforeCloseHandler).toHaveBeenCalledWith('Bulbasaurus');
197+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
198+
flush();
198199
}));
199200

200-
it('should close a dialog via the escape key', async(() => {
201+
it('should close a dialog via the escape key', fakeAsync(() => {
201202
dialog.open(PizzaMsg, {
202203
viewContainerRef: testViewContainerRef
203204
});
204205

205206
dispatchKeyboardEvent(document, 'keydown', ESCAPE);
206207
viewContainerFixture.detectChanges();
208+
flush();
207209

208-
viewContainerFixture.whenStable().then(() => {
209-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
210-
});
210+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
211+
flush();
211212
}));
212213

213214
it('should close from a ViewContainerRef with OnPush change detection', fakeAsync(() => {
@@ -235,7 +236,7 @@ describe('MdDialog', () => {
235236
.toBe(0, 'Expected no open dialogs.');
236237
}));
237238

238-
it('should close when clicking on the overlay backdrop', async(() => {
239+
it('should close when clicking on the overlay backdrop', fakeAsync(() => {
239240
dialog.open(PizzaMsg, {
240241
viewContainerRef: testViewContainerRef
241242
});
@@ -246,13 +247,13 @@ describe('MdDialog', () => {
246247

247248
backdrop.click();
248249
viewContainerFixture.detectChanges();
250+
flush();
249251

250-
viewContainerFixture.whenStable().then(() => {
251-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
252-
});
252+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
253+
flush();
253254
}));
254255

255-
it('should emit the backdropClick stream when clicking on the overlay backdrop', async(() => {
256+
it('should emit the backdropClick stream when clicking on the overlay backdrop', fakeAsync(() => {
256257
const dialogRef = dialog.open(PizzaMsg, {
257258
viewContainerRef: testViewContainerRef
258259
});
@@ -268,12 +269,12 @@ describe('MdDialog', () => {
268269
expect(spy).toHaveBeenCalledTimes(1);
269270

270271
viewContainerFixture.detectChanges();
272+
flush();
271273

272-
viewContainerFixture.whenStable().then(() => {
273-
// Additional clicks after the dialog has closed should not be emitted
274-
backdrop.click();
275-
expect(spy).toHaveBeenCalledTimes(1);
276-
});
274+
// Additional clicks after the dialog has closed should not be emitted
275+
backdrop.click();
276+
expect(spy).toHaveBeenCalledTimes(1);
277+
flush();
277278
}));
278279

279280
it('should notify the observers if a dialog has been opened', () => {
@@ -284,7 +285,7 @@ describe('MdDialog', () => {
284285
});
285286
});
286287

287-
it('should notify the observers if all open dialogs have finished closing', async(() => {
288+
it('should notify the observers if all open dialogs have finished closing', fakeAsync(() => {
288289
const ref1 = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
289290
const ref2 = dialog.open(ContentElementDialog, { viewContainerRef: testViewContainerRef });
290291
const spy = jasmine.createSpy('afterAllClosed spy');
@@ -293,14 +294,16 @@ describe('MdDialog', () => {
293294

294295
ref1.close();
295296
viewContainerFixture.detectChanges();
297+
flush();
296298

297-
viewContainerFixture.whenStable().then(() => {
298-
expect(spy).not.toHaveBeenCalled();
299+
expect(spy).not.toHaveBeenCalled();
299300

300-
ref2.close();
301-
viewContainerFixture.detectChanges();
302-
viewContainerFixture.whenStable().then(() => expect(spy).toHaveBeenCalled());
303-
});
301+
ref2.close();
302+
viewContainerFixture.detectChanges();
303+
flush();
304+
305+
expect(spy).toHaveBeenCalled();
306+
flush();
304307
}));
305308

306309
it('should emit the afterAllClosed stream on subscribe if there are no open dialogs', () => {
@@ -433,7 +436,7 @@ describe('MdDialog', () => {
433436
expect(overlayPane.getAttribute('dir')).toBe('rtl');
434437
});
435438

436-
it('should close all of the dialogs', async(() => {
439+
it('should close all of the dialogs', fakeAsync(() => {
437440
dialog.open(PizzaMsg);
438441
dialog.open(PizzaMsg);
439442
dialog.open(PizzaMsg);
@@ -442,10 +445,10 @@ describe('MdDialog', () => {
442445

443446
dialog.closeAll();
444447
viewContainerFixture.detectChanges();
448+
flush();
445449

446-
viewContainerFixture.whenStable().then(() => {
447-
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
448-
});
450+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
451+
flush();
449452
}));
450453

451454
it('should set the proper animation states', () => {
@@ -460,32 +463,32 @@ describe('MdDialog', () => {
460463
expect(dialogContainer._state).toBe('exit');
461464
});
462465

463-
it('should close all dialogs when the user goes forwards/backwards in history', async(() => {
466+
it('should close all dialogs when the user goes forwards/backwards in history', fakeAsync(() => {
464467
dialog.open(PizzaMsg);
465468
dialog.open(PizzaMsg);
466469

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

469472
mockLocation.simulateUrlPop('');
470473
viewContainerFixture.detectChanges();
474+
flush();
471475

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

477-
it('should close all open dialogs when the location hash changes', async(() => {
480+
it('should close all open dialogs when the location hash changes', fakeAsync(() => {
478481
dialog.open(PizzaMsg);
479482
dialog.open(PizzaMsg);
480483

481484
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(2);
482485

483486
mockLocation.simulateHashChange('');
484487
viewContainerFixture.detectChanges();
488+
flush();
485489

486-
viewContainerFixture.whenStable().then(() => {
487-
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
488-
});
490+
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
491+
flush();
489492
}));
490493

491494
it('should have the componentInstance available in the afterClosed callback', fakeAsync(() => {
@@ -534,17 +537,17 @@ describe('MdDialog', () => {
534537
});
535538
});
536539

537-
it('should not keep a reference to the component after the dialog is closed', async(() => {
540+
it('should not keep a reference to the component after the dialog is closed', fakeAsync(() => {
538541
let dialogRef = dialog.open(PizzaMsg);
539542

540543
expect(dialogRef.componentInstance).toBeTruthy();
541544

542545
dialogRef.close();
543546
viewContainerFixture.detectChanges();
547+
flush();
544548

545-
viewContainerFixture.whenStable().then(() => {
546-
expect(dialogRef.componentInstance).toBeFalsy('Expected reference to have been cleared.');
547-
});
549+
expect(dialogRef.componentInstance).toBeFalsy('Expected reference to have been cleared.');
550+
flush();
548551
}));
549552

550553
it('should assign a unique id to each dialog', () => {
@@ -598,7 +601,7 @@ describe('MdDialog', () => {
598601
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeTruthy();
599602
});
600603

601-
it('should allow for the disableClose option to be updated while open', async(() => {
604+
it('should allow for the disableClose option to be updated while open', fakeAsync(() => {
602605
let dialogRef = dialog.open(PizzaMsg, {
603606
disableClose: true,
604607
viewContainerRef: testViewContainerRef
@@ -615,9 +618,10 @@ describe('MdDialog', () => {
615618
backdrop.click();
616619

617620
viewContainerFixture.detectChanges();
618-
viewContainerFixture.whenStable().then(() => {
619-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
620-
});
621+
flush();
622+
623+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
624+
flush();
621625
}));
622626
});
623627

@@ -878,7 +882,7 @@ describe('MdDialog with a parent MdDialog', () => {
878882
});
879883

880884
it('should close dialogs opened by a parent when calling closeAll on a child MdDialog',
881-
async(() => {
885+
fakeAsync(() => {
882886
parentDialog.open(PizzaMsg);
883887
fixture.detectChanges();
884888

@@ -887,15 +891,15 @@ describe('MdDialog with a parent MdDialog', () => {
887891

888892
childDialog.closeAll();
889893
fixture.detectChanges();
894+
flush();
890895

891-
fixture.whenStable().then(() => {
892-
expect(overlayContainerElement.textContent!.trim())
893-
.toBe('', 'Expected closeAll on child MdDialog to close dialog opened by parent');
894-
});
896+
expect(overlayContainerElement.textContent!.trim())
897+
.toBe('', 'Expected closeAll on child MdDialog to close dialog opened by parent');
898+
flush();
895899
}));
896900

897901
it('should close dialogs opened by a child when calling closeAll on a parent MdDialog',
898-
async(() => {
902+
fakeAsync(() => {
899903
childDialog.open(PizzaMsg);
900904
fixture.detectChanges();
901905

@@ -904,22 +908,22 @@ describe('MdDialog with a parent MdDialog', () => {
904908

905909
parentDialog.closeAll();
906910
fixture.detectChanges();
911+
flush();
907912

908-
fixture.whenStable().then(() => {
909-
expect(overlayContainerElement.textContent!.trim())
910-
.toBe('', 'Expected closeAll on parent MdDialog to close dialog opened by child');
911-
});
913+
expect(overlayContainerElement.textContent!.trim())
914+
.toBe('', 'Expected closeAll on parent MdDialog to close dialog opened by child');
915+
flush();
912916
}));
913917

914-
it('should close the top dialog via the escape key', async(() => {
918+
it('should close the top dialog via the escape key', fakeAsync(() => {
915919
childDialog.open(PizzaMsg);
916920

917921
dispatchKeyboardEvent(document, 'keydown', ESCAPE);
918922
fixture.detectChanges();
923+
flush();
919924

920-
fixture.whenStable().then(() => {
921-
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
922-
});
925+
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
926+
flush();
923927
}));
924928
});
925929

0 commit comments

Comments
 (0)