Skip to content

refactor(checkbox): switch to fakeAsync tests #8639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 8, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 77 additions & 60 deletions src/lib/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import {
async,
ComponentFixture,
fakeAsync,
flushMicrotasks,
TestBed,
tick,
} from '@angular/core/testing';
import {ComponentFixture, fakeAsync, TestBed, tick, flush} from '@angular/core/testing';
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
import {dispatchFakeEvent} from '@angular/cdk/testing';
import {MatCheckbox, MatCheckboxChange, MatCheckboxModule} from './index';
import {RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from '@angular/material/core';
import {MAT_CHECKBOX_CLICK_ACTION} from './checkbox-config';
import {MutationObserverFactory} from '@angular/cdk/observers';


describe('MatCheckbox', () => {
let fixture: ComponentFixture<any>;

beforeEach(async(() => {
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [MatCheckboxModule, FormsModule, ReactiveFormsModule],
declarations: [
Expand Down Expand Up @@ -116,7 +110,7 @@ describe('MatCheckbox', () => {
fixture.detectChanges();

// Flush the microtasks because the forms module updates the model state asynchronously.
flushMicrotasks();
flush();

// The checked property has been updated from the model and now the view needs
// to reflect the state change.
Expand All @@ -141,7 +135,7 @@ describe('MatCheckbox', () => {
fixture.detectChanges();

// Flush the microtasks because the forms module updates the model state asynchronously.
flushMicrotasks();
flush();

// The checked property has been updated from the model and now the view needs
// to reflect the state change.
Expand All @@ -153,7 +147,7 @@ describe('MatCheckbox', () => {
expect(testComponent.isIndeterminate).toBe(false);
}));

it('should not set indeterminate to false when checked is set programmatically', async(() => {
it('should not set indeterminate to false when checked is set programmatically', () => {
testComponent.isIndeterminate = true;
fixture.detectChanges();

Expand All @@ -176,7 +170,7 @@ describe('MatCheckbox', () => {
expect(inputElement.indeterminate).toBe(true);
expect(inputElement.checked).toBe(false);
expect(testComponent.isIndeterminate).toBe(true);
}));
});

it('should change native element checked when check programmatically', () => {
expect(inputElement.checked).toBe(false);
Expand Down Expand Up @@ -212,7 +206,7 @@ describe('MatCheckbox', () => {
checkboxInstance._onInputClick(<Event>{stopPropagation: () => {}});

// Flush the microtasks because the indeterminate state will be updated in the next tick.
flushMicrotasks();
flush();

expect(checkboxInstance.checked).toBe(true);
expect(checkboxInstance.indeterminate).toBe(false);
Expand Down Expand Up @@ -262,7 +256,7 @@ describe('MatCheckbox', () => {
fixture.detectChanges();

// Flush the microtasks because the indeterminate state will be updated in the next tick.
flushMicrotasks();
flush();

expect(checkboxInstance.checked).toBe(true);
expect(checkboxInstance.indeterminate).toBe(false);
Expand Down Expand Up @@ -317,7 +311,7 @@ describe('MatCheckbox', () => {
expect(testComponent.onCheckboxClick).toHaveBeenCalledTimes(1);
});

it('should trigger a change event when the native input does', async(() => {
it('should trigger a change event when the native input does', fakeAsync(() => {
spyOn(testComponent, 'onCheckboxChange');

expect(inputElement.checked).toBe(false);
Expand All @@ -329,16 +323,15 @@ describe('MatCheckbox', () => {
expect(inputElement.checked).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-checked');

// Wait for the fixture to become stable, because the EventEmitter for the change event,
// will only fire after the zone async change detection has finished.
fixture.whenStable().then(() => {
// The change event shouldn't fire, because the value change was not caused
// by any interaction.
expect(testComponent.onCheckboxChange).toHaveBeenCalledTimes(1);
});
fixture.detectChanges();
flush();

// The change event shouldn't fire, because the value change was not caused
// by any interaction.
expect(testComponent.onCheckboxChange).toHaveBeenCalledTimes(1);
}));

it('should not trigger the change event by changing the native value', async(() => {
it('should not trigger the change event by changing the native value', fakeAsync(() => {
spyOn(testComponent, 'onCheckboxChange');

expect(inputElement.checked).toBe(false);
Expand All @@ -350,14 +343,12 @@ describe('MatCheckbox', () => {
expect(inputElement.checked).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-checked');

// Wait for the fixture to become stable, because the EventEmitter for the change event,
// will only fire after the zone async change detection has finished.
fixture.whenStable().then(() => {
// The change event shouldn't fire, because the value change was not caused
// by any interaction.
expect(testComponent.onCheckboxChange).not.toHaveBeenCalled();
});
fixture.detectChanges();
flush();

// The change event shouldn't fire, because the value change was not caused
// by any interaction.
expect(testComponent.onCheckboxChange).not.toHaveBeenCalled();
}));

it('should forward the required attribute', () => {
Expand Down Expand Up @@ -576,7 +567,7 @@ describe('MatCheckbox', () => {
inputElement.click();

fixture.detectChanges();
flushMicrotasks();
flush();
fixture.detectChanges();
expect(inputElement.checked).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-checked');
Expand Down Expand Up @@ -614,7 +605,7 @@ describe('MatCheckbox', () => {
inputElement.click();

fixture.detectChanges();
flushMicrotasks();
flush();
fixture.detectChanges();

expect(inputElement.checked).toBe(false);
Expand All @@ -630,7 +621,7 @@ describe('MatCheckbox', () => {
inputElement.click();

fixture.detectChanges();
flushMicrotasks();
flush();
fixture.detectChanges();

expect(inputElement.checked).toBe(true);
Expand All @@ -642,7 +633,7 @@ describe('MatCheckbox', () => {
inputElement.click();

fixture.detectChanges();
flushMicrotasks();
flush();
fixture.detectChanges();

expect(inputElement.checked).toBe(false);
Expand Down Expand Up @@ -689,22 +680,20 @@ describe('MatCheckbox', () => {
expect(changeSpy).toHaveBeenCalledTimes(1);
});

it('should not emit a DOM event to the change output', async(() => {
it('should not emit a DOM event to the change output', fakeAsync(() => {
fixture.detectChanges();
expect(testComponent.lastEvent).toBeUndefined();

// Trigger the click on the inputElement, because the input will probably
// emit a DOM event to the change output.
inputElement.click();
fixture.detectChanges();
flush();

fixture.whenStable().then(() => {
// We're checking the arguments type / emitted value to be a boolean, because sometimes the
// emitted value can be a DOM Event, which is not valid.
// See angular/angular#4059
expect(testComponent.lastEvent.checked).toBe(true);
});

// We're checking the arguments type / emitted value to be a boolean, because sometimes the
// emitted value can be a DOM Event, which is not valid.
// See angular/angular#4059
expect(testComponent.lastEvent.checked).toBe(true);
}));
});

Expand Down Expand Up @@ -789,7 +778,7 @@ describe('MatCheckbox', () => {

describe('with native tabindex attribute', () => {

it('should properly detect native tabindex attribute', async(() => {
it('should properly detect native tabindex attribute', fakeAsync(() => {
fixture = TestBed.createComponent(CheckboxWithTabindexAttr);
fixture.detectChanges();

Expand Down Expand Up @@ -835,7 +824,7 @@ describe('MatCheckbox', () => {
});

it('should be in pristine, untouched, and valid states initially', fakeAsync(() => {
flushMicrotasks();
flush();

let checkboxElement = fixture.debugElement.query(By.directive(MatCheckbox));
let ngModel = checkboxElement.injector.get<NgModel>(NgModel);
Expand Down Expand Up @@ -968,35 +957,63 @@ describe('MatCheckbox', () => {
.toContain('mat-checkbox-inner-container-no-side-margin');
});

it('should not remove margin if initial label is set through binding', async(() => {
it('should not remove margin if initial label is set through binding', () => {
testComponent.label = 'Some content';
fixture.detectChanges();

expect(checkboxInnerContainer.classList)
.not.toContain('mat-checkbox-inner-container-no-side-margin');
}));
});

it('should re-add margin if label is added asynchronously', () => {
fixture.destroy();

const mutationCallbacks: Function[] = [];

TestBed
.resetTestingModule()
.configureTestingModule({
imports: [MatCheckboxModule, FormsModule, ReactiveFormsModule],
declarations: [CheckboxWithoutLabel],
providers: [{
provide: MutationObserverFactory,
useValue: {
// Stub out the factory that creates mutation observers for the underlying directive
// to allows us to flush out the callbacks asynchronously.
create: (callback: Function) => {
mutationCallbacks.push(callback);

return {
observe: () => {},
disconnect: () => {}
};
}
}
}]
})
.compileComponents();

fixture = TestBed.createComponent(CheckboxWithoutLabel);
checkboxInnerContainer = fixture.debugElement
.query(By.css('.mat-checkbox-inner-container')).nativeElement;

it('should re-add margin if label is added asynchronously', async(() => {
fixture.detectChanges();

expect(checkboxInnerContainer.classList)
.toContain('mat-checkbox-inner-container-no-side-margin');

testComponent.label = 'Some content';
fixture.componentInstance.label = 'Some content';
fixture.detectChanges();
mutationCallbacks.forEach(callback => callback());

// Wait for the MutationObserver to detect the content change and for the cdkObserveContent
// to emit the change event to the checkbox.
setTimeout(() => {
// The MutationObserver from the cdkObserveContent directive detected the content change
// and notified the checkbox component. The checkbox then marks the component as dirty
// by calling `markForCheck()`. This needs to be reflected by the component template then.
fixture.detectChanges();
// The MutationObserver from the cdkObserveContent directive detected the content change
// and notified the checkbox component. The checkbox then marks the component as dirty
// by calling `markForCheck()`. This needs to be reflected by the component template then.
fixture.detectChanges();

expect(checkboxInnerContainer.classList)
.not.toContain('mat-checkbox-inner-container-no-side-margin');
}, 1);
}));
expect(checkboxInnerContainer.classList)
.not.toContain('mat-checkbox-inner-container-no-side-margin');
});

it('should not add the "name" attribute if it is not passed in', () => {
fixture.detectChanges();
Expand Down