1412 lines
50 KiB
TypeScript
1412 lines
50 KiB
TypeScript
import {dispatchFakeEvent} from '@angular/cdk/testing/private';
|
|
import {ChangeDetectionStrategy, Component, DebugElement, Type} from '@angular/core';
|
|
import {ComponentFixture, TestBed, fakeAsync, flush, flushMicrotasks} from '@angular/core/testing';
|
|
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
|
|
import {ThemePalette} from '@angular/material/core';
|
|
import {By} from '@angular/platform-browser';
|
|
import {
|
|
MAT_CHECKBOX_DEFAULT_OPTIONS,
|
|
MatCheckbox,
|
|
MatCheckboxChange,
|
|
MatCheckboxDefaultOptions,
|
|
MatCheckboxModule,
|
|
} from './index';
|
|
|
|
describe('MatCheckbox', () => {
|
|
let fixture: ComponentFixture<any>;
|
|
|
|
function createComponent<T>(componentType: Type<T>) {
|
|
TestBed.configureTestingModule({
|
|
imports: [MatCheckboxModule, FormsModule, ReactiveFormsModule, componentType],
|
|
});
|
|
|
|
return TestBed.createComponent<T>(componentType);
|
|
}
|
|
|
|
describe('basic behaviors', () => {
|
|
let checkboxDebugElement: DebugElement;
|
|
let checkboxNativeElement: HTMLElement;
|
|
let checkboxInstance: MatCheckbox;
|
|
let testComponent: SingleCheckbox;
|
|
let inputElement: HTMLInputElement;
|
|
let labelElement: HTMLLabelElement;
|
|
let checkboxElement: HTMLElement;
|
|
|
|
beforeEach(() => {
|
|
fixture = 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');
|
|
checkboxElement = <HTMLLabelElement>checkboxNativeElement.querySelector('.mdc-checkbox');
|
|
});
|
|
|
|
it('should add and remove the checked state', fakeAsync(() => {
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
expect(inputElement.checked).toBe(false);
|
|
|
|
testComponent.isChecked = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxInstance.checked).toBe(true);
|
|
expect(inputElement.checked).toBe(true);
|
|
expect(inputElement.hasAttribute('aria-checked'))
|
|
.withContext('Expect aria-checked attribute to not be used')
|
|
.toBe(false);
|
|
|
|
testComponent.isChecked = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
expect(inputElement.checked).toBe(false);
|
|
}));
|
|
|
|
it('should hide the internal SVG', () => {
|
|
const svg = checkboxNativeElement.querySelector('svg')!;
|
|
expect(svg.getAttribute('aria-hidden')).toBe('true');
|
|
});
|
|
|
|
it('should toggle checkbox ripple disabledness correctly', fakeAsync(() => {
|
|
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
|
|
|
|
testComponent.isDisabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
dispatchFakeEvent(checkboxElement, 'mousedown');
|
|
dispatchFakeEvent(checkboxElement, 'mouseup');
|
|
checkboxElement.click();
|
|
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(0);
|
|
|
|
flush();
|
|
testComponent.isDisabled = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
dispatchFakeEvent(checkboxElement, 'mousedown');
|
|
dispatchFakeEvent(checkboxElement, 'mouseup');
|
|
checkboxElement.click();
|
|
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(1);
|
|
|
|
flush();
|
|
}));
|
|
|
|
it('should add and remove indeterminate state', fakeAsync(() => {
|
|
expect(inputElement.checked).toBe(false);
|
|
expect(inputElement.indeterminate).toBe(false);
|
|
|
|
testComponent.isIndeterminate = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
expect(inputElement.indeterminate).toBe(true);
|
|
|
|
testComponent.isIndeterminate = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
expect(inputElement.indeterminate).toBe(false);
|
|
}));
|
|
|
|
it('should set indeterminate to false when input clicked', fakeAsync(() => {
|
|
testComponent.isIndeterminate = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
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.
|
|
flush();
|
|
|
|
// 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.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxInstance.indeterminate).toBe(true);
|
|
expect(inputElement.indeterminate).toBe(true);
|
|
expect(inputElement.checked).toBe(true);
|
|
expect(testComponent.isIndeterminate).toBe(true);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
|
|
// Flush the microtasks because the forms module updates the model state asynchronously.
|
|
flush();
|
|
|
|
// 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', fakeAsync(() => {
|
|
testComponent.isIndeterminate = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.indeterminate).toBe(true);
|
|
expect(inputElement.indeterminate).toBe(true);
|
|
expect(testComponent.isIndeterminate).toBe(true);
|
|
|
|
testComponent.isChecked = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
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.changeDetectorRef.markForCheck();
|
|
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', fakeAsync(() => {
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
|
|
labelElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(true);
|
|
|
|
labelElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
}));
|
|
|
|
it('should change from indeterminate to checked on click', fakeAsync(() => {
|
|
testComponent.isChecked = false;
|
|
testComponent.isIndeterminate = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
expect(checkboxInstance.indeterminate).toBe(true);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(true);
|
|
expect(checkboxInstance.indeterminate).toBe(false);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
expect(checkboxInstance.indeterminate).toBe(false);
|
|
}));
|
|
|
|
it('should add and remove disabled state', fakeAsync(() => {
|
|
expect(checkboxInstance.disabled).toBe(false);
|
|
expect(inputElement.tabIndex).toBe(0);
|
|
expect(inputElement.disabled).toBe(false);
|
|
|
|
testComponent.isDisabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxInstance.disabled).toBe(true);
|
|
expect(inputElement.disabled).toBe(true);
|
|
|
|
testComponent.isDisabled = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxInstance.disabled).toBe(false);
|
|
expect(inputElement.tabIndex).toBe(0);
|
|
expect(inputElement.disabled).toBe(false);
|
|
}));
|
|
|
|
it('should not toggle `checked` state upon interation while disabled', () => {
|
|
testComponent.isDisabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
checkboxNativeElement.click();
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
});
|
|
|
|
it('should overwrite indeterminate state when clicked', fakeAsync(() => {
|
|
testComponent.isIndeterminate = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
|
|
// Flush the microtasks because the indeterminate state will be updated in the next tick.
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(true);
|
|
expect(checkboxInstance.indeterminate).toBe(false);
|
|
}));
|
|
|
|
it('should preserve the user-provided id', fakeAsync(() => {
|
|
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', fakeAsync(() => {
|
|
testComponent.checkboxId = null;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxInstance.inputId).toMatch(/mat-mdc-checkbox-\w+\d+/);
|
|
expect(inputElement.id).toBe(checkboxInstance.inputId);
|
|
}));
|
|
|
|
it('should project the checkbox content into the label element', fakeAsync(() => {
|
|
let label = <HTMLLabelElement>checkboxNativeElement.querySelector('label');
|
|
expect(label.textContent!.trim()).toBe('Simple checkbox');
|
|
}));
|
|
|
|
it('should make the host element a tab stop', fakeAsync(() => {
|
|
expect(inputElement.tabIndex).toBe(0);
|
|
}));
|
|
|
|
it('should add a css class to position the label before the checkbox', fakeAsync(() => {
|
|
testComponent.labelPos = 'before';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxNativeElement.querySelector('.mdc-form-field')!.classList).toContain(
|
|
'mdc-form-field--align-end',
|
|
);
|
|
}));
|
|
|
|
it('should trigger the click once when clicking on the <input/>', fakeAsync(() => {
|
|
spyOn(testComponent, 'onCheckboxClick');
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(true);
|
|
expect(testComponent.onCheckboxClick).toHaveBeenCalledTimes(1);
|
|
}));
|
|
|
|
it('should trigger the click event once when clicking on the label', fakeAsync(() => {
|
|
// 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);
|
|
|
|
labelElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(true);
|
|
expect(testComponent.onCheckboxClick).toHaveBeenCalledTimes(1);
|
|
}));
|
|
|
|
it('should trigger a change event when the native input does', fakeAsync(() => {
|
|
spyOn(testComponent, 'onCheckboxChange');
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
|
|
labelElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(true);
|
|
expect(testComponent.onCheckboxChange).toHaveBeenCalledTimes(1);
|
|
}));
|
|
|
|
it('should not trigger the change event by changing the native value', fakeAsync(() => {
|
|
spyOn(testComponent, 'onCheckboxChange');
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
|
|
testComponent.isChecked = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(true);
|
|
expect(testComponent.onCheckboxChange).not.toHaveBeenCalled();
|
|
}));
|
|
|
|
it('should keep the view in sync if the `checked` value changes inside the `change` listener', fakeAsync(() => {
|
|
spyOn(testComponent, 'onCheckboxChange').and.callFake(() => {
|
|
checkboxInstance.checked = false;
|
|
});
|
|
|
|
labelElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
}));
|
|
|
|
it('should forward the required attribute', fakeAsync(() => {
|
|
testComponent.isRequired = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputElement.required).toBe(true);
|
|
|
|
testComponent.isRequired = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputElement.required).toBe(false);
|
|
}));
|
|
|
|
it('should focus on underlying input element when focus() is called', fakeAsync(() => {
|
|
expect(document.activeElement).not.toBe(inputElement);
|
|
|
|
checkboxInstance.focus();
|
|
fixture.detectChanges();
|
|
|
|
expect(document.activeElement).toBe(inputElement);
|
|
}));
|
|
|
|
it('should focus underlying input element when the touch target is clicked', fakeAsync(() => {
|
|
const touchTarget = checkboxElement.querySelector(
|
|
'.mat-mdc-checkbox-touch-target',
|
|
) as HTMLElement;
|
|
|
|
expect(document.activeElement).not.toBe(inputElement);
|
|
|
|
touchTarget.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(document.activeElement).toBe(inputElement);
|
|
}));
|
|
|
|
it('should forward the value to input element', fakeAsync(() => {
|
|
testComponent.checkboxValue = 'basic_checkbox';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputElement.value).toBe('basic_checkbox');
|
|
}));
|
|
|
|
it('should remove the SVG checkmark from the tab order', fakeAsync(() => {
|
|
expect(checkboxNativeElement.querySelector('svg')!.getAttribute('focusable')).toBe('false');
|
|
}));
|
|
|
|
it('should be able to mark a checkbox as disabled while keeping it interactive', fakeAsync(() => {
|
|
testComponent.isDisabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxNativeElement.classList).not.toContain(
|
|
'mat-mdc-checkbox-disabled-interactive',
|
|
);
|
|
expect(inputElement.hasAttribute('aria-disabled')).toBe(false);
|
|
expect(inputElement.tabIndex).toBe(-1);
|
|
expect(inputElement.disabled).toBe(true);
|
|
|
|
testComponent.disabledInteractive = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxNativeElement.classList).toContain('mat-mdc-checkbox-disabled-interactive');
|
|
expect(inputElement.getAttribute('aria-disabled')).toBe('true');
|
|
expect(inputElement.tabIndex).toBe(0);
|
|
expect(inputElement.disabled).toBe(false);
|
|
}));
|
|
|
|
it('should not change the checked state if disabled and interactive', fakeAsync(() => {
|
|
testComponent.isDisabled = testComponent.disabledInteractive = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
}));
|
|
|
|
describe('ripple elements', () => {
|
|
it('should show ripples on label mousedown', fakeAsync(() => {
|
|
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
|
|
|
|
expect(checkboxNativeElement.querySelector(rippleSelector)).toBeFalsy();
|
|
|
|
dispatchFakeEvent(checkboxElement, 'mousedown');
|
|
dispatchFakeEvent(checkboxElement, 'mouseup');
|
|
checkboxElement.click();
|
|
|
|
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(1);
|
|
|
|
flush();
|
|
}));
|
|
|
|
it('should not show ripples when disabled', fakeAsync(() => {
|
|
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
|
|
testComponent.isDisabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
dispatchFakeEvent(checkboxElement, 'mousedown');
|
|
dispatchFakeEvent(checkboxElement, 'mouseup');
|
|
checkboxElement.click();
|
|
|
|
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(0);
|
|
|
|
flush();
|
|
testComponent.isDisabled = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
dispatchFakeEvent(checkboxElement, 'mousedown');
|
|
dispatchFakeEvent(checkboxElement, 'mouseup');
|
|
checkboxElement.click();
|
|
|
|
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(1);
|
|
|
|
flush();
|
|
}));
|
|
|
|
it('should remove ripple if matRippleDisabled input is set', fakeAsync(() => {
|
|
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
|
|
testComponent.disableRipple = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
dispatchFakeEvent(checkboxElement, 'mousedown');
|
|
dispatchFakeEvent(checkboxElement, 'mouseup');
|
|
checkboxElement.click();
|
|
|
|
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(0);
|
|
|
|
flush();
|
|
testComponent.disableRipple = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
dispatchFakeEvent(checkboxElement, 'mousedown');
|
|
dispatchFakeEvent(checkboxElement, 'mouseup');
|
|
checkboxElement.click();
|
|
|
|
expect(checkboxNativeElement.querySelectorAll(rippleSelector).length).toBe(1);
|
|
|
|
flush();
|
|
}));
|
|
});
|
|
|
|
describe('color behaviour', () => {
|
|
it('should apply class based on color attribute', fakeAsync(() => {
|
|
testComponent.checkboxColor = 'primary';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(checkboxNativeElement.classList.contains('mat-primary')).toBe(true);
|
|
|
|
testComponent.checkboxColor = 'accent';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(checkboxNativeElement.classList.contains('mat-accent')).toBe(true);
|
|
}));
|
|
|
|
it('should not clear previous defined classes', fakeAsync(() => {
|
|
checkboxNativeElement.classList.add('custom-class');
|
|
|
|
testComponent.checkboxColor = 'primary';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxNativeElement.classList.contains('mat-primary')).toBe(true);
|
|
expect(checkboxNativeElement.classList.contains('custom-class')).toBe(true);
|
|
|
|
testComponent.checkboxColor = 'accent';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
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);
|
|
}));
|
|
|
|
it('should default to accent if no color is passed in', fakeAsync(() => {
|
|
testComponent.checkboxColor = undefined;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(checkboxNativeElement.classList).toContain('mat-accent');
|
|
}));
|
|
});
|
|
|
|
describe(`when MAT_CHECKBOX_CLICK_ACTION is 'check'`, () => {
|
|
beforeEach(() => {
|
|
TestBed.resetTestingModule();
|
|
TestBed.configureTestingModule({
|
|
imports: [MatCheckboxModule, FormsModule, ReactiveFormsModule, SingleCheckbox],
|
|
providers: [{provide: MAT_CHECKBOX_DEFAULT_OPTIONS, useValue: {clickAction: 'check'}}],
|
|
});
|
|
|
|
fixture = 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;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(true);
|
|
expect(inputElement.indeterminate).toBe(true);
|
|
}));
|
|
});
|
|
|
|
describe(`when MAT_CHECKBOX_CLICK_ACTION is 'noop'`, () => {
|
|
beforeEach(() => {
|
|
TestBed.resetTestingModule();
|
|
TestBed.configureTestingModule({
|
|
imports: [MatCheckboxModule, FormsModule, ReactiveFormsModule, SingleCheckbox],
|
|
providers: [{provide: MAT_CHECKBOX_DEFAULT_OPTIONS, useValue: {clickAction: 'noop'}}],
|
|
});
|
|
|
|
fixture = 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;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
expect(inputElement.indeterminate).toBe(true);
|
|
}));
|
|
|
|
it(`should not change 'checked' or 'indeterminate' on click if noop is set`, fakeAsync(() => {
|
|
testComponent.isChecked = true;
|
|
testComponent.isIndeterminate = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(true);
|
|
expect(inputElement.indeterminate).toBe(true);
|
|
|
|
testComponent.isChecked = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(inputElement.checked).toBe(false);
|
|
expect(inputElement.indeterminate)
|
|
.withContext('indeterminate should not change')
|
|
.toBe(true);
|
|
}));
|
|
});
|
|
|
|
it('should have a focus indicator', () => {
|
|
const checkboxRippleNativeElement = checkboxNativeElement.querySelector(
|
|
'.mat-mdc-checkbox-ripple',
|
|
)!;
|
|
|
|
expect(checkboxRippleNativeElement.classList.contains('mat-focus-indicator')).toBe(true);
|
|
});
|
|
});
|
|
|
|
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 = 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', fakeAsync(() => {
|
|
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();
|
|
flush();
|
|
|
|
expect(changeSpy).toHaveBeenCalledTimes(1);
|
|
}));
|
|
|
|
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();
|
|
|
|
// 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('aria handling', () => {
|
|
it('should use the provided aria-label', fakeAsync(() => {
|
|
fixture = createComponent(CheckboxWithAriaLabel);
|
|
const checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
const checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
const inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-label')).toBe('Super effective');
|
|
}));
|
|
|
|
it('should not set the aria-label attribute if no value is provided', fakeAsync(() => {
|
|
fixture = createComponent(SingleCheckbox);
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.querySelector('input').hasAttribute('aria-label')).toBe(false);
|
|
}));
|
|
|
|
it('should use the provided aria-labelledby', fakeAsync(() => {
|
|
fixture = createComponent(CheckboxWithAriaLabelledby);
|
|
const checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
const checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
const 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', fakeAsync(() => {
|
|
fixture = createComponent(SingleCheckbox);
|
|
const checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
const checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
const inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-labelledby')).toBe(null);
|
|
}));
|
|
|
|
it('should clear the static aria attributes from the host node', () => {
|
|
fixture = createComponent(CheckboxWithStaticAriaAttributes);
|
|
const checkbox = fixture.debugElement.query(By.directive(MatCheckbox))!.nativeElement;
|
|
fixture.detectChanges();
|
|
|
|
expect(checkbox.hasAttribute('aria')).toBe(false);
|
|
expect(checkbox.hasAttribute('aria-labelledby')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('with provided aria-describedby ', () => {
|
|
let checkboxDebugElement: DebugElement;
|
|
let checkboxNativeElement: HTMLElement;
|
|
let inputElement: HTMLInputElement;
|
|
|
|
it('should use the provided aria-describedby', () => {
|
|
fixture = createComponent(CheckboxWithAriaDescribedby);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-describedby')).toBe('some-id');
|
|
});
|
|
|
|
it('should not assign aria-describedby if none is provided', () => {
|
|
fixture = createComponent(SingleCheckbox);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-describedby')).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('with provided aria-expanded', () => {
|
|
let checkboxDebugElement: DebugElement;
|
|
let checkboxNativeElement: HTMLElement;
|
|
let inputElement: HTMLInputElement;
|
|
|
|
it('should use the provided postive aria-expanded', () => {
|
|
fixture = createComponent(CheckboxWithPositiveAriaExpanded);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-expanded')).toBe('true');
|
|
});
|
|
|
|
it('should use the provided negative aria-expanded', () => {
|
|
fixture = createComponent(CheckboxWithNegativeAriaExpanded);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-expanded')).toBe('false');
|
|
});
|
|
|
|
it('should not assign aria-expanded if none is provided', () => {
|
|
fixture = createComponent(SingleCheckbox);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-expanded')).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('with provided aria-controls', () => {
|
|
let checkboxDebugElement: DebugElement;
|
|
let checkboxNativeElement: HTMLElement;
|
|
let inputElement: HTMLInputElement;
|
|
|
|
it('should use the provided aria-controls', () => {
|
|
fixture = createComponent(CheckboxWithAriaControls);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-controls')).toBe('some-id');
|
|
});
|
|
|
|
it('should not assign aria-controls if none is provided', () => {
|
|
fixture = createComponent(SingleCheckbox);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-controls')).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('with provided aria-owns', () => {
|
|
let checkboxDebugElement: DebugElement;
|
|
let checkboxNativeElement: HTMLElement;
|
|
let inputElement: HTMLInputElement;
|
|
|
|
it('should use the provided aria-owns', () => {
|
|
fixture = createComponent(CheckboxWithAriaOwns);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-owns')).toBe('some-id');
|
|
});
|
|
|
|
it('should not assign aria-owns if none is provided', () => {
|
|
fixture = createComponent(SingleCheckbox);
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
|
|
fixture.detectChanges();
|
|
expect(inputElement.getAttribute('aria-owns')).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('with provided tabIndex', () => {
|
|
let checkboxDebugElement: DebugElement;
|
|
let checkboxNativeElement: HTMLElement;
|
|
let testComponent: CheckboxWithTabIndex;
|
|
let inputElement: HTMLInputElement;
|
|
|
|
beforeEach(() => {
|
|
fixture = createComponent(CheckboxWithTabIndex);
|
|
fixture.detectChanges();
|
|
|
|
testComponent = fixture.debugElement.componentInstance;
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
});
|
|
|
|
it('should preserve any given tabIndex', fakeAsync(() => {
|
|
expect(inputElement.tabIndex).toBe(7);
|
|
}));
|
|
|
|
it('should preserve given tabIndex when the checkbox is disabled then enabled', fakeAsync(() => {
|
|
testComponent.isDisabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
testComponent.customTabIndex = 13;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
testComponent.isDisabled = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputElement.tabIndex).toBe(13);
|
|
}));
|
|
});
|
|
|
|
describe('with native tabindex attribute', () => {
|
|
it('should properly detect native tabindex attribute', fakeAsync(() => {
|
|
fixture = createComponent(CheckboxWithTabindexAttr);
|
|
fixture.detectChanges();
|
|
|
|
const checkbox = fixture.debugElement.query(By.directive(MatCheckbox))!
|
|
.componentInstance as MatCheckbox;
|
|
|
|
expect(checkbox.tabIndex)
|
|
.withContext('Expected tabIndex property to have been set based on the native attribute')
|
|
.toBe(5);
|
|
}));
|
|
|
|
it('should clear the tabindex attribute from the host element', fakeAsync(() => {
|
|
fixture = createComponent(CheckboxWithTabindexAttr);
|
|
fixture.detectChanges();
|
|
|
|
const checkbox = fixture.debugElement.query(By.directive(MatCheckbox))!.nativeElement;
|
|
expect(checkbox.getAttribute('tabindex')).toBeFalsy();
|
|
}));
|
|
});
|
|
|
|
describe('with multiple checkboxes', () => {
|
|
beforeEach(() => {
|
|
fixture = createComponent(MultipleCheckboxes);
|
|
fixture.detectChanges();
|
|
});
|
|
|
|
it('should assign a unique id to each checkbox', fakeAsync(() => {
|
|
let [firstId, secondId] = fixture.debugElement
|
|
.queryAll(By.directive(MatCheckbox))
|
|
.map(debugElement => debugElement.nativeElement.querySelector('input').id);
|
|
|
|
expect(firstId).toMatch(/mat-mdc-checkbox-\w+\d+-input/);
|
|
expect(secondId).toMatch(/mat-mdc-checkbox-\w+\d+-input/);
|
|
expect(firstId).not.toEqual(secondId);
|
|
}));
|
|
});
|
|
|
|
describe('with ngModel', () => {
|
|
let checkboxDebugElement: DebugElement;
|
|
let checkboxNativeElement: HTMLElement;
|
|
let checkboxInstance: MatCheckbox;
|
|
let inputElement: HTMLInputElement;
|
|
let ngModel: NgModel;
|
|
|
|
beforeEach(() => {
|
|
fixture = createComponent(CheckboxWithNgModel);
|
|
|
|
fixture.componentInstance.isRequired = false;
|
|
fixture.detectChanges();
|
|
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
checkboxInstance = checkboxDebugElement.componentInstance;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
ngModel = checkboxDebugElement.injector.get<NgModel>(NgModel);
|
|
});
|
|
|
|
it('should be pristine, untouched, and valid initially', fakeAsync(() => {
|
|
expect(ngModel.valid).toBe(true);
|
|
expect(ngModel.pristine).toBe(true);
|
|
expect(ngModel.touched).toBe(false);
|
|
}));
|
|
|
|
it('should have correct control states after interaction', fakeAsync(() => {
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
|
|
// Flush the timeout that is being created whenever a `click` event has been fired by
|
|
// the underlying input.
|
|
flush();
|
|
|
|
// After the value change through interaction, the control should be dirty, but remain
|
|
// untouched as long as the focus is still on the underlying input.
|
|
expect(ngModel.pristine).toBe(false);
|
|
expect(ngModel.touched).toBe(false);
|
|
|
|
// If the input element loses focus, the control should remain dirty but should
|
|
// also turn touched.
|
|
dispatchFakeEvent(inputElement, 'blur');
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(ngModel.pristine).toBe(false);
|
|
expect(ngModel.touched).toBe(true);
|
|
}));
|
|
|
|
it('should mark the element as touched on blur when inside an OnPush parent', fakeAsync(() => {
|
|
fixture.destroy();
|
|
TestBed.resetTestingModule();
|
|
fixture = createComponent(CheckboxWithNgModelAndOnPush);
|
|
fixture.detectChanges();
|
|
|
|
checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
|
checkboxInstance = checkboxDebugElement.componentInstance;
|
|
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
|
|
ngModel = checkboxDebugElement.injector.get<NgModel>(NgModel);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxNativeElement.classList).not.toContain('ng-touched');
|
|
|
|
dispatchFakeEvent(inputElement, 'blur');
|
|
fixture.detectChanges();
|
|
flushMicrotasks();
|
|
fixture.detectChanges();
|
|
|
|
expect(checkboxNativeElement.classList).toContain('ng-touched');
|
|
}));
|
|
|
|
it('should not throw an error when disabling while focused', fakeAsync(() => {
|
|
expect(() => {
|
|
// Focus the input element because after disabling, the `blur` event should automatically
|
|
// fire and not result in a changed after checked exception. Related: #12323
|
|
inputElement.focus();
|
|
|
|
fixture.componentInstance.isDisabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
flush();
|
|
}).not.toThrow();
|
|
}));
|
|
|
|
it('should toggle checked state on click', fakeAsync(() => {
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(true);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
}));
|
|
|
|
it('should validate with RequiredTrue validator', fakeAsync(() => {
|
|
fixture.componentInstance.isRequired = true;
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(true);
|
|
expect(ngModel.valid).toBe(true);
|
|
|
|
inputElement.click();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(checkboxInstance.checked).toBe(false);
|
|
expect(ngModel.valid).toBe(false);
|
|
}));
|
|
|
|
it('should update the ngModel value when using the `toggle` method', fakeAsync(() => {
|
|
const checkbox = fixture.debugElement.query(By.directive(MatCheckbox)).componentInstance;
|
|
|
|
expect(fixture.componentInstance.isGood).toBe(false);
|
|
|
|
checkbox.toggle();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.componentInstance.isGood).toBe(true);
|
|
}));
|
|
});
|
|
|
|
describe('with name attribute', () => {
|
|
beforeEach(() => {
|
|
fixture = createComponent(CheckboxWithNameAttribute);
|
|
fixture.detectChanges();
|
|
});
|
|
|
|
it('should forward name value to input element', fakeAsync(() => {
|
|
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 = 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', fakeAsync(() => {
|
|
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 checkboxInnerContainer: HTMLElement;
|
|
|
|
beforeEach(() => {
|
|
fixture = createComponent(CheckboxWithoutLabel);
|
|
const checkboxDebugEl = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
checkboxInnerContainer = checkboxDebugEl.query(By.css('.mdc-form-field'))!.nativeElement;
|
|
});
|
|
|
|
it('should not add the "name" attribute if it is not passed in', fakeAsync(() => {
|
|
fixture.detectChanges();
|
|
expect(checkboxInnerContainer.querySelector('input')!.hasAttribute('name')).toBe(false);
|
|
}));
|
|
|
|
it('should not add the "value" attribute if it is not passed in', fakeAsync(() => {
|
|
fixture.detectChanges();
|
|
expect(checkboxInnerContainer.querySelector('input')!.hasAttribute('value')).toBe(false);
|
|
}));
|
|
});
|
|
});
|
|
|
|
describe('MatCheckboxDefaultOptions', () => {
|
|
describe('when MAT_CHECKBOX_DEFAULT_OPTIONS overridden', () => {
|
|
function configure(defaults: MatCheckboxDefaultOptions) {
|
|
TestBed.configureTestingModule({
|
|
imports: [MatCheckboxModule, FormsModule, SingleCheckbox, SingleCheckbox],
|
|
providers: [{provide: MAT_CHECKBOX_DEFAULT_OPTIONS, useValue: defaults}],
|
|
});
|
|
}
|
|
|
|
it('should override default color in component', () => {
|
|
configure({color: 'primary'});
|
|
const fixture: ComponentFixture<SingleCheckbox> = TestBed.createComponent(SingleCheckbox);
|
|
fixture.detectChanges();
|
|
const checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
expect(checkboxDebugElement.nativeElement.classList).toContain('mat-primary');
|
|
});
|
|
|
|
it('should not override explicit input bindings', () => {
|
|
configure({color: 'primary'});
|
|
const fixture: ComponentFixture<SingleCheckbox> = TestBed.createComponent(SingleCheckbox);
|
|
fixture.componentInstance.checkboxColor = 'warn';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
const checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
expect(checkboxDebugElement.nativeElement.classList).not.toContain('mat-primary');
|
|
expect(checkboxDebugElement.nativeElement.classList).toContain('mat-warn');
|
|
expect(checkboxDebugElement.nativeElement.classList).toContain('mat-warn');
|
|
});
|
|
|
|
it('should default to accent if config does not specify color', () => {
|
|
configure({clickAction: 'noop'});
|
|
const fixture: ComponentFixture<SingleCheckbox> = TestBed.createComponent(SingleCheckbox);
|
|
fixture.componentInstance.checkboxColor = undefined;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
const checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!;
|
|
expect(checkboxDebugElement.nativeElement.classList).toContain('mat-accent');
|
|
});
|
|
});
|
|
});
|
|
|
|
/** Simple component for testing a single checkbox. */
|
|
@Component({
|
|
template: `
|
|
<div (click)="parentElementClicked = true" (keyup)="parentElementKeyedUp = true" (click)="onCheckboxClick($event)">
|
|
<mat-checkbox
|
|
[id]="checkboxId"
|
|
[required]="isRequired"
|
|
[labelPosition]="labelPos"
|
|
[checked]="isChecked"
|
|
[(indeterminate)]="isIndeterminate"
|
|
[disabled]="isDisabled"
|
|
[color]="checkboxColor"
|
|
[disableRipple]="disableRipple"
|
|
[value]="checkboxValue"
|
|
[disabledInteractive]="disabledInteractive"
|
|
(change)="onCheckboxChange($event)">
|
|
Simple checkbox
|
|
</mat-checkbox>
|
|
</div>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class SingleCheckbox {
|
|
labelPos: 'before' | 'after' = 'after';
|
|
isChecked = false;
|
|
isRequired = false;
|
|
isIndeterminate = false;
|
|
isDisabled = false;
|
|
disableRipple = false;
|
|
parentElementClicked = false;
|
|
parentElementKeyedUp = false;
|
|
disabledInteractive = false;
|
|
checkboxId: string | null = 'simple-check';
|
|
checkboxColor: ThemePalette = 'primary';
|
|
checkboxValue: string = 'single_checkbox';
|
|
|
|
onCheckboxClick: (event?: Event) => void = () => {};
|
|
onCheckboxChange: (event?: MatCheckboxChange) => void = () => {};
|
|
}
|
|
|
|
/** Simple component for testing an MatCheckbox with required ngModel. */
|
|
@Component({
|
|
template: `<mat-checkbox [required]="isRequired" [(ngModel)]="isGood"
|
|
[disabled]="isDisabled">Be good</mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox, FormsModule],
|
|
})
|
|
class CheckboxWithNgModel {
|
|
isGood = false;
|
|
isRequired = true;
|
|
isDisabled = false;
|
|
}
|
|
|
|
@Component({
|
|
template: `<mat-checkbox [required]="isRequired" [(ngModel)]="isGood">Be good</mat-checkbox>`,
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
standalone: true,
|
|
imports: [MatCheckbox, FormsModule],
|
|
})
|
|
class CheckboxWithNgModelAndOnPush extends CheckboxWithNgModel {}
|
|
|
|
/** Simple test component with multiple checkboxes. */
|
|
@Component({
|
|
template: `
|
|
<mat-checkbox>Option 1</mat-checkbox>
|
|
<mat-checkbox>Option 2</mat-checkbox>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class MultipleCheckboxes {}
|
|
|
|
/** Simple test component with tabIndex */
|
|
@Component({
|
|
template: `
|
|
<mat-checkbox
|
|
[tabIndex]="customTabIndex"
|
|
[disabled]="isDisabled">
|
|
</mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithTabIndex {
|
|
customTabIndex: number = 7;
|
|
isDisabled: boolean = false;
|
|
}
|
|
|
|
/** Simple test component with an aria-label set. */
|
|
@Component({
|
|
template: `<mat-checkbox aria-label="Super effective"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithAriaLabel {}
|
|
|
|
/** Simple test component with an aria-label set. */
|
|
@Component({
|
|
template: `<mat-checkbox aria-labelledby="some-id"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithAriaLabelledby {}
|
|
|
|
/** Simple test component with an aria-describedby set. */
|
|
@Component({
|
|
template: `<mat-checkbox aria-describedby="some-id"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithAriaDescribedby {}
|
|
|
|
/** Simple test component with an aria-expanded set with true. */
|
|
@Component({
|
|
template: `<mat-checkbox aria-expanded="true"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithPositiveAriaExpanded {}
|
|
|
|
/** Simple test component with an aria-expanded set with false. */
|
|
@Component({
|
|
template: `<mat-checkbox aria-expanded="false"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithNegativeAriaExpanded {}
|
|
|
|
/** Simple test component with an aria-controls set. */
|
|
@Component({
|
|
template: `<mat-checkbox aria-controls="some-id"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithAriaControls {}
|
|
|
|
/** Simple test component with an aria-owns set. */
|
|
@Component({
|
|
template: `<mat-checkbox aria-owns="some-id"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithAriaOwns {}
|
|
|
|
/** Simple test component with name attribute */
|
|
@Component({
|
|
template: `<mat-checkbox name="test-name"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithNameAttribute {}
|
|
|
|
/** Simple test component with change event */
|
|
@Component({
|
|
template: `<mat-checkbox (change)="lastEvent = $event"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithChangeEvent {
|
|
lastEvent: MatCheckboxChange;
|
|
}
|
|
|
|
/** Test component with reactive forms */
|
|
@Component({
|
|
template: `<mat-checkbox [formControl]="formControl"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox, ReactiveFormsModule],
|
|
})
|
|
class CheckboxWithFormControl {
|
|
formControl = new FormControl(false);
|
|
}
|
|
|
|
/** Test component without label */
|
|
@Component({
|
|
template: `<mat-checkbox>{{ label }}</mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithoutLabel {
|
|
label: string;
|
|
}
|
|
|
|
/** Test component with the native tabindex attribute. */
|
|
@Component({
|
|
template: `<mat-checkbox tabindex="5"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithTabindexAttr {}
|
|
|
|
@Component({
|
|
template: `<mat-checkbox aria-label="Checkbox" aria-labelledby="something"></mat-checkbox>`,
|
|
standalone: true,
|
|
imports: [MatCheckbox],
|
|
})
|
|
class CheckboxWithStaticAriaAttributes {}
|