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 . 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); 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); 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); 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; 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); 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, 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( component: Type, providers: Provider[] = [], imports: any[] = [], declarations: any[] = [], ): ComponentFixture { configureTestingModule(component, {providers, imports, declarations}); return TestBed.createComponent(component); } @Component({ template: ` Label `, standalone: false, }) class MatInputWithId { floatLabel: 'always' | 'auto' = 'auto'; } @Component({ template: ` `, standalone: false, }) class MatInputWithDisabled { disabled = false; disabledInteractive = false; } @Component({ template: ``, standalone: false, }) class MatInputWithRequired { required: boolean; } @Component({ template: ``, standalone: false, }) class MatInputWithType { type: string; } @Component({ template: ` hello `, standalone: false, }) class MatInputLabelRequiredTestComponent { hideRequiredMarker: boolean = false; disabled: boolean = false; } @Component({ template: ` `, standalone: false, }) class MatInputWithFormControl { formControl = new FormControl(''); } @Component({ template: `{{label}}`, standalone: false, }) class MatInputHintLabel2TestController { label: string = ''; } @Component({ template: ` `, standalone: false, }) class MatInputHintLabelTestController { label: string = ''; } @Component({ template: ` @if (showError) { Some error } `, standalone: false, }) class MatInputWithSubscriptAndAriaDescribedBy { label: string = ''; userDescribedByValue: string = ''; showError = false; formControl = new FormControl(''); } @Component({ template: ``, standalone: false, }) class MatInputInvalidTypeTestController { t = 'file'; } @Component({ template: ` World `, standalone: false, }) class MatInputInvalidHint2TestController {} @Component({ template: ` Hello World `, standalone: false, }) class MatInputInvalidHintTestController {} @Component({ template: ` Hello World `, standalone: false, }) class MatInputMultipleHintTestController { startId: string; endId: string; } @Component({ template: ` World `, standalone: false, }) class MatInputMultipleHintMixedTestController {} @Component({ template: ` `, standalone: false, }) class MatInputDateTestController {} @Component({ template: ` Label `, standalone: false, }) class MatInputTextTestController { disabled = false; disabledInteractive = false; } @Component({ template: ` `, standalone: false, }) class MatInputPasswordTestController {} @Component({ template: ` `, standalone: false, }) class MatInputNumberTestController {} @Component({ template: ` `, standalone: false, }) class MatInputZeroTestController { value = 0; } @Component({ template: ` `, standalone: false, }) class MatInputWithValueBinding { value: string = 'Initial'; } @Component({ template: ` `, standalone: false, }) class MatInputWithStaticLabel {} @Component({ template: ` Label `, standalone: false, }) class MatInputWithDynamicLabel { shouldFloat: 'always' | 'auto' = 'always'; disabled = false; disabledInteractive = false; } @Component({ template: ` `, standalone: false, }) class MatInputWithoutDefinedLabel {} @Component({ template: ` @if (hasLabel) { Label } `, standalone: false, }) class MatInputWithCondictionalLabel { hasLabel = true; } @Component({ template: ` `, standalone: false, }) class MatInputTextareaWithBindings { rows: number = 4; cols: number = 8; wrap: string = 'hard'; } @Component({ template: ``, standalone: false, }) class MatInputMissingMatInputTestController {} @Component({ template: `
Please type something @if (renderError) { This field is required }
`, standalone: false, }) class MatInputWithFormErrorMessages { @ViewChild('form') form: NgForm; formControl = new FormControl('incorrect', [ Validators.required, Validators.pattern(/valid value/), ]); renderError = true; } @Component({ template: `
Please type something This field is required
`, standalone: false, }) class InputInFormGroup { formGroup = new FormGroup({ name: new FormControl('', [Validators.required, Validators.pattern(/valid value/)]), }); errorState = false; customErrorStateMatcher = { isErrorState: () => this.errorState, }; } @Component({ template: `
Please type something This field is required
`, standalone: false, }) class MatInputWithFormGroupErrorMessages { @ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective; formGroup = new FormGroup({ name: new FormControl('incorrect', [Validators.required, Validators.pattern(/valid value/)]), }); } @Component({ template: ` favorite
Prefix
Suffix
favorite
`, standalone: false, }) class MatInputWithPrefixAndSuffix {} @Component({ template: ` @if (renderInput) { } `, standalone: false, }) class MatInputWithNgIf { renderInput = true; } @Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: ` Label `, standalone: false, }) class MatInputOnPush { formControl = new FormControl(''); } @Component({ template: ` Label `, standalone: false, }) class MatInputWithLabel {} @Component({ template: ` Label `, standalone: false, }) class MatInputWithLabelAndPlaceholder { floatLabel: FloatLabelType; appearance: MatFormFieldAppearance; } @Component({ template: ` My Label `, standalone: false, }) class MatInputWithAppearance { @ViewChild(MatFormField) formField: MatFormField; appearance: MatFormFieldAppearance; } @Component({ template: ` My Label `, standalone: false, }) class MatInputWithSubscriptSizing { @ViewChild(MatFormField) formField: MatFormField; sizing: SubscriptSizing; } @Component({ template: ` `, standalone: false, }) class MatInputWithoutPlaceholder {} @Component({ template: ` Label `, standalone: false, }) class MatInputSelect { disabled: boolean; required: boolean; } @Component({ template: ` Form-field label `, standalone: false, }) class MatInputSelectWithNoLabelNoValue {} @Component({ template: ` Label `, standalone: false, }) class MatInputSelectWithLabel {} @Component({ template: ` Label `, standalone: false, }) class MatInputSelectWithInnerHtml {} @Component({ template: ` `, standalone: false, }) class MatInputWithCustomAccessor {} @Component({ template: ` `, 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: ` `, standalone: false, }) class MatInputWithColor { color: ThemePalette; } @Component({ template: ` `, standalone: false, }) class MatInputInsideOutsideFormField {} @Component({ template: ` Hello `, standalone: false, }) class MatInputWithRequiredFormControl { formControl = new FormControl('', [Validators.required]); } @Component({ template: ` `, standalone: false, }) class MatInputSimple {} @Component({ template: ` icon-prefix text-prefix text-suffix icon-suffix `, standalone: false, }) class InputWithNgContainerPrefixAndSuffix {}