2336 lines
77 KiB
TypeScript
2336 lines
77 KiB
TypeScript
import {getSupportedInputTypes} from '@angular/cdk/platform';
|
|
import {dispatchFakeEvent, wrappedErrorMessage} from '@angular/cdk/testing/private';
|
|
import {
|
|
ChangeDetectionStrategy,
|
|
Component,
|
|
Directive,
|
|
ErrorHandler,
|
|
Provider,
|
|
Type,
|
|
ViewChild,
|
|
} from '@angular/core';
|
|
import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing';
|
|
import {
|
|
FormControl,
|
|
FormGroup,
|
|
FormGroupDirective,
|
|
FormsModule,
|
|
NgForm,
|
|
ReactiveFormsModule,
|
|
Validators,
|
|
} from '@angular/forms';
|
|
import {
|
|
ErrorStateMatcher,
|
|
ShowOnDirtyErrorStateMatcher,
|
|
ThemePalette,
|
|
} from '@angular/material/core';
|
|
import {
|
|
FloatLabelType,
|
|
MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
MatFormField,
|
|
MatFormFieldAppearance,
|
|
MatFormFieldModule,
|
|
SubscriptSizing,
|
|
getMatFormFieldDuplicatedHintError,
|
|
getMatFormFieldMissingControlError,
|
|
} from '@angular/material/form-field';
|
|
import {MatIconModule} from '@angular/material/icon';
|
|
import {By} from '@angular/platform-browser';
|
|
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
|
|
import {MAT_INPUT_VALUE_ACCESSOR, MatInput, MatInputModule} from './index';
|
|
|
|
describe('MatMdcInput without forms', () => {
|
|
it('should default to floating labels', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithLabel);
|
|
fixture.detectChanges();
|
|
|
|
let formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField.floatLabel)
|
|
.withContext('Expected MatInput to set floatingLabel to auto by default.')
|
|
.toBe('auto');
|
|
}));
|
|
|
|
it('should default to global floating label type', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithLabel, [
|
|
{
|
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
useValue: {floatLabel: 'always'},
|
|
},
|
|
]);
|
|
fixture.detectChanges();
|
|
|
|
let formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField.floatLabel)
|
|
.withContext('Expected MatInput to set floatingLabel to always from global option.')
|
|
.toBe('always');
|
|
}));
|
|
|
|
it('should not be treated as empty if type is date', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputDateTestController);
|
|
fixture.detectChanges();
|
|
|
|
if (getSupportedInputTypes().has('date')) {
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField).toBeTruthy();
|
|
expect(formField!._control.empty).toBe(false);
|
|
}
|
|
}));
|
|
|
|
it('should be treated as empty if type is date on unsupported browser', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputDateTestController);
|
|
fixture.detectChanges();
|
|
|
|
if (!getSupportedInputTypes().has('date')) {
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField).toBeTruthy();
|
|
expect(formField!._control.empty).toBe(true);
|
|
}
|
|
}));
|
|
|
|
it('should treat text input type as empty at init', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputTextTestController);
|
|
fixture.detectChanges();
|
|
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField).toBeTruthy();
|
|
expect(formField!._control.empty).toBe(true);
|
|
}));
|
|
|
|
it('should treat password input type as empty at init', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputPasswordTestController);
|
|
fixture.detectChanges();
|
|
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField).toBeTruthy();
|
|
expect(formField!._control.empty).toBe(true);
|
|
}));
|
|
|
|
it('should treat number input type as empty at init', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputNumberTestController);
|
|
fixture.detectChanges();
|
|
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField).toBeTruthy();
|
|
expect(formField!._control.empty).toBe(true);
|
|
}));
|
|
|
|
it('should not be empty after input entered', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputTextTestController);
|
|
fixture.detectChanges();
|
|
|
|
let inputEl = fixture.debugElement.query(By.css('input'))!;
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField).toBeTruthy();
|
|
expect(formField!._control.empty).toBe(true);
|
|
|
|
inputEl.nativeElement.value = 'hello';
|
|
// Simulate input event.
|
|
inputEl.triggerEventHandler('input', {target: inputEl.nativeElement});
|
|
fixture.detectChanges();
|
|
expect(formField!._control.empty).toBe(false);
|
|
}));
|
|
|
|
it('should update the placeholder when input entered', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithStaticLabel);
|
|
fixture.detectChanges();
|
|
|
|
const inputEl = fixture.debugElement.query(By.css('input'))!;
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
|
|
expect(formField).toBeTruthy();
|
|
expect(formField._control.empty).toBe(true);
|
|
expect(formField._shouldLabelFloat()).toBe(false);
|
|
|
|
// Update the value of the input.
|
|
inputEl.nativeElement.value = 'Text';
|
|
|
|
// Fake behavior of the `(input)` event which should trigger a change detection.
|
|
fixture.detectChanges();
|
|
|
|
expect(formField._control.empty).toBe(false);
|
|
// should not float label if there is no label
|
|
expect(formField._shouldLabelFloat()).toBe(false);
|
|
}));
|
|
|
|
it('should not be empty when the value set before view init', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithValueBinding);
|
|
fixture.detectChanges();
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField._control.empty).toBe(false);
|
|
|
|
fixture.componentInstance.value = '';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(formField._control.empty).toBe(true);
|
|
}));
|
|
|
|
it('should add id', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputTextTestController);
|
|
fixture.detectChanges();
|
|
|
|
const inputElement: HTMLInputElement = fixture.debugElement.query(
|
|
By.css('input'),
|
|
)!.nativeElement;
|
|
const labelElement: HTMLInputElement = fixture.debugElement.query(
|
|
By.css('label'),
|
|
)!.nativeElement;
|
|
|
|
expect(inputElement.id).toBeTruthy();
|
|
expect(inputElement.id).toEqual(labelElement.getAttribute('for')!);
|
|
}));
|
|
|
|
it('should add aria-required reflecting the required state', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithRequired);
|
|
fixture.detectChanges();
|
|
|
|
const inputElement: HTMLInputElement = fixture.debugElement.query(
|
|
By.css('input'),
|
|
)!.nativeElement;
|
|
|
|
expect(inputElement.getAttribute('aria-required'))
|
|
.withContext('Expected aria-required to reflect required state of false')
|
|
.toBe('false');
|
|
|
|
fixture.componentInstance.required = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputElement.getAttribute('aria-required'))
|
|
.withContext('Expected aria-required to reflect required state of true')
|
|
.toBe('true');
|
|
}));
|
|
|
|
it('should not overwrite existing id', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithId);
|
|
fixture.detectChanges();
|
|
|
|
const inputElement: HTMLInputElement = fixture.debugElement.query(
|
|
By.css('input'),
|
|
)!.nativeElement;
|
|
const labelElement: HTMLInputElement = fixture.debugElement.query(
|
|
By.css('label'),
|
|
)!.nativeElement;
|
|
|
|
expect(inputElement.id).toBe('test-id');
|
|
expect(labelElement.getAttribute('for')).toBe('test-id');
|
|
}));
|
|
|
|
it("validates there's only one hint label per side", () => {
|
|
let fixture = createComponent(MatInputInvalidHintTestController);
|
|
expect(() => fixture.detectChanges()).toThrowError(
|
|
wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start')),
|
|
);
|
|
});
|
|
|
|
it("validates there's only one hint label per side (attribute)", () => {
|
|
let fixture = createComponent(MatInputInvalidHint2TestController);
|
|
|
|
expect(() => fixture.detectChanges()).toThrowError(
|
|
wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start')),
|
|
);
|
|
});
|
|
|
|
it('validates that matInput child is present', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputMissingMatInputTestController);
|
|
|
|
expect(() => fixture.detectChanges()).toThrowError(
|
|
wrappedErrorMessage(getMatFormFieldMissingControlError()),
|
|
);
|
|
}));
|
|
|
|
it('validates that matInput child is present after initialization', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithNgIf);
|
|
|
|
expect(() => fixture.detectChanges()).not.toThrowError(
|
|
wrappedErrorMessage(getMatFormFieldMissingControlError()),
|
|
);
|
|
|
|
fixture.componentInstance.renderInput = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
|
|
expect(() => fixture.detectChanges()).toThrowError(
|
|
wrappedErrorMessage(getMatFormFieldMissingControlError()),
|
|
);
|
|
}));
|
|
|
|
it('validates the type', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputInvalidTypeTestController);
|
|
|
|
// Technically this throws during the OnChanges detection phase,
|
|
// so the error is really a ChangeDetectionError and it becomes
|
|
// hard to build a full exception to compare with.
|
|
// We just check for any exception in this case.
|
|
expect(() => fixture.detectChanges())
|
|
.toThrow
|
|
/* new MatInputUnsupportedTypeError('file') */
|
|
();
|
|
}));
|
|
|
|
it('supports hint labels attribute', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputHintLabelTestController);
|
|
fixture.detectChanges();
|
|
|
|
// If the hint label is empty, expect no label.
|
|
expect(fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))).toBeNull();
|
|
|
|
fixture.componentInstance.label = 'label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))).not.toBeNull();
|
|
}));
|
|
|
|
it('sets an id on hint labels', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputHintLabelTestController);
|
|
|
|
fixture.componentInstance.label = 'label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
let hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement;
|
|
|
|
expect(hint.getAttribute('id')).toBeTruthy();
|
|
}));
|
|
|
|
it('supports hint labels elements', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputHintLabel2TestController);
|
|
fixture.detectChanges();
|
|
|
|
// In this case, we should have an empty <mat-hint>.
|
|
let el = fixture.debugElement.query(By.css('mat-hint'))!.nativeElement;
|
|
expect(el.textContent).toBeFalsy();
|
|
|
|
fixture.componentInstance.label = 'label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
el = fixture.debugElement.query(By.css('mat-hint'))!.nativeElement;
|
|
expect(el.textContent).toBe('label');
|
|
}));
|
|
|
|
it('sets an id on the hint element', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputHintLabel2TestController);
|
|
|
|
fixture.componentInstance.label = 'label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
let hint = fixture.debugElement.query(By.css('mat-hint'))!.nativeElement;
|
|
|
|
expect(hint.getAttribute('id')).toBeTruthy();
|
|
}));
|
|
|
|
it('supports label required star', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputLabelRequiredTestComponent);
|
|
fixture.detectChanges();
|
|
|
|
const label = fixture.debugElement.query(By.css('label'))!;
|
|
expect(label).not.toBeNull();
|
|
expect(label.nativeElement.textContent).toBe('hello');
|
|
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
|
|
}));
|
|
|
|
it('should show the required star when using a FormControl', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithRequiredFormControl);
|
|
fixture.detectChanges();
|
|
|
|
const label = fixture.debugElement.query(By.css('label'))!;
|
|
expect(label).not.toBeNull();
|
|
expect(label.nativeElement.textContent).toBe('Hello');
|
|
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
|
|
}));
|
|
|
|
it('should not hide the required star if input is disabled', () => {
|
|
const fixture = createComponent(MatInputLabelRequiredTestComponent);
|
|
|
|
fixture.componentInstance.disabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const label = fixture.debugElement.query(By.css('label'))!;
|
|
expect(label).not.toBeNull();
|
|
expect(label.nativeElement.textContent).toBe('hello');
|
|
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
|
|
});
|
|
|
|
it('hide label required star when set to hide the required marker', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputLabelRequiredTestComponent);
|
|
fixture.detectChanges();
|
|
|
|
const label = fixture.debugElement.query(By.css('label'))!;
|
|
expect(label).not.toBeNull();
|
|
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeTruthy();
|
|
expect(label.nativeElement.textContent).toBe('hello');
|
|
|
|
fixture.componentInstance.hideRequiredMarker = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(label.nativeElement.querySelector('.mat-mdc-form-field-required-marker')).toBeFalsy();
|
|
expect(label.nativeElement.textContent).toBe('hello');
|
|
}));
|
|
|
|
it('supports the disabled attribute as binding', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithDisabled);
|
|
fixture.detectChanges();
|
|
|
|
const wrapperEl = fixture.debugElement.query(
|
|
By.css('.mat-mdc-text-field-wrapper'),
|
|
)!.nativeElement;
|
|
const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(wrapperEl.classList.contains('mdc-text-field--disabled'))
|
|
.withContext(`Expected form field not to start out disabled.`)
|
|
.toBe(false);
|
|
expect(inputEl.disabled).toBe(false);
|
|
|
|
fixture.componentInstance.disabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(wrapperEl.classList.contains('mdc-text-field--disabled'))
|
|
.withContext(`Expected form field to look disabled after property is set.`)
|
|
.toBe(true);
|
|
expect(inputEl.disabled).toBe(true);
|
|
}));
|
|
|
|
it('should be able to set an input as being disabled and interactive', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithDisabled);
|
|
fixture.componentInstance.disabled = true;
|
|
fixture.detectChanges();
|
|
|
|
const input = fixture.nativeElement.querySelector('input') as HTMLInputElement;
|
|
expect(input.disabled).toBe(true);
|
|
expect(input.readOnly).toBe(false);
|
|
expect(input.hasAttribute('aria-disabled')).toBe(false);
|
|
expect(input.classList).not.toContain('mat-mdc-input-disabled-interactive');
|
|
|
|
fixture.componentInstance.disabledInteractive = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(input.disabled).toBe(false);
|
|
expect(input.readOnly).toBe(true);
|
|
expect(input.getAttribute('aria-disabled')).toBe('true');
|
|
expect(input.classList).toContain('mat-mdc-input-disabled-interactive');
|
|
}));
|
|
|
|
it('should not float the label when disabled and disabledInteractive are set', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputTextTestController);
|
|
fixture.componentInstance.disabled = fixture.componentInstance.disabledInteractive = true;
|
|
fixture.detectChanges();
|
|
|
|
const label = fixture.nativeElement.querySelector('label');
|
|
const input = fixture.debugElement
|
|
.query(By.directive(MatInput))!
|
|
.injector.get<MatInput>(MatInput);
|
|
|
|
expect(label.classList).not.toContain('mdc-floating-label--float-above');
|
|
|
|
// Call the focus handler directly to avoid flakyness where
|
|
// browsers don't focus elements if the window is minimized.
|
|
input._focusChanged(true);
|
|
fixture.detectChanges();
|
|
|
|
expect(label.classList).not.toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should float the label when disabledInteractive is set and the input has a value', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithDynamicLabel);
|
|
fixture.componentInstance.shouldFloat = 'auto';
|
|
fixture.componentInstance.disabled = fixture.componentInstance.disabledInteractive = true;
|
|
fixture.detectChanges();
|
|
|
|
const input = fixture.nativeElement.querySelector('input');
|
|
const label = fixture.nativeElement.querySelector('label');
|
|
|
|
expect(label.classList).not.toContain('mdc-floating-label--float-above');
|
|
|
|
input.value = 'Text';
|
|
dispatchFakeEvent(input, 'input');
|
|
fixture.detectChanges();
|
|
|
|
expect(label.classList).toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('supports the disabled attribute as binding for select', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelect);
|
|
fixture.detectChanges();
|
|
|
|
const wrapperEl = fixture.debugElement.query(
|
|
By.css('.mat-mdc-text-field-wrapper'),
|
|
)!.nativeElement;
|
|
const selectEl = fixture.debugElement.query(By.css('select'))!.nativeElement;
|
|
|
|
expect(wrapperEl.classList.contains('mdc-text-field--disabled'))
|
|
.withContext(`Expected form field not to start out disabled.`)
|
|
.toBe(false);
|
|
expect(selectEl.disabled).toBe(false);
|
|
|
|
fixture.componentInstance.disabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(wrapperEl.classList.contains('mdc-text-field--disabled'))
|
|
.withContext(`Expected form field to look disabled after property is set.`)
|
|
.toBe(true);
|
|
expect(selectEl.disabled).toBe(true);
|
|
}));
|
|
|
|
it('should add a class to the form-field if animations are disabled', () => {
|
|
configureTestingModule(MatInputWithId, {animations: false});
|
|
const fixture = TestBed.createComponent(MatInputWithId);
|
|
fixture.detectChanges();
|
|
|
|
const formFieldEl = fixture.nativeElement.querySelector('.mat-mdc-form-field');
|
|
expect(formFieldEl.classList).toContain('mat-form-field-no-animations');
|
|
});
|
|
|
|
it('should add a class to the form field if it has a native select', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelect);
|
|
fixture.detectChanges();
|
|
|
|
const formField = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
|
|
expect(formField.classList).toContain('mat-mdc-form-field-type-mat-native-select');
|
|
}));
|
|
|
|
it('supports the required attribute as binding', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithRequired);
|
|
fixture.detectChanges();
|
|
|
|
let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(inputEl.required).toBe(false);
|
|
|
|
fixture.componentInstance.required = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputEl.required).toBe(true);
|
|
}));
|
|
|
|
it('supports the required attribute as binding for select', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelect);
|
|
fixture.detectChanges();
|
|
|
|
const selectEl = fixture.debugElement.query(By.css('select'))!.nativeElement;
|
|
|
|
expect(selectEl.required).toBe(false);
|
|
|
|
fixture.componentInstance.required = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(selectEl.required).toBe(true);
|
|
}));
|
|
|
|
it('supports the type attribute as binding', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithType);
|
|
fixture.detectChanges();
|
|
|
|
let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(inputEl.type).toBe('text');
|
|
|
|
fixture.componentInstance.type = 'password';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(inputEl.type).toBe('password');
|
|
}));
|
|
|
|
it('supports textarea', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputTextareaWithBindings);
|
|
fixture.detectChanges();
|
|
|
|
const textarea: HTMLTextAreaElement = fixture.nativeElement.querySelector('textarea');
|
|
expect(textarea).not.toBeNull();
|
|
}));
|
|
|
|
it('supports select', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelect);
|
|
fixture.detectChanges();
|
|
|
|
const nativeSelect: HTMLTextAreaElement = fixture.nativeElement.querySelector('select');
|
|
expect(nativeSelect).not.toBeNull();
|
|
}));
|
|
|
|
it('sets the aria-describedby when a hintLabel is set', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputHintLabelTestController);
|
|
|
|
fixture.componentInstance.label = 'label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement;
|
|
const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
const hintId = hint.getAttribute('id');
|
|
|
|
expect(input.getAttribute('aria-describedby')).toBe(`initial ${hintId}`);
|
|
}));
|
|
|
|
it('supports user binding to aria-describedby', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithSubscriptAndAriaDescribedBy);
|
|
|
|
fixture.componentInstance.label = 'label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement;
|
|
const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
const hintId = hint.getAttribute('id');
|
|
|
|
expect(input.getAttribute('aria-describedby')).toBe(hintId);
|
|
|
|
fixture.componentInstance.userDescribedByValue = 'custom-error custom-error-two';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(input.getAttribute('aria-describedby')).toBe(`custom-error custom-error-two ${hintId}`);
|
|
|
|
fixture.componentInstance.userDescribedByValue = 'custom-error';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(input.getAttribute('aria-describedby')).toBe(`custom-error ${hintId}`);
|
|
|
|
fixture.componentInstance.showError = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.componentInstance.formControl.markAsTouched();
|
|
fixture.componentInstance.formControl.setErrors({invalid: true});
|
|
fixture.detectChanges();
|
|
expect(input.getAttribute('aria-describedby')).toMatch(/^custom-error mat-mdc-error-\w+\d+$/);
|
|
|
|
fixture.componentInstance.label = '';
|
|
fixture.componentInstance.userDescribedByValue = '';
|
|
fixture.componentInstance.showError = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(input.hasAttribute('aria-describedby')).toBe(false);
|
|
}));
|
|
|
|
it('sets the aria-describedby to the id of the mat-hint', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputHintLabel2TestController);
|
|
|
|
fixture.componentInstance.label = 'label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
let hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement;
|
|
let input = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(input.getAttribute('aria-describedby')).toBe(hint.getAttribute('id'));
|
|
}));
|
|
|
|
it('sets the aria-describedby with multiple mat-hint instances', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputMultipleHintTestController);
|
|
|
|
fixture.componentInstance.startId = 'start';
|
|
fixture.componentInstance.endId = 'end';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
let input = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(input.getAttribute('aria-describedby')).toBe('start end');
|
|
}));
|
|
|
|
it('should preserve aria-describedby set directly in the DOM', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputHintLabel2TestController);
|
|
const input = fixture.nativeElement.querySelector('input');
|
|
input.setAttribute('aria-describedby', 'custom');
|
|
fixture.componentInstance.label = 'label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
const hint = fixture.nativeElement.querySelector('.mat-mdc-form-field-hint');
|
|
|
|
expect(input.getAttribute('aria-describedby')).toBe(`${hint.getAttribute('id')} custom`);
|
|
}));
|
|
|
|
it('should set a class on the hint element based on its alignment', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputMultipleHintTestController);
|
|
|
|
fixture.componentInstance.startId = 'start';
|
|
fixture.componentInstance.endId = 'end';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const start = fixture.nativeElement.querySelector('#start');
|
|
const end = fixture.nativeElement.querySelector('#end');
|
|
|
|
expect(start.classList).not.toContain('mat-mdc-form-field-hint-end');
|
|
expect(end.classList).toContain('mat-mdc-form-field-hint-end');
|
|
}));
|
|
|
|
it('sets the aria-describedby when a hintLabel is set, in addition to a mat-hint', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputMultipleHintMixedTestController);
|
|
|
|
fixture.detectChanges();
|
|
|
|
let hintLabel = fixture.debugElement.query(
|
|
By.css('.mat-mdc-form-field-hint:not(.mat-mdc-form-field-hint-end)'),
|
|
)!.nativeElement;
|
|
let endLabel = fixture.debugElement.query(
|
|
By.css('.mat-mdc-form-field-hint.mat-mdc-form-field-hint-end'),
|
|
)!.nativeElement;
|
|
let input = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
let ariaValue = input.getAttribute('aria-describedby');
|
|
|
|
expect(ariaValue).toBe(`${hintLabel.getAttribute('id')} ${endLabel.getAttribute('id')}`);
|
|
}));
|
|
|
|
it('should float when floatLabel is set to default and text is entered', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithDynamicLabel);
|
|
fixture.detectChanges();
|
|
|
|
let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
let labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
|
|
expect(labelEl.classList).toContain('mdc-floating-label--float-above');
|
|
|
|
fixture.componentInstance.shouldFloat = 'auto';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(labelEl.classList).not.toContain('mdc-floating-label--float-above');
|
|
|
|
// Update the value of the input.
|
|
inputEl.value = 'Text';
|
|
|
|
// Fake behavior of the `(input)` event which should trigger a change detection.
|
|
dispatchFakeEvent(inputEl, 'input');
|
|
fixture.detectChanges();
|
|
|
|
expect(labelEl.classList).toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should always float the label when floatLabel is set to always', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithDynamicLabel);
|
|
fixture.detectChanges();
|
|
|
|
let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
let labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
|
|
expect(labelEl.classList).toContain('mdc-floating-label--float-above');
|
|
|
|
fixture.detectChanges();
|
|
|
|
// Update the value of the input.
|
|
inputEl.value = 'Text';
|
|
|
|
// Fake behavior of the `(input)` event which should trigger a change detection.
|
|
fixture.detectChanges();
|
|
|
|
expect(labelEl.classList).toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should float labels when select has value', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelect);
|
|
fixture.detectChanges();
|
|
|
|
const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
expect(labelEl.classList).toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should mark a multi-select as being inline', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelect);
|
|
fixture.detectChanges();
|
|
|
|
const select: HTMLSelectElement = fixture.nativeElement.querySelector('select');
|
|
|
|
expect(select.classList).not.toContain('mat-mdc-native-select-inline');
|
|
|
|
select.multiple = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(select.classList).toContain('mat-mdc-native-select-inline');
|
|
}));
|
|
|
|
it('should mark a select with a size as being inline', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelect);
|
|
fixture.detectChanges();
|
|
|
|
const select: HTMLSelectElement = fixture.nativeElement.querySelector('select');
|
|
|
|
expect(select.classList).not.toContain('mat-mdc-native-select-inline');
|
|
|
|
select.size = 3;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(select.classList).toContain('mat-mdc-native-select-inline');
|
|
|
|
select.size = 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(select.classList).not.toContain('mat-mdc-native-select-inline');
|
|
}));
|
|
|
|
it('should not float the label if the selectedIndex is negative', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelect);
|
|
fixture.detectChanges();
|
|
|
|
const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
const selectEl: HTMLSelectElement = fixture.nativeElement.querySelector('select');
|
|
|
|
expect(labelEl.classList).toContain('mdc-floating-label--float-above');
|
|
|
|
selectEl.selectedIndex = -1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(labelEl.classList).not.toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should not float labels when select has no value, no option label, no option innerHtml', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelectWithNoLabelNoValue);
|
|
fixture.detectChanges();
|
|
|
|
const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
expect(labelEl.classList).not.toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should floating labels when select has no value but has option label', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelectWithLabel);
|
|
fixture.detectChanges();
|
|
|
|
const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
expect(labelEl.classList).toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should floating labels when select has no value but has option innerHTML', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelectWithInnerHtml);
|
|
fixture.detectChanges();
|
|
|
|
const labelEl = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
expect(labelEl.classList).toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should not throw if a native select does not have options', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputSelectWithoutOptions);
|
|
expect(() => fixture.detectChanges()).not.toThrow();
|
|
}));
|
|
|
|
it('should be able to toggle the floating label programmatically', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithId);
|
|
|
|
fixture.detectChanges();
|
|
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!;
|
|
const containerInstance = formField.componentInstance as MatFormField;
|
|
const label = formField.nativeElement.querySelector('label');
|
|
|
|
expect(containerInstance.floatLabel).toBe('auto');
|
|
expect(label.classList).not.toContain('mdc-floating-label--float-above');
|
|
|
|
fixture.componentInstance.floatLabel = 'always';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(containerInstance.floatLabel).toBe('always');
|
|
expect(label.classList).toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should not have prefix and suffix elements when none are specified', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithId);
|
|
fixture.detectChanges();
|
|
|
|
let prefixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-prefix'));
|
|
let suffixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-suffix'));
|
|
|
|
expect(prefixEl).toBeNull();
|
|
expect(suffixEl).toBeNull();
|
|
}));
|
|
|
|
it('should add prefix and suffix elements when specified', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithPrefixAndSuffix);
|
|
fixture.detectChanges();
|
|
|
|
const textPrefixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-text-prefix'))!;
|
|
const textSuffixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-text-suffix'))!;
|
|
const iconPrefixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-icon-prefix'))!;
|
|
const iconSuffixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-icon-suffix'))!;
|
|
|
|
expect(textPrefixEl).not.toBeNull();
|
|
expect(textSuffixEl).not.toBeNull();
|
|
expect(iconPrefixEl).not.toBeNull();
|
|
expect(iconSuffixEl).not.toBeNull();
|
|
expect(textPrefixEl.nativeElement.innerText.trim()).toEqual('Prefix');
|
|
expect(textSuffixEl.nativeElement.innerText.trim()).toEqual('Suffix');
|
|
expect(iconPrefixEl.nativeElement.innerText.trim()).toEqual('favorite');
|
|
expect(iconSuffixEl.nativeElement.innerText.trim()).toEqual('favorite');
|
|
}));
|
|
|
|
it('should allow ng-container as prefix and suffix', () => {
|
|
const fixture = createComponent(InputWithNgContainerPrefixAndSuffix);
|
|
fixture.detectChanges();
|
|
|
|
const textPrefixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-text-prefix'))!;
|
|
const textSuffixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-text-suffix'))!;
|
|
const iconPrefixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-icon-prefix'))!;
|
|
const iconSuffixEl = fixture.debugElement.query(By.css('.mat-mdc-form-field-icon-suffix'))!;
|
|
|
|
expect(textPrefixEl.nativeElement.innerText.trim()).toEqual('text-prefix');
|
|
expect(textSuffixEl.nativeElement.innerText.trim()).toEqual('text-suffix');
|
|
expect(iconPrefixEl.nativeElement.innerText.trim()).toEqual('icon-prefix');
|
|
expect(iconSuffixEl.nativeElement.innerText.trim()).toEqual('icon-suffix');
|
|
});
|
|
|
|
it('should update empty class when value changes programmatically and OnPush', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputOnPush);
|
|
fixture.detectChanges();
|
|
|
|
let component = fixture.componentInstance;
|
|
let label = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
|
|
expect(label.classList).not.toContain('mdc-floating-label--float-above');
|
|
|
|
component.formControl.setValue('something');
|
|
fixture.detectChanges();
|
|
|
|
expect(label.classList).toContain('mdc-floating-label--float-above');
|
|
}));
|
|
|
|
it('should set the focused class when the input is focused', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputTextTestController);
|
|
fixture.detectChanges();
|
|
|
|
let input = fixture.debugElement
|
|
.query(By.directive(MatInput))!
|
|
.injector.get<MatInput>(MatInput);
|
|
let container = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
|
|
// Call the focus handler directly to avoid flakyness where
|
|
// browsers don't focus elements if the window is minimized.
|
|
input._focusChanged(true);
|
|
fixture.detectChanges();
|
|
|
|
expect(container.classList).toContain('mat-focused');
|
|
}));
|
|
|
|
it('should remove the focused class if the input becomes disabled while focused', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputTextTestController);
|
|
fixture.detectChanges();
|
|
|
|
const input = fixture.debugElement
|
|
.query(By.directive(MatInput))!
|
|
.injector.get<MatInput>(MatInput);
|
|
const container = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
|
|
// Call the focus handler directly to avoid flakyness where
|
|
// browsers don't focus elements if the window is minimized.
|
|
input._focusChanged(true);
|
|
fixture.detectChanges();
|
|
|
|
expect(container.classList).toContain('mat-focused');
|
|
|
|
input.disabled = true;
|
|
fixture.detectChanges();
|
|
|
|
expect(container.classList).not.toContain('mat-focused');
|
|
}));
|
|
|
|
it('should only show the native control placeholder, when there is a label, on focus', () => {
|
|
const fixture = createComponent(MatInputWithLabelAndPlaceholder);
|
|
fixture.detectChanges();
|
|
|
|
const container = fixture.debugElement.query(By.css('mat-form-field'))!.nativeElement;
|
|
const label = fixture.debugElement.query(By.css('label'))!.nativeElement;
|
|
const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(container.classList).toContain('mat-form-field-hide-placeholder');
|
|
expect(label.textContent.trim()).toBe('Label');
|
|
|
|
input.value = 'Value';
|
|
dispatchFakeEvent(input, 'input');
|
|
fixture.detectChanges();
|
|
|
|
expect(container.classList).not.toContain('mat-form-field-hide-placeholder');
|
|
});
|
|
|
|
it('should always show the native control placeholder when floatLabel is set to "always"', () => {
|
|
const fixture = createComponent(MatInputWithLabelAndPlaceholder);
|
|
|
|
fixture.componentInstance.floatLabel = 'always';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const container = fixture.debugElement.query(By.css('mat-form-field'))!.nativeElement;
|
|
|
|
expect(container.classList).not.toContain('mat-form-field-hide-placeholder');
|
|
});
|
|
|
|
it('should not add the `placeholder` attribute if there is no placeholder', () => {
|
|
const fixture = createComponent(MatInputWithoutPlaceholder);
|
|
fixture.detectChanges();
|
|
const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(input.hasAttribute('placeholder')).toBe(false);
|
|
});
|
|
|
|
it('should not add the native select class if the control is not a native select', () => {
|
|
const fixture = createComponent(MatInputWithId);
|
|
fixture.detectChanges();
|
|
const formField = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
|
|
expect(formField.classList).not.toContain('mat-mdc-form-field-type-mat-native-select');
|
|
});
|
|
|
|
it('should preserve the native placeholder on a non-legacy appearance', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithLabelAndPlaceholder);
|
|
fixture.componentInstance.floatLabel = 'auto';
|
|
fixture.componentInstance.appearance = 'outline';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.querySelector('input').getAttribute('placeholder')).toBe(
|
|
'Placeholder',
|
|
);
|
|
}));
|
|
|
|
it(
|
|
'should use the native input value when determining whether ' +
|
|
'the element is empty with a custom accessor',
|
|
fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithCustomAccessor, [], [], [CustomMatInputAccessor]);
|
|
fixture.detectChanges();
|
|
let formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
|
|
expect(formField._control.empty).toBe(true);
|
|
|
|
fixture.nativeElement.querySelector('input').value = 'abc';
|
|
fixture.detectChanges();
|
|
|
|
expect(formField._control.empty).toBe(false);
|
|
}),
|
|
);
|
|
|
|
it('should default the form field color to primary', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithColor);
|
|
fixture.detectChanges();
|
|
|
|
const formField = fixture.nativeElement.querySelector('.mat-mdc-form-field');
|
|
expect(formField.classList).toContain('mat-primary');
|
|
}));
|
|
|
|
it('should be able to change the form field color', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithColor);
|
|
fixture.componentInstance.color = 'accent';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
const formField = fixture.nativeElement.querySelector('.mat-mdc-form-field');
|
|
|
|
expect(formField.classList).toContain('mat-accent');
|
|
|
|
fixture.componentInstance.color = 'warn';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(formField.classList).toContain('mat-warn');
|
|
}));
|
|
|
|
it('should set a class on the input depending on whether it is in a form field', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputInsideOutsideFormField);
|
|
fixture.detectChanges();
|
|
|
|
const inFormField = fixture.nativeElement.querySelector('.inside');
|
|
const outsideFormField = fixture.nativeElement.querySelector('.outside');
|
|
|
|
expect(inFormField.classList).toContain('mat-mdc-form-field-input-control');
|
|
expect(outsideFormField.classList).not.toContain('mat-mdc-form-field-input-control');
|
|
}));
|
|
});
|
|
|
|
describe('MatMdcInput with forms', () => {
|
|
describe('error messages', () => {
|
|
let fixture: ComponentFixture<MatInputWithFormErrorMessages>;
|
|
let testComponent: MatInputWithFormErrorMessages;
|
|
let containerEl: HTMLElement;
|
|
let inputEl: HTMLInputElement;
|
|
|
|
beforeEach(fakeAsync(() => {
|
|
fixture = createComponent(MatInputWithFormErrorMessages);
|
|
fixture.detectChanges();
|
|
testComponent = fixture.componentInstance;
|
|
containerEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
}));
|
|
|
|
it('should not show any errors if the user has not interacted', fakeAsync(() => {
|
|
expect(testComponent.formControl.untouched)
|
|
.withContext('Expected untouched form control')
|
|
.toBe(true);
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error message')
|
|
.toBe(0);
|
|
expect(inputEl.getAttribute('aria-invalid'))
|
|
.withContext('Expected aria-invalid to be set to "false".')
|
|
.toBe('false');
|
|
}));
|
|
|
|
it('should display an error message when the input is touched and invalid', fakeAsync(() => {
|
|
expect(testComponent.formControl.invalid)
|
|
.withContext('Expected form control to be invalid')
|
|
.toBe(true);
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error message')
|
|
.toBe(0);
|
|
|
|
inputEl.value = 'not valid';
|
|
testComponent.formControl.markAsTouched();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(containerEl.classList)
|
|
.withContext('Expected container to have the invalid CSS class.')
|
|
.toContain('mat-form-field-invalid');
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected one error message to have been rendered.')
|
|
.toBe(1);
|
|
expect(inputEl.getAttribute('aria-invalid'))
|
|
.withContext('Expected aria-invalid to be set to "true".')
|
|
.toBe('true');
|
|
}));
|
|
|
|
it('should not reset text-field validity if focus changes for an invalid input', fakeAsync(() => {
|
|
// Mark the control as touched, so that the form-field displays as invalid.
|
|
testComponent.formControl.markAsTouched();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
const wrapperEl = containerEl.querySelector('.mdc-text-field')!;
|
|
expect(wrapperEl.classList).toContain('mdc-text-field--invalid');
|
|
|
|
dispatchFakeEvent(inputEl, 'focus');
|
|
dispatchFakeEvent(inputEl, 'blur');
|
|
flush();
|
|
expect(wrapperEl.classList).toContain('mdc-text-field--invalid');
|
|
}));
|
|
|
|
it('should display an error message when the parent form is submitted', fakeAsync(() => {
|
|
expect(testComponent.form.submitted)
|
|
.withContext('Expected form not to have been submitted')
|
|
.toBe(false);
|
|
expect(testComponent.formControl.invalid)
|
|
.withContext('Expected form control to be invalid')
|
|
.toBe(true);
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error message')
|
|
.toBe(0);
|
|
|
|
inputEl.value = 'not valid';
|
|
dispatchFakeEvent(fixture.debugElement.query(By.css('form'))!.nativeElement, 'submit');
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(testComponent.form.submitted)
|
|
.withContext('Expected form to have been submitted')
|
|
.toBe(true);
|
|
expect(containerEl.classList)
|
|
.withContext('Expected container to have the invalid CSS class.')
|
|
.toContain('mat-form-field-invalid');
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected one error message to have been rendered.')
|
|
.toBe(1);
|
|
expect(inputEl.getAttribute('aria-invalid'))
|
|
.withContext('Expected aria-invalid to be set to "true".')
|
|
.toBe('true');
|
|
}));
|
|
|
|
it('should display an error message when the parent form group is submitted', fakeAsync(() => {
|
|
fixture.destroy();
|
|
TestBed.resetTestingModule();
|
|
|
|
let groupFixture = createComponent(MatInputWithFormGroupErrorMessages);
|
|
let component: MatInputWithFormGroupErrorMessages;
|
|
|
|
groupFixture.detectChanges();
|
|
component = groupFixture.componentInstance;
|
|
containerEl = groupFixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
inputEl = groupFixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(component.formGroup.invalid)
|
|
.withContext('Expected form control to be invalid')
|
|
.toBe(true);
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error message')
|
|
.toBe(0);
|
|
expect(inputEl.getAttribute('aria-invalid'))
|
|
.withContext('Expected aria-invalid to be set to "false".')
|
|
.toBe('false');
|
|
expect(component.formGroupDirective.submitted)
|
|
.withContext('Expected form not to have been submitted')
|
|
.toBe(false);
|
|
|
|
inputEl.value = 'not valid';
|
|
dispatchFakeEvent(groupFixture.debugElement.query(By.css('form'))!.nativeElement, 'submit');
|
|
groupFixture.detectChanges();
|
|
flush();
|
|
|
|
expect(component.formGroupDirective.submitted)
|
|
.withContext('Expected form to have been submitted')
|
|
.toBe(true);
|
|
expect(containerEl.classList)
|
|
.withContext('Expected container to have the invalid CSS class.')
|
|
.toContain('mat-form-field-invalid');
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected one error message to have been rendered.')
|
|
.toBe(1);
|
|
expect(inputEl.getAttribute('aria-invalid'))
|
|
.withContext('Expected aria-invalid to be set to "true".')
|
|
.toBe('true');
|
|
}));
|
|
|
|
it('should hide the errors and show the hints once the input becomes valid', fakeAsync(() => {
|
|
testComponent.formControl.markAsTouched();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(containerEl.classList)
|
|
.withContext('Expected container to have the invalid CSS class.')
|
|
.toContain('mat-form-field-invalid');
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected one error message to have been rendered.')
|
|
.toBe(1);
|
|
expect(containerEl.querySelectorAll('mat-hint').length)
|
|
.withContext('Expected no hints to be shown.')
|
|
.toBe(0);
|
|
|
|
testComponent.formControl.setValue('valid value');
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(containerEl.classList).not.toContain(
|
|
'mat-form-field-invalid',
|
|
'Expected container not to have the invalid class when valid.',
|
|
);
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error messages when the input is valid.')
|
|
.toBe(0);
|
|
expect(containerEl.querySelectorAll('mat-hint').length)
|
|
.withContext('Expected one hint to be shown once the input is valid.')
|
|
.toBe(1);
|
|
}));
|
|
|
|
it('should not hide the hint if there are no error messages', fakeAsync(() => {
|
|
testComponent.renderError = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(containerEl.querySelectorAll('mat-hint').length)
|
|
.withContext('Expected one hint to be shown on load.')
|
|
.toBe(1);
|
|
|
|
testComponent.formControl.markAsTouched();
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(containerEl.querySelectorAll('mat-hint').length)
|
|
.withContext('Expected one hint to still be shown.')
|
|
.toBe(1);
|
|
}));
|
|
|
|
it('should set the proper aria-live attribute on the error messages', fakeAsync(() => {
|
|
testComponent.formControl.markAsTouched();
|
|
fixture.detectChanges();
|
|
|
|
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
|
|
}));
|
|
|
|
it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => {
|
|
let hintId = fixture.debugElement
|
|
.query(By.css('.mat-mdc-form-field-hint'))!
|
|
.nativeElement.getAttribute('id');
|
|
let describedBy = inputEl.getAttribute('aria-describedby');
|
|
|
|
expect(hintId).withContext('hint should be shown').toBeTruthy();
|
|
expect(describedBy).toBe(hintId);
|
|
|
|
fixture.componentInstance.formControl.markAsTouched();
|
|
fixture.detectChanges();
|
|
|
|
let errorIds = fixture.debugElement
|
|
.queryAll(By.css('.mat-mdc-form-field-error'))
|
|
.map(el => el.nativeElement.getAttribute('id'))
|
|
.join(' ');
|
|
describedBy = inputEl.getAttribute('aria-describedby');
|
|
|
|
expect(errorIds).withContext('errors should be shown').toBeTruthy();
|
|
expect(describedBy).toBe(errorIds);
|
|
}));
|
|
|
|
it('should set `aria-invalid` to true if the input is empty', fakeAsync(() => {
|
|
// Submit the form since it's the one that triggers the default error state matcher.
|
|
dispatchFakeEvent(fixture.nativeElement.querySelector('form'), 'submit');
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
expect(testComponent.formControl.invalid)
|
|
.withContext('Expected form control to be invalid')
|
|
.toBe(true);
|
|
expect(inputEl.value).toBe('incorrect');
|
|
expect(inputEl.getAttribute('aria-invalid'))
|
|
.withContext('Expected aria-invalid to be set to "true"')
|
|
.toBe('true');
|
|
|
|
inputEl.value = 'not valid';
|
|
fixture.detectChanges();
|
|
|
|
expect(testComponent.formControl.invalid)
|
|
.withContext('Expected form control to be invalid')
|
|
.toBe(true);
|
|
expect(inputEl.getAttribute('aria-invalid'))
|
|
.withContext('Expected aria-invalid to be set to "true".')
|
|
.toBe('true');
|
|
}));
|
|
});
|
|
|
|
describe('custom error behavior', () => {
|
|
it('should display an error message when a custom error matcher returns true', fakeAsync(() => {
|
|
let fixture = createComponent(InputInFormGroup);
|
|
fixture.detectChanges();
|
|
|
|
let component = fixture.componentInstance;
|
|
let containerEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
|
|
const control = component.formGroup.get('name')!;
|
|
|
|
expect(control.invalid).withContext('Expected form control to be invalid').toBe(true);
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error messages')
|
|
.toBe(0);
|
|
|
|
control.markAsTouched();
|
|
fixture.detectChanges();
|
|
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error messages after being touched.')
|
|
.toBe(0);
|
|
|
|
component.errorState = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected one error messages to have been rendered.')
|
|
.toBe(1);
|
|
}));
|
|
|
|
it('should display an error message when global error matcher returns true', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithFormErrorMessages, [
|
|
{
|
|
provide: ErrorStateMatcher,
|
|
useValue: {isErrorState: () => true},
|
|
},
|
|
]);
|
|
|
|
fixture.detectChanges();
|
|
|
|
let containerEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
let testComponent = fixture.componentInstance;
|
|
|
|
// Expect the control to still be untouched but the error to show due to the global setting
|
|
// Expect the control to still be untouched but the error to show due to the global setting
|
|
expect(testComponent.formControl.untouched)
|
|
.withContext('Expected untouched form control')
|
|
.toBe(true);
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected an error message')
|
|
.toBe(1);
|
|
}));
|
|
|
|
it('should display an error message when using ShowOnDirtyErrorStateMatcher', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputWithFormErrorMessages, [
|
|
{
|
|
provide: ErrorStateMatcher,
|
|
useClass: ShowOnDirtyErrorStateMatcher,
|
|
},
|
|
]);
|
|
fixture.detectChanges();
|
|
|
|
let containerEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
let testComponent = fixture.componentInstance;
|
|
|
|
expect(testComponent.formControl.invalid)
|
|
.withContext('Expected form control to be invalid')
|
|
.toBe(true);
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error message')
|
|
.toBe(0);
|
|
|
|
testComponent.formControl.markAsTouched();
|
|
fixture.detectChanges();
|
|
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected no error messages when touched')
|
|
.toBe(0);
|
|
|
|
testComponent.formControl.markAsDirty();
|
|
fixture.detectChanges();
|
|
|
|
expect(containerEl.querySelectorAll('mat-error').length)
|
|
.withContext('Expected one error message when dirty')
|
|
.toBe(1);
|
|
}));
|
|
});
|
|
|
|
it('should update the value when using FormControl.setValue', () => {
|
|
let fixture = createComponent(MatInputWithFormControl);
|
|
fixture.detectChanges();
|
|
|
|
let input = fixture.debugElement
|
|
.query(By.directive(MatInput))!
|
|
.injector.get<MatInput>(MatInput);
|
|
|
|
expect(input.value).toBeFalsy();
|
|
|
|
fixture.componentInstance.formControl.setValue('something');
|
|
|
|
expect(input.value).toBe('something');
|
|
});
|
|
|
|
it('should display disabled styles when using FormControl.disable()', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithFormControl);
|
|
fixture.detectChanges();
|
|
|
|
const formFieldEl = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
|
|
const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;
|
|
|
|
expect(formFieldEl.classList).not.toContain(
|
|
'mat-form-field-disabled',
|
|
`Expected form field not to start out disabled.`,
|
|
);
|
|
expect(inputEl.disabled).toBe(false);
|
|
|
|
fixture.componentInstance.formControl.disable();
|
|
fixture.detectChanges();
|
|
|
|
expect(formFieldEl.classList)
|
|
.withContext(`Expected form field to look disabled after disable() is called.`)
|
|
.toContain('mat-form-field-disabled');
|
|
expect(inputEl.disabled).toBe(true);
|
|
}));
|
|
|
|
it('should not treat the number 0 as empty', fakeAsync(() => {
|
|
let fixture = createComponent(MatInputZeroTestController);
|
|
fixture.detectChanges();
|
|
flush();
|
|
|
|
fixture.detectChanges();
|
|
|
|
let formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
expect(formField).not.toBeNull();
|
|
expect(formField._control.empty).toBe(false);
|
|
}));
|
|
|
|
it('should update when the form field value is patched without emitting', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithFormControl);
|
|
fixture.detectChanges();
|
|
|
|
let formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
|
|
expect(formField._control.empty).toBe(true);
|
|
|
|
fixture.componentInstance.formControl.patchValue('value', {emitEvent: false});
|
|
fixture.detectChanges();
|
|
|
|
expect(formField._control.empty).toBe(false);
|
|
}));
|
|
|
|
it('should update notch size after changing appearance to outline', fakeAsync(() => {
|
|
const fixture = createComponent(MatInputWithAppearance);
|
|
fixture.detectChanges();
|
|
tick(16);
|
|
|
|
expect(fixture.nativeElement.querySelector('.mdc-notched-outline__notch')).toBe(null);
|
|
|
|
fixture.componentInstance.appearance = 'outline';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick(16);
|
|
|
|
let notch = fixture.nativeElement.querySelector('.mdc-notched-outline__notch')! as HTMLElement;
|
|
expect(notch.style.width).toBeFalsy();
|
|
|
|
fixture.nativeElement.querySelector('input')!.focus();
|
|
fixture.detectChanges();
|
|
tick(16);
|
|
|
|
expect(notch.style.width).toBeTruthy();
|
|
}));
|
|
});
|
|
|
|
describe('MatFormField default options', () => {
|
|
it('should be fill appearance if no default options provided', () => {
|
|
const fixture = createComponent(MatInputWithAppearance);
|
|
fixture.detectChanges();
|
|
expect(fixture.componentInstance.formField.appearance).toBe('fill');
|
|
});
|
|
|
|
it('should be fill appearance if empty default options provided', () => {
|
|
const fixture = createComponent(MatInputWithAppearance, [
|
|
{
|
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
useValue: {},
|
|
},
|
|
]);
|
|
|
|
fixture.detectChanges();
|
|
expect(fixture.componentInstance.formField.appearance).toBe('fill');
|
|
});
|
|
|
|
it('should be able to change the default appearance', () => {
|
|
const fixture = createComponent(MatInputWithAppearance, [
|
|
{
|
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
useValue: {appearance: 'outline'},
|
|
},
|
|
]);
|
|
fixture.detectChanges();
|
|
expect(fixture.componentInstance.formField.appearance).toBe('outline');
|
|
});
|
|
|
|
it('should default hideRequiredMarker to false', () => {
|
|
const fixture = createComponent(MatInputWithAppearance, [
|
|
{
|
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
useValue: {},
|
|
},
|
|
]);
|
|
|
|
fixture.detectChanges();
|
|
expect(fixture.componentInstance.formField.hideRequiredMarker).toBe(false);
|
|
});
|
|
|
|
it('should be able to change the default value of hideRequiredMarker and appearance', () => {
|
|
const fixture = createComponent(MatInputWithAppearance, [
|
|
{
|
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
useValue: {
|
|
hideRequiredMarker: true,
|
|
appearance: 'outline',
|
|
},
|
|
},
|
|
]);
|
|
|
|
fixture.detectChanges();
|
|
expect(fixture.componentInstance.formField.hideRequiredMarker).toBe(true);
|
|
expect(fixture.componentInstance.formField.appearance).toBe('outline');
|
|
});
|
|
|
|
it('should be able to change the default color', () => {
|
|
const fixture = createComponent(MatInputSimple, [
|
|
{
|
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
useValue: {color: 'accent'},
|
|
},
|
|
]);
|
|
fixture.detectChanges();
|
|
const formField = fixture.nativeElement.querySelector('.mat-mdc-form-field');
|
|
expect(formField.classList).toContain('mat-accent');
|
|
});
|
|
|
|
it('defaults subscriptSizing to false', () => {
|
|
const fixture = createComponent(MatInputWithSubscriptSizing);
|
|
fixture.detectChanges();
|
|
|
|
const subscriptElement = fixture.nativeElement.querySelector(
|
|
'.mat-mdc-form-field-subscript-wrapper',
|
|
);
|
|
|
|
expect(fixture.componentInstance.formField.subscriptSizing).toBe('fixed');
|
|
expect(subscriptElement.classList.contains('mat-mdc-form-field-subscript-dynamic-size')).toBe(
|
|
false,
|
|
);
|
|
|
|
fixture.componentInstance.sizing = 'dynamic';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.componentInstance.formField.subscriptSizing).toBe('dynamic');
|
|
expect(subscriptElement.classList.contains('mat-mdc-form-field-subscript-dynamic-size')).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it('changes the default value of subscriptSizing (undefined input)', () => {
|
|
const fixture = createComponent(MatInputWithSubscriptSizing, [
|
|
{
|
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
useValue: {
|
|
subscriptSizing: 'dynamic',
|
|
},
|
|
},
|
|
]);
|
|
|
|
fixture.detectChanges();
|
|
expect(fixture.componentInstance.formField.subscriptSizing).toBe('dynamic');
|
|
expect(
|
|
fixture.nativeElement
|
|
.querySelector('.mat-mdc-form-field-subscript-wrapper')
|
|
.classList.contains('mat-mdc-form-field-subscript-dynamic-size'),
|
|
).toBe(true);
|
|
});
|
|
|
|
it('changes the default value of subscriptSizing (no input)', () => {
|
|
const fixture = createComponent(MatInputWithAppearance, [
|
|
{
|
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
useValue: {
|
|
subscriptSizing: 'dynamic',
|
|
},
|
|
},
|
|
]);
|
|
|
|
fixture.detectChanges();
|
|
expect(fixture.componentInstance.formField.subscriptSizing).toBe('dynamic');
|
|
expect(
|
|
fixture.nativeElement
|
|
.querySelector('.mat-mdc-form-field-subscript-wrapper')
|
|
.classList.contains('mat-mdc-form-field-subscript-dynamic-size'),
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('MatFormField without label', () => {
|
|
it('should not float the label when no label is defined.', () => {
|
|
let fixture = createComponent(MatInputWithoutDefinedLabel);
|
|
fixture.detectChanges();
|
|
|
|
const inputEl = fixture.debugElement.query(By.css('input'))!;
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
|
|
// Update the value of the input and set focus.
|
|
inputEl.nativeElement.value = 'Text';
|
|
fixture.detectChanges();
|
|
|
|
// should not float label if there is no label
|
|
expect(formField._shouldLabelFloat()).toBe(false);
|
|
});
|
|
|
|
it('should not float the label when the label is removed after it has been shown', () => {
|
|
let fixture = createComponent(MatInputWithCondictionalLabel);
|
|
fixture.detectChanges();
|
|
const inputEl = fixture.debugElement.query(By.css('input'))!;
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
|
|
// initially, label is present
|
|
expect(fixture.nativeElement.querySelector('label')).not.toBeNull();
|
|
|
|
// removing label after it has been shown
|
|
fixture.componentInstance.hasLabel = false;
|
|
inputEl.nativeElement.value = 'Text';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
// now expected to not have a label
|
|
expect(fixture.nativeElement.querySelector('label')).toBeNull();
|
|
// should not float label since there is no label
|
|
expect(formField._shouldLabelFloat()).toBe(false);
|
|
});
|
|
|
|
it('should float the label when the label is not removed', () => {
|
|
let fixture = createComponent(MatInputWithCondictionalLabel);
|
|
fixture.detectChanges();
|
|
const inputEl = fixture.debugElement.query(By.css('input'))!;
|
|
const formField = fixture.debugElement.query(By.directive(MatFormField))!
|
|
.componentInstance as MatFormField;
|
|
|
|
inputEl.nativeElement.value = 'Text';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
// Expected to have a label
|
|
expect(fixture.nativeElement.querySelector('label')).not.toBeNull();
|
|
// should float label since there is a label
|
|
expect(formField._shouldLabelFloat()).toBe(true);
|
|
});
|
|
});
|
|
|
|
function configureTestingModule(
|
|
component: Type<any>,
|
|
options: {
|
|
providers?: Provider[];
|
|
imports?: any[];
|
|
declarations?: any[];
|
|
animations?: boolean;
|
|
} = {},
|
|
) {
|
|
const {providers = [], imports = [], declarations = [], animations = true} = options;
|
|
TestBed.configureTestingModule({
|
|
imports: [
|
|
FormsModule,
|
|
MatFormFieldModule,
|
|
MatIconModule,
|
|
MatInputModule,
|
|
animations ? BrowserAnimationsModule : NoopAnimationsModule,
|
|
ReactiveFormsModule,
|
|
...imports,
|
|
],
|
|
declarations: [component, ...declarations],
|
|
providers: [
|
|
// Custom error handler that re-throws the error. Errors happening within
|
|
// change detection phase will be reported through the handler and thrown
|
|
// in Ivy. Since we do not want to pollute the "console.error", but rather
|
|
// just rely on the actual error interrupting the test, we re-throw here.
|
|
{
|
|
provide: ErrorHandler,
|
|
useValue: {
|
|
handleError: (err: any) => {
|
|
throw err;
|
|
},
|
|
},
|
|
},
|
|
...providers,
|
|
],
|
|
});
|
|
}
|
|
|
|
function createComponent<T>(
|
|
component: Type<T>,
|
|
providers: Provider[] = [],
|
|
imports: any[] = [],
|
|
declarations: any[] = [],
|
|
): ComponentFixture<T> {
|
|
configureTestingModule(component, {providers, imports, declarations});
|
|
return TestBed.createComponent<T>(component);
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [floatLabel]="floatLabel">
|
|
<mat-label>Label</mat-label>
|
|
<input matNativeControl id="test-id" placeholder="test">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithId {
|
|
floatLabel: 'always' | 'auto' = 'auto';
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput [disabled]="disabled" [disabledInteractive]="disabledInteractive">
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithDisabled {
|
|
disabled = false;
|
|
disabledInteractive = false;
|
|
}
|
|
|
|
@Component({
|
|
template: `<mat-form-field><input matInput [required]="required"></mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithRequired {
|
|
required: boolean;
|
|
}
|
|
|
|
@Component({
|
|
template: `<mat-form-field><input matInput [type]="type"></mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithType {
|
|
type: string;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [hideRequiredMarker]="hideRequiredMarker">
|
|
<mat-label>hello</mat-label>
|
|
<input matInput required [disabled]="disabled">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputLabelRequiredTestComponent {
|
|
hideRequiredMarker: boolean = false;
|
|
disabled: boolean = false;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput placeholder="Hello" [formControl]="formControl">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithFormControl {
|
|
formControl = new FormControl('');
|
|
}
|
|
|
|
@Component({
|
|
template: `<mat-form-field><input matInput><mat-hint>{{label}}</mat-hint></mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputHintLabel2TestController {
|
|
label: string = '';
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [hintLabel]="label">
|
|
<input matInput aria-describedby="initial">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputHintLabelTestController {
|
|
label: string = '';
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [hintLabel]="label">
|
|
<input matInput [formControl]="formControl" [aria-describedby]="userDescribedByValue">
|
|
@if (showError) {
|
|
<mat-error>Some error</mat-error>
|
|
}
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithSubscriptAndAriaDescribedBy {
|
|
label: string = '';
|
|
userDescribedByValue: string = '';
|
|
showError = false;
|
|
formControl = new FormControl('');
|
|
}
|
|
|
|
@Component({
|
|
template: `<mat-form-field><input matInput [type]="t"></mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputInvalidTypeTestController {
|
|
t = 'file';
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field hintLabel="Hello">
|
|
<input matInput>
|
|
<mat-hint>World</mat-hint>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputInvalidHint2TestController {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput>
|
|
<mat-hint>Hello</mat-hint>
|
|
<mat-hint>World</mat-hint>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputInvalidHintTestController {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput>
|
|
<mat-hint align="start" [id]="startId">Hello</mat-hint>
|
|
<mat-hint align="end" [id]="endId">World</mat-hint>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputMultipleHintTestController {
|
|
startId: string;
|
|
endId: string;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field hintLabel="Hello">
|
|
<input matInput>
|
|
<mat-hint align="end">World</mat-hint>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputMultipleHintMixedTestController {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput type="date" placeholder="Placeholder">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputDateTestController {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-label>Label</mat-label>
|
|
<input
|
|
matInput
|
|
type="text"
|
|
placeholder="Placeholder"
|
|
[disabled]="disabled"
|
|
[disabledInteractive]="disabledInteractive">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputTextTestController {
|
|
disabled = false;
|
|
disabledInteractive = false;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput type="password" placeholder="Placeholder">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputPasswordTestController {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput type="number" placeholder="Placeholder">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputNumberTestController {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput type="number" placeholder="Placeholder" [(ngModel)]="value">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputZeroTestController {
|
|
value = 0;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput placeholder="Label" [value]="value">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithValueBinding {
|
|
value: string = 'Initial';
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput placeholder="Label">
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithStaticLabel {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [floatLabel]="shouldFloat">
|
|
<mat-label>Label</mat-label>
|
|
<input
|
|
matInput
|
|
placeholder="Placeholder"
|
|
[disabled]="disabled"
|
|
[disabledInteractive]="disabledInteractive">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithDynamicLabel {
|
|
shouldFloat: 'always' | 'auto' = 'always';
|
|
disabled = false;
|
|
disabledInteractive = false;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput placeholder="Label">
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithoutDefinedLabel {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
@if (hasLabel) {
|
|
<mat-label>Label</mat-label>
|
|
}
|
|
<input matInput>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithCondictionalLabel {
|
|
hasLabel = true;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<textarea matNativeControl [rows]="rows" [cols]="cols" [wrap]="wrap" placeholder="Snacks">
|
|
</textarea>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputTextareaWithBindings {
|
|
rows: number = 4;
|
|
cols: number = 8;
|
|
wrap: string = 'hard';
|
|
}
|
|
|
|
@Component({
|
|
template: `<mat-form-field><input></mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputMissingMatInputTestController {}
|
|
|
|
@Component({
|
|
template: `
|
|
<form #form="ngForm" novalidate>
|
|
<mat-form-field>
|
|
<input matInput [formControl]="formControl">
|
|
<mat-hint>Please type something</mat-hint>
|
|
@if (renderError) {
|
|
<mat-error>This field is required</mat-error>
|
|
}
|
|
</mat-form-field>
|
|
</form>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithFormErrorMessages {
|
|
@ViewChild('form') form: NgForm;
|
|
formControl = new FormControl('incorrect', [
|
|
Validators.required,
|
|
Validators.pattern(/valid value/),
|
|
]);
|
|
renderError = true;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<form [formGroup]="formGroup">
|
|
<mat-form-field>
|
|
<input matInput
|
|
formControlName="name"
|
|
[errorStateMatcher]="customErrorStateMatcher">
|
|
<mat-hint>Please type something</mat-hint>
|
|
<mat-error>This field is required</mat-error>
|
|
</mat-form-field>
|
|
</form>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class InputInFormGroup {
|
|
formGroup = new FormGroup({
|
|
name: new FormControl('', [Validators.required, Validators.pattern(/valid value/)]),
|
|
});
|
|
|
|
errorState = false;
|
|
|
|
customErrorStateMatcher = {
|
|
isErrorState: () => this.errorState,
|
|
};
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<form [formGroup]="formGroup" novalidate>
|
|
<mat-form-field>
|
|
<input matInput formControlName="name">
|
|
<mat-hint>Please type something</mat-hint>
|
|
<mat-error>This field is required</mat-error>
|
|
</mat-form-field>
|
|
</form>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithFormGroupErrorMessages {
|
|
@ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective;
|
|
formGroup = new FormGroup({
|
|
name: new FormControl('incorrect', [Validators.required, Validators.pattern(/valid value/)]),
|
|
});
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-icon matIconPrefix>favorite</mat-icon>
|
|
<div matTextPrefix>Prefix</div>
|
|
<input matInput>
|
|
<div matTextSuffix>Suffix</div>
|
|
<mat-icon matIconSuffix>favorite</mat-icon>
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithPrefixAndSuffix {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
@if (renderInput) {
|
|
<input matInput>
|
|
}
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithNgIf {
|
|
renderInput = true;
|
|
}
|
|
|
|
@Component({
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-label>Label</mat-label>
|
|
<input matInput [formControl]="formControl">
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputOnPush {
|
|
formControl = new FormControl('');
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-label>Label</mat-label>
|
|
<input matInput>
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithLabel {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [floatLabel]="floatLabel" [appearance]="appearance">
|
|
<mat-label>Label</mat-label>
|
|
<input matInput placeholder="Placeholder">
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithLabelAndPlaceholder {
|
|
floatLabel: FloatLabelType;
|
|
appearance: MatFormFieldAppearance;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [appearance]="appearance">
|
|
<mat-label>My Label</mat-label>
|
|
<input matInput placeholder="Placeholder">
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithAppearance {
|
|
@ViewChild(MatFormField) formField: MatFormField;
|
|
appearance: MatFormFieldAppearance;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [subscriptSizing]="sizing">
|
|
<mat-label>My Label</mat-label>
|
|
<input matInput placeholder="Placeholder" required>
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithSubscriptSizing {
|
|
@ViewChild(MatFormField) formField: MatFormField;
|
|
sizing: SubscriptSizing;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput>
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithoutPlaceholder {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-label>Label</mat-label>
|
|
<select matNativeControl id="test-id" [disabled]="disabled" [required]="required">
|
|
<option value="volvo">Volvo</option>
|
|
<option value="saab">Saab</option>
|
|
<option value="mercedes">Mercedes</option>
|
|
<option value="audi">Audi</option>
|
|
</select>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputSelect {
|
|
disabled: boolean;
|
|
required: boolean;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-label>Form-field label</mat-label>
|
|
<select matNativeControl>
|
|
<option value="" disabled selected></option>
|
|
<option value="saab">Saab</option>
|
|
<option value="mercedes">Mercedes</option>
|
|
<option value="audi">Audi</option>
|
|
</select>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputSelectWithNoLabelNoValue {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-label>Label</mat-label>
|
|
<select matNativeControl>
|
|
<option value="" label="select a car"></option>
|
|
<option value="saab">Saab</option>
|
|
<option value="mercedes">Mercedes</option>
|
|
<option value="audi">Audi</option>
|
|
</select>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputSelectWithLabel {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-label>Label</mat-label>
|
|
<select matNativeControl>
|
|
<option value="">select a car</option>
|
|
<option value="saab">Saab</option>
|
|
<option value="mercedes">Mercedes</option>
|
|
<option value="audi">Audi</option>
|
|
</select>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputSelectWithInnerHtml {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput customInputAccessor placeholder="Placeholder">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithCustomAccessor {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<select matNativeControl>
|
|
</select>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputSelectWithoutOptions {}
|
|
|
|
/** Custom component that never has a value. Used for testing the `MAT_INPUT_VALUE_ACCESSOR`. */
|
|
@Directive({
|
|
selector: 'input[customInputAccessor]',
|
|
providers: [
|
|
{
|
|
provide: MAT_INPUT_VALUE_ACCESSOR,
|
|
useExisting: CustomMatInputAccessor,
|
|
},
|
|
],
|
|
standalone: false,
|
|
})
|
|
class CustomMatInputAccessor {
|
|
get value() {
|
|
return this._value;
|
|
}
|
|
set value(_value: any) {}
|
|
private _value = null;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field [color]="color">
|
|
<input matNativeControl>
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithColor {
|
|
color: ThemePalette;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input class="inside" matNativeControl>
|
|
</mat-form-field>
|
|
|
|
<input class="outside" matNativeControl>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputInsideOutsideFormField {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<mat-label>Hello</mat-label>
|
|
<input matInput [formControl]="formControl">
|
|
</mat-form-field>`,
|
|
standalone: false,
|
|
})
|
|
class MatInputWithRequiredFormControl {
|
|
formControl = new FormControl('', [Validators.required]);
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<input matInput>
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class MatInputSimple {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-form-field>
|
|
<ng-container matIconPrefix>icon-prefix</ng-container>
|
|
<ng-container matTextPrefix>text-prefix</ng-container>
|
|
<input matInput>
|
|
<ng-container matTextSuffix>text-suffix</ng-container>
|
|
<ng-container matIconSuffix>icon-suffix</ng-container>
|
|
</mat-form-field>
|
|
`,
|
|
standalone: false,
|
|
})
|
|
class InputWithNgContainerPrefixAndSuffix {}
|