Skip to content

Commit a582dd2

Browse files
crisbetojelbourn
authored andcommitted
refactor(slide-toggle): switch to fakeAsync tests (#10506)
Switches all of the slide toggle unit tests to run in the `fakeAsync` zone, rather than `async`, in order to make them cleaner and more reliable.
1 parent 6857426 commit a582dd2

File tree

1 file changed

+47
-44
lines changed

1 file changed

+47
-44
lines changed

src/lib/slide-toggle/slide-toggle.spec.ts

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
import {Component} from '@angular/core';
22
import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
33
import {
4-
async, ComponentFixture, TestBed, fakeAsync, tick,
5-
flushMicrotasks
4+
ComponentFixture,
5+
TestBed,
6+
fakeAsync,
7+
tick,
8+
flushMicrotasks,
69
} from '@angular/core/testing';
710
import {NgModel, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
811
import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index';
912
import {TestGestureConfig} from '../slider/test-gesture-config';
1013
import {dispatchFakeEvent} from '@angular/cdk/testing';
1114
import {defaultRippleAnimationConfig} from '@angular/material/core';
15+
import {MutationObserverFactory} from '@angular/cdk/observers';
1216

1317
describe('MatSlideToggle without forms', () => {
1418
let gestureConfig: TestGestureConfig;
19+
let mutationObserverCallbacks: Function[];
20+
let flushMutationObserver = () => mutationObserverCallbacks.forEach(callback => callback());
21+
22+
beforeEach(fakeAsync(() => {
23+
mutationObserverCallbacks = [];
1524

16-
beforeEach(async(() => {
1725
TestBed.configureTestingModule({
1826
imports: [MatSlideToggleModule],
1927
declarations: [
@@ -22,7 +30,16 @@ describe('MatSlideToggle without forms', () => {
2230
SlideToggleWithoutLabel
2331
],
2432
providers: [
25-
{provide: HAMMER_GESTURE_CONFIG, useFactory: () => gestureConfig = new TestGestureConfig()}
33+
{provide: HAMMER_GESTURE_CONFIG, useFactory: () => gestureConfig = new TestGestureConfig()},
34+
{
35+
provide: MutationObserverFactory,
36+
useValue: {
37+
create: (callback: Function) => {
38+
mutationObserverCallbacks.push(callback);
39+
return {observe: () => {}, disconnect: () => {}};
40+
}
41+
}
42+
}
2643
]
2744
});
2845

@@ -39,7 +56,7 @@ describe('MatSlideToggle without forms', () => {
3956
let inputElement: HTMLInputElement;
4057

4158
// This initialization is async() because it needs to wait for ngModel to set the initial value.
42-
beforeEach(async(() => {
59+
beforeEach(fakeAsync(() => {
4360
fixture = TestBed.createComponent(SlideToggleBasic);
4461

4562
// Enable jasmine spies on event functions, which may trigger at initialization
@@ -129,7 +146,7 @@ describe('MatSlideToggle without forms', () => {
129146
expect(testComponent.onSlideChange).toHaveBeenCalledTimes(1);
130147
});
131148

132-
it('should not trigger the change event by changing the native value', async(() => {
149+
it('should not trigger the change event by changing the native value', fakeAsync(() => {
133150
expect(inputElement.checked).toBe(false);
134151
expect(slideToggleElement.classList).not.toContain('mat-checked');
135152

@@ -138,15 +155,12 @@ describe('MatSlideToggle without forms', () => {
138155

139156
expect(inputElement.checked).toBe(true);
140157
expect(slideToggleElement.classList).toContain('mat-checked');
158+
tick();
141159

142-
// The change event shouldn't fire because the value change was not caused
143-
// by any interaction. Use whenStable to ensure an event isn't fired asynchronously.
144-
fixture.whenStable().then(() => {
145-
expect(testComponent.onSlideChange).not.toHaveBeenCalled();
146-
});
160+
expect(testComponent.onSlideChange).not.toHaveBeenCalled();
147161
}));
148162

149-
it('should not trigger the change event on initialization', async(() => {
163+
it('should not trigger the change event on initialization', fakeAsync(() => {
150164
expect(inputElement.checked).toBe(false);
151165
expect(slideToggleElement.classList).not.toContain('mat-checked');
152166

@@ -155,12 +169,9 @@ describe('MatSlideToggle without forms', () => {
155169

156170
expect(inputElement.checked).toBe(true);
157171
expect(slideToggleElement.classList).toContain('mat-checked');
172+
tick();
158173

159-
// The change event shouldn't fire, because the native input element is not focused.
160-
// Use whenStable to ensure an event isn't fired asynchronously.
161-
fixture.whenStable().then(() => {
162-
expect(testComponent.onSlideChange).not.toHaveBeenCalled();
163-
});
174+
expect(testComponent.onSlideChange).not.toHaveBeenCalled();
164175
}));
165176

166177
it('should add a suffix to the inputs id', () => {
@@ -235,16 +246,15 @@ describe('MatSlideToggle without forms', () => {
235246
expect(inputElement.hasAttribute('aria-labelledby')).toBeFalsy();
236247
});
237248

238-
it('should emit the new values properly', async(() => {
249+
it('should emit the new values properly', fakeAsync(() => {
239250
labelElement.click();
240251
fixture.detectChanges();
252+
tick();
241253

242-
fixture.whenStable().then(() => {
243-
// We're checking the arguments type / emitted value to be a boolean, because sometimes the
244-
// emitted value can be a DOM Event, which is not valid.
245-
// See angular/angular#4059
246-
expect(testComponent.lastEvent.checked).toBe(true);
247-
});
254+
// We're checking the arguments type / emitted value to be a boolean, because sometimes the
255+
// emitted value can be a DOM Event, which is not valid.
256+
// See angular/angular#4059
257+
expect(testComponent.lastEvent.checked).toBe(true);
248258
}));
249259

250260
it('should support subscription on the change observable', () => {
@@ -329,7 +339,7 @@ describe('MatSlideToggle without forms', () => {
329339
});
330340

331341
describe('custom template', () => {
332-
it('should not trigger the change event on initialization', async(() => {
342+
it('should not trigger the change event on initialization', fakeAsync(() => {
333343
const fixture = TestBed.createComponent(SlideToggleBasic);
334344

335345
fixture.componentInstance.slideChecked = true;
@@ -338,7 +348,7 @@ describe('MatSlideToggle without forms', () => {
338348
expect(fixture.componentInstance.lastEvent).toBeFalsy();
339349
}));
340350

341-
it('should be able to set the tabindex via the native attribute', async(() => {
351+
it('should be able to set the tabindex via the native attribute', fakeAsync(() => {
342352
const fixture = TestBed.createComponent(SlideToggleWithTabindexAttr);
343353

344354
fixture.detectChanges();
@@ -360,7 +370,7 @@ describe('MatSlideToggle without forms', () => {
360370
let slideThumbContainer: HTMLElement;
361371
let inputElement: HTMLInputElement;
362372

363-
beforeEach(async(() => {
373+
beforeEach(fakeAsync(() => {
364374
fixture = TestBed.createComponent(SlideToggleBasic);
365375
fixture.detectChanges();
366376

@@ -545,42 +555,34 @@ describe('MatSlideToggle without forms', () => {
545555
.toContain('mat-slide-toggle-bar-no-side-margin');
546556
});
547557

548-
it('should not remove margin if initial label is set through binding', async(() => {
558+
it('should not remove margin if initial label is set through binding', fakeAsync(() => {
549559
testComponent.label = 'Some content';
550560
fixture.detectChanges();
551561

552562
expect(slideToggleBarElement.classList)
553563
.not.toContain('mat-slide-toggle-bar-no-side-margin');
554564
}));
555565

556-
it('should re-add margin if label is added asynchronously', async(() => {
566+
it('should re-add margin if label is added asynchronously', fakeAsync(() => {
557567
fixture.detectChanges();
558568

559569
expect(slideToggleBarElement.classList)
560570
.toContain('mat-slide-toggle-bar-no-side-margin');
561571

562572
testComponent.label = 'Some content';
563573
fixture.detectChanges();
574+
flushMutationObserver();
575+
fixture.detectChanges();
564576

565-
// Wait for the MutationObserver to detect the content change and for the cdkObserveContent
566-
// to emit the change event to the slide-toggle.
567-
setTimeout(() => {
568-
// The MutationObserver from the cdkObserveContent directive detected the content change
569-
// and notified the slide-toggle component. The slide-toggle then marks the component as
570-
// dirty by calling `markForCheck()`. This needs to be reflected by the component template
571-
// then.
572-
fixture.detectChanges();
573-
574-
expect(slideToggleElement.classList)
575-
.not.toContain('mat-slide-toggle-bar-no-side-margin');
576-
}, 1);
577+
expect(slideToggleElement.classList)
578+
.not.toContain('mat-slide-toggle-bar-no-side-margin');
577579
}));
578580
});
579581
});
580582

581583
describe('MatSlideToggle with forms', () => {
582584

583-
beforeEach(async(() => {
585+
beforeEach(fakeAsync(() => {
584586
TestBed.configureTestingModule({
585587
imports: [MatSlideToggleModule, FormsModule, ReactiveFormsModule],
586588
declarations: [
@@ -605,7 +607,7 @@ describe('MatSlideToggle with forms', () => {
605607
let labelElement: HTMLLabelElement;
606608

607609
// This initialization is async() because it needs to wait for ngModel to set the initial value.
608-
beforeEach(async(() => {
610+
beforeEach(fakeAsync(() => {
609611
fixture = TestBed.createComponent(SlideToggleWithModel);
610612
fixture.detectChanges();
611613

@@ -723,6 +725,7 @@ describe('MatSlideToggle with forms', () => {
723725

724726
labelElement.click();
725727
fixture.detectChanges();
728+
tick();
726729

727730
expect(slideToggle.checked)
728731
.toBe(false, 'Expected slide-toggle to be no longer checked after label click.');
@@ -785,7 +788,7 @@ describe('MatSlideToggle with forms', () => {
785788
let inputElement: HTMLInputElement;
786789

787790
// This initialization is async() because it needs to wait for ngModel to set the initial value.
788-
beforeEach(async(() => {
791+
beforeEach(fakeAsync(() => {
789792
fixture = TestBed.createComponent(SlideToggleWithForm);
790793
fixture.detectChanges();
791794

0 commit comments

Comments
 (0)