ipsos-components
Version:
Material Design components for Angular
1,142 lines (888 loc) • 42.1 kB
text/typescript
import {
async,
ComponentFixture,
fakeAsync,
flushMicrotasks,
TestBed,
tick,
} 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';
describe('MatCheckbox', () => {
let fixture: ComponentFixture<any>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatCheckboxModule, FormsModule, ReactiveFormsModule],
declarations: [
SingleCheckbox,
CheckboxWithFormDirectives,
MultipleCheckboxes,
CheckboxWithNgModel,
CheckboxWithTabIndex,
CheckboxWithAriaLabel,
CheckboxWithAriaLabelledby,
CheckboxWithNameAttribute,
CheckboxWithChangeEvent,
CheckboxWithFormControl,
CheckboxWithoutLabel,
CheckboxWithTabindexAttr,
]
});
TestBed.compileComponents();
}));
describe('basic behaviors', () => {
let checkboxDebugElement: DebugElement;
let checkboxNativeElement: HTMLElement;
let checkboxInstance: MatCheckbox;
let testComponent: SingleCheckbox;
let inputElement: HTMLInputElement;
let labelElement: HTMLLabelElement;
beforeEach(() => {
fixture = TestBed.createComponent(SingleCheckbox);
fixture.detectChanges();
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
checkboxInstance = checkboxDebugElement.componentInstance;
testComponent = fixture.debugElement.componentInstance;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
labelElement = <HTMLLabelElement>checkboxNativeElement.querySelector('label');
});
it('should add and remove the checked state', () => {
expect(checkboxInstance.checked).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
expect(inputElement.checked).toBe(false);
testComponent.isChecked = true;
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-checked');
expect(inputElement.checked).toBe(true);
testComponent.isChecked = false;
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
expect(inputElement.checked).toBe(false);
});
it('should add and remove indeterminate state', () => {
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
expect(inputElement.checked).toBe(false);
expect(inputElement.indeterminate).toBe(false);
expect(inputElement.getAttribute('aria-checked'))
.toBe('false', 'Expect aria-checked to be false');
testComponent.isIndeterminate = true;
fixture.detectChanges();
expect(checkboxNativeElement.classList).toContain('mat-checkbox-indeterminate');
expect(inputElement.checked).toBe(false);
expect(inputElement.indeterminate).toBe(true);
expect(inputElement.getAttribute('aria-checked'))
.toBe('mixed', 'Expect aria checked to be mixed for indeterminate checkbox');
testComponent.isIndeterminate = false;
fixture.detectChanges();
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-indeterminate');
expect(inputElement.checked).toBe(false);
expect(inputElement.indeterminate).toBe(false);
});
it('should set indeterminate to false when input clicked', fakeAsync(() => {
testComponent.isIndeterminate = true;
fixture.detectChanges();
expect(checkboxInstance.indeterminate).toBe(true);
expect(inputElement.indeterminate).toBe(true);
expect(testComponent.isIndeterminate).toBe(true);
inputElement.click();
fixture.detectChanges();
// Flush the microtasks because the forms module updates the model state asynchronously.
flushMicrotasks();
// The checked property has been updated from the model and now the view needs
// to reflect the state change.
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(true);
expect(inputElement.indeterminate).toBe(false);
expect(inputElement.checked).toBe(true);
expect(testComponent.isIndeterminate).toBe(false);
testComponent.isIndeterminate = true;
fixture.detectChanges();
expect(checkboxInstance.indeterminate).toBe(true);
expect(inputElement.indeterminate).toBe(true);
expect(inputElement.checked).toBe(true);
expect(testComponent.isIndeterminate).toBe(true);
expect(inputElement.getAttribute('aria-checked'))
.toBe('true', 'Expect aria checked to be true');
inputElement.click();
fixture.detectChanges();
// Flush the microtasks because the forms module updates the model state asynchronously.
flushMicrotasks();
// The checked property has been updated from the model and now the view needs
// to reflect the state change.
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
expect(inputElement.indeterminate).toBe(false);
expect(inputElement.checked).toBe(false);
expect(testComponent.isIndeterminate).toBe(false);
}));
it('should not set indeterminate to false when checked is set programmatically', async(() => {
testComponent.isIndeterminate = true;
fixture.detectChanges();
expect(checkboxInstance.indeterminate).toBe(true);
expect(inputElement.indeterminate).toBe(true);
expect(testComponent.isIndeterminate).toBe(true);
testComponent.isChecked = true;
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(true);
expect(inputElement.indeterminate).toBe(true);
expect(inputElement.checked).toBe(true);
expect(testComponent.isIndeterminate).toBe(true);
testComponent.isChecked = false;
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
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);
checkboxInstance.checked = true;
fixture.detectChanges();
expect(inputElement.checked).toBe(true);
});
it('should toggle checked state on click', () => {
expect(checkboxInstance.checked).toBe(false);
labelElement.click();
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(true);
labelElement.click();
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
});
it('should change from indeterminate to checked on click', fakeAsync(() => {
testComponent.isChecked = false;
testComponent.isIndeterminate = true;
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
expect(checkboxInstance.indeterminate).toBe(true);
checkboxInstance._onInputClick(<Event>{stopPropagation: () => {}});
// Flush the microtasks because the indeterminate state will be updated in the next tick.
flushMicrotasks();
expect(checkboxInstance.checked).toBe(true);
expect(checkboxInstance.indeterminate).toBe(false);
checkboxInstance._onInputClick(<Event>{stopPropagation: () => {}});
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
expect(checkboxInstance.indeterminate).toBe(false);
}));
it('should add and remove disabled state', () => {
expect(checkboxInstance.disabled).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-disabled');
expect(inputElement.tabIndex).toBe(0);
expect(inputElement.disabled).toBe(false);
testComponent.isDisabled = true;
fixture.detectChanges();
expect(checkboxInstance.disabled).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-disabled');
expect(inputElement.disabled).toBe(true);
testComponent.isDisabled = false;
fixture.detectChanges();
expect(checkboxInstance.disabled).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-disabled');
expect(inputElement.tabIndex).toBe(0);
expect(inputElement.disabled).toBe(false);
});
it('should not toggle `checked` state upon interation while disabled', () => {
testComponent.isDisabled = true;
fixture.detectChanges();
checkboxNativeElement.click();
expect(checkboxInstance.checked).toBe(false);
});
it('should overwrite indeterminate state when clicked', fakeAsync(() => {
testComponent.isIndeterminate = true;
fixture.detectChanges();
inputElement.click();
fixture.detectChanges();
// Flush the microtasks because the indeterminate state will be updated in the next tick.
flushMicrotasks();
expect(checkboxInstance.checked).toBe(true);
expect(checkboxInstance.indeterminate).toBe(false);
}));
it('should preserve the user-provided id', () => {
expect(checkboxNativeElement.id).toBe('simple-check');
expect(inputElement.id).toBe('simple-check-input');
});
it('should generate a unique id for the checkbox input if no id is set', () => {
testComponent.checkboxId = null;
fixture.detectChanges();
expect(checkboxInstance.inputId).toMatch(/mat-checkbox-\d+/);
expect(inputElement.id).toBe(checkboxInstance.inputId);
});
it('should project the checkbox content into the label element', () => {
let label = <HTMLLabelElement>checkboxNativeElement.querySelector('.mat-checkbox-label');
expect(label.textContent!.trim()).toBe('Simple checkbox');
});
it('should make the host element a tab stop', () => {
expect(inputElement.tabIndex).toBe(0);
});
it('should add a css class to position the label before the checkbox', () => {
testComponent.labelPos = 'before';
fixture.detectChanges();
expect(checkboxNativeElement.classList).toContain('mat-checkbox-label-before');
});
it('should not trigger the click event multiple times', () => {
// By default, when clicking on a label element, a generated click will be dispatched
// on the associated input element.
// Since we're using a label element and a visual hidden input, this behavior can led
// to an issue, where the click events on the checkbox are getting executed twice.
spyOn(testComponent, 'onCheckboxClick');
expect(inputElement.checked).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
labelElement.click();
fixture.detectChanges();
expect(checkboxNativeElement.classList).toContain('mat-checkbox-checked');
expect(inputElement.checked).toBe(true);
expect(testComponent.onCheckboxClick).toHaveBeenCalledTimes(1);
});
it('should trigger a change event when the native input does', async(() => {
spyOn(testComponent, 'onCheckboxChange');
expect(inputElement.checked).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
labelElement.click();
fixture.detectChanges();
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);
});
}));
it('should not trigger the change event by changing the native value', async(() => {
spyOn(testComponent, 'onCheckboxChange');
expect(inputElement.checked).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
testComponent.isChecked = true;
fixture.detectChanges();
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();
});
}));
it('should forward the required attribute', () => {
testComponent.isRequired = true;
fixture.detectChanges();
expect(inputElement.required).toBe(true);
testComponent.isRequired = false;
fixture.detectChanges();
expect(inputElement.required).toBe(false);
});
it('should focus on underlying input element when focus() is called', () => {
expect(document.activeElement).not.toBe(inputElement);
checkboxInstance.focus();
fixture.detectChanges();
expect(document.activeElement).toBe(inputElement);
});
it('should forward the value to input element', () => {
testComponent.checkboxValue = 'basic_checkbox';
fixture.detectChanges();
expect(inputElement.value).toBe('basic_checkbox');
});
it('should show a ripple when focused by a keyboard action', fakeAsync(() => {
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.toBe(0, 'Expected no ripples on load.');
dispatchFakeEvent(inputElement, 'keydown');
dispatchFakeEvent(inputElement, 'focus');
tick(RIPPLE_FADE_IN_DURATION);
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.toBe(1, 'Expected ripple after element is focused.');
dispatchFakeEvent(checkboxInstance._inputElement.nativeElement, 'blur');
tick(RIPPLE_FADE_OUT_DURATION);
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.toBe(0, 'Expected no ripple after element is blurred.');
}));
it('should remove the SVG checkmark from the tab order', () => {
expect(checkboxNativeElement.querySelector('svg')!.getAttribute('focusable')).toBe('false');
});
describe('ripple elements', () => {
it('should show ripples on label mousedown', () => {
expect(checkboxNativeElement.querySelector('.mat-ripple-element')).toBeFalsy();
dispatchFakeEvent(labelElement, 'mousedown');
dispatchFakeEvent(labelElement, 'mouseup');
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
});
it('should not show ripples when disabled', () => {
testComponent.isDisabled = true;
fixture.detectChanges();
dispatchFakeEvent(labelElement, 'mousedown');
dispatchFakeEvent(labelElement, 'mouseup');
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
testComponent.isDisabled = false;
fixture.detectChanges();
dispatchFakeEvent(labelElement, 'mousedown');
dispatchFakeEvent(labelElement, 'mouseup');
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
});
it('should remove ripple if matRippleDisabled input is set', () => {
testComponent.disableRipple = true;
fixture.detectChanges();
dispatchFakeEvent(labelElement, 'mousedown');
dispatchFakeEvent(labelElement, 'mouseup');
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
testComponent.disableRipple = false;
fixture.detectChanges();
dispatchFakeEvent(labelElement, 'mousedown');
dispatchFakeEvent(labelElement, 'mouseup');
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
});
});
describe('color behaviour', () => {
it('should apply class based on color attribute', () => {
testComponent.checkboxColor = 'primary';
fixture.detectChanges();
expect(checkboxNativeElement.classList.contains('mat-primary')).toBe(true);
testComponent.checkboxColor = 'accent';
fixture.detectChanges();
expect(checkboxNativeElement.classList.contains('mat-accent')).toBe(true);
});
it('should should not clear previous defined classes', () => {
checkboxNativeElement.classList.add('custom-class');
testComponent.checkboxColor = 'primary';
fixture.detectChanges();
expect(checkboxNativeElement.classList.contains('mat-primary')).toBe(true);
expect(checkboxNativeElement.classList.contains('custom-class')).toBe(true);
testComponent.checkboxColor = 'accent';
fixture.detectChanges();
expect(checkboxNativeElement.classList.contains('mat-primary')).toBe(false);
expect(checkboxNativeElement.classList.contains('mat-accent')).toBe(true);
expect(checkboxNativeElement.classList.contains('custom-class')).toBe(true);
});
});
describe('state transition css classes', () => {
it('should transition unchecked -> checked -> unchecked', () => {
inputElement.click();
fixture.detectChanges();
expect(checkboxNativeElement.classList).toContain('mat-checkbox-anim-unchecked-checked');
inputElement.click();
fixture.detectChanges();
expect(checkboxNativeElement.classList)
.not.toContain('mat-checkbox-anim-unchecked-checked');
expect(checkboxNativeElement.classList)
.toContain('mat-checkbox-anim-checked-unchecked');
});
it('should transition unchecked -> indeterminate -> unchecked', () => {
testComponent.isIndeterminate = true;
fixture.detectChanges();
expect(checkboxNativeElement.classList)
.toContain('mat-checkbox-anim-unchecked-indeterminate');
testComponent.isIndeterminate = false;
fixture.detectChanges();
expect(checkboxNativeElement.classList)
.not.toContain('mat-checkbox-anim-unchecked-indeterminate');
expect(checkboxNativeElement.classList)
.toContain('mat-checkbox-anim-indeterminate-unchecked');
});
it('should transition indeterminate -> checked', () => {
testComponent.isIndeterminate = true;
fixture.detectChanges();
inputElement.click();
fixture.detectChanges();
expect(checkboxNativeElement.classList).not.toContain(
'mat-checkbox-anim-unchecked-indeterminate');
expect(checkboxNativeElement.classList)
.toContain('mat-checkbox-anim-indeterminate-checked');
});
it('should not apply transition classes when there is no state change', () => {
testComponent.isChecked = checkboxInstance.checked;
fixture.detectChanges();
expect(checkboxNativeElement).not.toMatch(/^mat\-checkbox\-anim/g);
testComponent.isIndeterminate = checkboxInstance.indeterminate;
expect(checkboxNativeElement).not.toMatch(/^mat\-checkbox\-anim/g);
});
it('should not initially have any transition classes', () => {
expect(checkboxNativeElement).not.toMatch(/^mat\-checkbox\-anim/g);
});
});
describe(`when MAT_CHECKBOX_CLICK_ACTION is 'check'`, () => {
beforeEach(() => {
TestBed.resetTestingModule();
TestBed.configureTestingModule({
imports: [MatCheckboxModule, FormsModule, ReactiveFormsModule],
declarations: [
SingleCheckbox,
],
providers: [
{provide: MAT_CHECKBOX_CLICK_ACTION, useValue: 'check'}
]
});
fixture = TestBed.createComponent(SingleCheckbox);
fixture.detectChanges();
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
checkboxInstance = checkboxDebugElement.componentInstance;
testComponent = fixture.debugElement.componentInstance;
inputElement = checkboxNativeElement.querySelector('input') as HTMLInputElement;
labelElement = checkboxNativeElement.querySelector('label') as HTMLLabelElement;
});
it('should not set `indeterminate` to false on click if check is set', fakeAsync(() => {
testComponent.isIndeterminate = true;
inputElement.click();
fixture.detectChanges();
flushMicrotasks();
fixture.detectChanges();
expect(inputElement.checked).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-checked');
expect(inputElement.indeterminate).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-indeterminate');
}));
});
describe(`when MAT_CHECKBOX_CLICK_ACTION is 'noop'`, () => {
beforeEach(() => {
TestBed.resetTestingModule();
TestBed.configureTestingModule({
imports: [MatCheckboxModule, FormsModule, ReactiveFormsModule],
declarations: [
SingleCheckbox,
],
providers: [
{provide: MAT_CHECKBOX_CLICK_ACTION, useValue: 'noop'}
]
});
fixture = TestBed.createComponent(SingleCheckbox);
fixture.detectChanges();
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
checkboxInstance = checkboxDebugElement.componentInstance;
testComponent = fixture.debugElement.componentInstance;
inputElement = checkboxNativeElement.querySelector('input') as HTMLInputElement;
labelElement = checkboxNativeElement.querySelector('label') as HTMLLabelElement;
});
it('should not change `indeterminate` on click if noop is set', fakeAsync(() => {
testComponent.isIndeterminate = true;
inputElement.click();
fixture.detectChanges();
flushMicrotasks();
fixture.detectChanges();
expect(inputElement.checked).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
expect(inputElement.indeterminate).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-indeterminate');
}));
it(`should not change 'checked' or 'indeterminate' on click if noop is set`, fakeAsync(() => {
testComponent.isChecked = true;
testComponent.isIndeterminate = true;
inputElement.click();
fixture.detectChanges();
flushMicrotasks();
fixture.detectChanges();
expect(inputElement.checked).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-checked');
expect(inputElement.indeterminate).toBe(true);
expect(checkboxNativeElement.classList).toContain('mat-checkbox-indeterminate');
testComponent.isChecked = false;
inputElement.click();
fixture.detectChanges();
flushMicrotasks();
fixture.detectChanges();
expect(inputElement.checked).toBe(false);
expect(checkboxNativeElement.classList).not.toContain('mat-checkbox-checked');
expect(inputElement.indeterminate).toBe(true, 'indeterminate should not change');
expect(checkboxNativeElement.classList).toContain('mat-checkbox-indeterminate');
}));
});
});
describe('with change event and no initial value', () => {
let checkboxDebugElement: DebugElement;
let checkboxNativeElement: HTMLElement;
let checkboxInstance: MatCheckbox;
let testComponent: CheckboxWithChangeEvent;
let inputElement: HTMLInputElement;
let labelElement: HTMLLabelElement;
beforeEach(() => {
fixture = TestBed.createComponent(CheckboxWithChangeEvent);
fixture.detectChanges();
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
checkboxInstance = checkboxDebugElement.componentInstance;
testComponent = fixture.debugElement.componentInstance;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
labelElement = <HTMLLabelElement>checkboxNativeElement.querySelector('label');
});
it('should emit the event to the change observable', () => {
let changeSpy = jasmine.createSpy('onChangeObservable');
checkboxInstance.change.subscribe(changeSpy);
fixture.detectChanges();
expect(changeSpy).not.toHaveBeenCalled();
// When changing the native `checked` property the checkbox will not fire a change event,
// because the element is not focused and it's not the native behavior of the input element.
labelElement.click();
fixture.detectChanges();
expect(changeSpy).toHaveBeenCalledTimes(1);
});
it('should not emit a DOM event to the change output', async(() => {
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();
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);
});
}));
});
describe('with provided aria-label ', () => {
let checkboxDebugElement: DebugElement;
let checkboxNativeElement: HTMLElement;
let inputElement: HTMLInputElement;
it('should use the provided aria-label', () => {
fixture = TestBed.createComponent(CheckboxWithAriaLabel);
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
fixture.detectChanges();
expect(inputElement.getAttribute('aria-label')).toBe('Super effective');
});
});
describe('with provided aria-labelledby ', () => {
let checkboxDebugElement: DebugElement;
let checkboxNativeElement: HTMLElement;
let inputElement: HTMLInputElement;
it('should use the provided aria-labelledby', () => {
fixture = TestBed.createComponent(CheckboxWithAriaLabelledby);
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
fixture.detectChanges();
expect(inputElement.getAttribute('aria-labelledby')).toBe('some-id');
});
it('should not assign aria-labelledby if none is provided', () => {
fixture = TestBed.createComponent(SingleCheckbox);
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
fixture.detectChanges();
expect(inputElement.getAttribute('aria-labelledby')).toBe(null);
});
});
describe('with provided tabIndex', () => {
let checkboxDebugElement: DebugElement;
let checkboxNativeElement: HTMLElement;
let testComponent: CheckboxWithTabIndex;
let inputElement: HTMLInputElement;
let labelElement: HTMLLabelElement;
beforeEach(() => {
fixture = TestBed.createComponent(CheckboxWithTabIndex);
fixture.detectChanges();
testComponent = fixture.debugElement.componentInstance;
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
labelElement = <HTMLLabelElement>checkboxNativeElement.querySelector('label');
});
it('should preserve any given tabIndex', () => {
expect(inputElement.tabIndex).toBe(7);
});
it('should preserve given tabIndex when the checkbox is disabled then enabled', () => {
testComponent.isDisabled = true;
fixture.detectChanges();
testComponent.customTabIndex = 13;
fixture.detectChanges();
testComponent.isDisabled = false;
fixture.detectChanges();
expect(inputElement.tabIndex).toBe(13);
});
});
describe('with native tabindex attribute', () => {
it('should properly detect native tabindex attribute', async(() => {
fixture = TestBed.createComponent(CheckboxWithTabindexAttr);
fixture.detectChanges();
const checkbox = fixture.debugElement
.query(By.directive(MatCheckbox)).componentInstance as MatCheckbox;
expect(checkbox.tabIndex)
.toBe(5, 'Expected tabIndex property to have been set based on the native attribute');
}));
});
describe('with multiple checkboxes', () => {
beforeEach(() => {
fixture = TestBed.createComponent(MultipleCheckboxes);
fixture.detectChanges();
});
it('should assign a unique id to each checkbox', () => {
let [firstId, secondId] =
fixture.debugElement.queryAll(By.directive(MatCheckbox))
.map(debugElement => debugElement.nativeElement.querySelector('input').id);
expect(firstId).toMatch(/mat-checkbox-\d+-input/);
expect(secondId).toMatch(/mat-checkbox-\d+-input/);
expect(firstId).not.toEqual(secondId);
});
});
describe('with ngModel', () => {
let checkboxDebugElement: DebugElement;
let checkboxNativeElement: HTMLElement;
let checkboxInstance: MatCheckbox;
let inputElement: HTMLInputElement;
beforeEach(() => {
fixture = TestBed.createComponent(CheckboxWithFormDirectives);
fixture.detectChanges();
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
checkboxInstance = checkboxDebugElement.componentInstance;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
});
it('should be in pristine, untouched, and valid states initially', fakeAsync(() => {
flushMicrotasks();
let checkboxElement = fixture.debugElement.query(By.directive(MatCheckbox));
let ngModel = checkboxElement.injector.get<NgModel>(NgModel);
expect(ngModel.valid).toBe(true);
expect(ngModel.pristine).toBe(true);
expect(ngModel.touched).toBe(false);
// TODO(jelbourn): test that `touched` and `pristine` state are modified appropriately.
// This is currently blocked on issues with async() and fakeAsync().
}));
it('should toggle checked state on click', () => {
expect(checkboxInstance.checked).toBe(false);
inputElement.click();
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(true);
inputElement.click();
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
});
});
describe('with required ngModel', () => {
let checkboxInstance: MatCheckbox;
let inputElement: HTMLInputElement;
let testComponent: CheckboxWithNgModel;
beforeEach(() => {
fixture = TestBed.createComponent(CheckboxWithNgModel);
fixture.detectChanges();
let checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
let checkboxNativeElement = checkboxDebugElement.nativeElement;
testComponent = fixture.debugElement.componentInstance;
checkboxInstance = checkboxDebugElement.componentInstance;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
});
it('should validate with RequiredTrue validator', () => {
let checkboxElement = fixture.debugElement.query(By.directive(MatCheckbox));
let ngModel = checkboxElement.injector.get<NgModel>(NgModel);
testComponent.isRequired = true;
inputElement.click();
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(true);
expect(ngModel.valid).toBe(true);
inputElement.click();
fixture.detectChanges();
expect(checkboxInstance.checked).toBe(false);
expect(ngModel.valid).toBe(false);
});
});
describe('with name attribute', () => {
beforeEach(() => {
fixture = TestBed.createComponent(CheckboxWithNameAttribute);
fixture.detectChanges();
});
it('should forward name value to input element', () => {
let checkboxElement = fixture.debugElement.query(By.directive(MatCheckbox));
let inputElement = <HTMLInputElement> checkboxElement.nativeElement.querySelector('input');
expect(inputElement.getAttribute('name')).toBe('test-name');
});
});
describe('with form control', () => {
let checkboxDebugElement: DebugElement;
let checkboxInstance: MatCheckbox;
let testComponent: CheckboxWithFormControl;
let inputElement: HTMLInputElement;
beforeEach(() => {
fixture = TestBed.createComponent(CheckboxWithFormControl);
fixture.detectChanges();
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox));
checkboxInstance = checkboxDebugElement.componentInstance;
testComponent = fixture.debugElement.componentInstance;
inputElement = <HTMLInputElement>checkboxDebugElement.nativeElement.querySelector('input');
});
it('should toggle the disabled state', () => {
expect(checkboxInstance.disabled).toBe(false);
testComponent.formControl.disable();
fixture.detectChanges();
expect(checkboxInstance.disabled).toBe(true);
expect(inputElement.disabled).toBe(true);
testComponent.formControl.enable();
fixture.detectChanges();
expect(checkboxInstance.disabled).toBe(false);
expect(inputElement.disabled).toBe(false);
});
});
describe('without label', () => {
let testComponent: CheckboxWithoutLabel;
let checkboxElement: HTMLElement;
let checkboxInnerContainer: HTMLElement;
beforeEach(() => {
fixture = TestBed.createComponent(CheckboxWithoutLabel);
const checkboxDebugEl = fixture.debugElement.query(By.directive(MatCheckbox));
testComponent = fixture.componentInstance;
checkboxElement = checkboxDebugEl.nativeElement;
checkboxInnerContainer = checkboxDebugEl
.query(By.css('.mat-checkbox-inner-container')).nativeElement;
});
it('should remove margin for checkbox without a label', () => {
fixture.detectChanges();
expect(checkboxInnerContainer.classList)
.toContain('mat-checkbox-inner-container-no-side-margin');
});
it('should not remove margin if initial label is set through binding', async(() => {
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', async(() => {
fixture.detectChanges();
expect(checkboxInnerContainer.classList)
.toContain('mat-checkbox-inner-container-no-side-margin');
testComponent.label = 'Some content';
fixture.detectChanges();
// 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();
expect(checkboxInnerContainer.classList)
.not.toContain('mat-checkbox-inner-container-no-side-margin');
}, 1);
}));
it('should not add the "name" attribute if it is not passed in', () => {
fixture.detectChanges();
expect(checkboxInnerContainer.querySelector('input')!.hasAttribute('name')).toBe(false);
});
it('should not add the "value" attribute if it is not passed in', () => {
fixture.detectChanges();
expect(checkboxInnerContainer.querySelector('input')!.hasAttribute('value')).toBe(false);
});
});
});
/** Simple component for testing a single checkbox. */
({
template: `
<div (click)="parentElementClicked = true" (keyup)="parentElementKeyedUp = true">
<mat-checkbox
[id]="checkboxId"
[required]="isRequired"
[labelPosition]="labelPos"
[checked]="isChecked"
[(indeterminate)]="isIndeterminate"
[disabled]="isDisabled"
[color]="checkboxColor"
[disableRipple]="disableRipple"
[value]="checkboxValue"
(click)="onCheckboxClick($event)"
(change)="onCheckboxChange($event)">
Simple checkbox
</mat-checkbox>
</div>`
})
class SingleCheckbox {
labelPos: 'before' | 'after' = 'after';
isChecked: boolean = false;
isRequired: boolean = false;
isIndeterminate: boolean = false;
isDisabled: boolean = false;
disableRipple: boolean = false;
parentElementClicked: boolean = false;
parentElementKeyedUp: boolean = false;
checkboxId: string | null = 'simple-check';
checkboxColor: string = 'primary';
checkboxValue: string = 'single_checkbox';
onCheckboxClick: (event?: Event) => void = () => {};
onCheckboxChange: (event?: MatCheckboxChange) => void = () => {};
}
/** Simple component for testing an MatCheckbox with ngModel in a form. */
({
template: `
<form>
<mat-checkbox name="cb" [(ngModel)]="isGood">Be good</mat-checkbox>
</form>
`,
})
class CheckboxWithFormDirectives {
isGood: boolean = false;
}
/** Simple component for testing an MatCheckbox with required ngModel. */
({
template: `<mat-checkbox [required]="isRequired" [(ngModel)]="isGood">Be good</mat-checkbox>`,
})
class CheckboxWithNgModel {
isGood: boolean = false;
isRequired: boolean = true;
}
/** Simple test component with multiple checkboxes. */
(({
template: `
<mat-checkbox>Option 1</mat-checkbox>
<mat-checkbox>Option 2</mat-checkbox>
`
}))
class MultipleCheckboxes { }
/** Simple test component with tabIndex */
({
template: `
<mat-checkbox
[tabIndex]="customTabIndex"
[disabled]="isDisabled">
</mat-checkbox>`,
})
class CheckboxWithTabIndex {
customTabIndex: number = 7;
isDisabled: boolean = false;
}
/** Simple test component with an aria-label set. */
({
template: `<mat-checkbox aria-label="Super effective"></mat-checkbox>`
})
class CheckboxWithAriaLabel { }
/** Simple test component with an aria-label set. */
({
template: `<mat-checkbox aria-labelledby="some-id"></mat-checkbox>`
})
class CheckboxWithAriaLabelledby {}
/** Simple test component with name attribute */
({
template: `<mat-checkbox name="test-name"></mat-checkbox>`
})
class CheckboxWithNameAttribute {}
/** Simple test component with change event */
({
template: `<mat-checkbox (change)="lastEvent = $event"></mat-checkbox>`
})
class CheckboxWithChangeEvent {
lastEvent: MatCheckboxChange;
}
/** Test component with reactive forms */
({
template: `<mat-checkbox [formControl]="formControl"></mat-checkbox>`
})
class CheckboxWithFormControl {
formControl = new FormControl();
}
/** Test component without label */
({
template: `<mat-checkbox>{{ label }}</mat-checkbox>`
})
class CheckboxWithoutLabel {
label: string;
}
/** Test component with the native tabindex attribute. */
({
template: `<mat-checkbox tabindex="5"></mat-checkbox>`
})
class CheckboxWithTabindexAttr {}