UNPKG

ipsos-components

Version:

Material Design components for Angular

1,341 lines (1,044 loc) 52.7 kB
import {Platform, PlatformModule} from '@angular/cdk/platform'; import {createFakeEvent, dispatchFakeEvent, wrappedErrorMessage} from '@angular/cdk/testing'; import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core'; import {ComponentFixture, inject, TestBed, fakeAsync, flush} from '@angular/core/testing'; import { FormControl, FormGroup, FormGroupDirective, FormsModule, NgForm, ReactiveFormsModule, Validators, } from '@angular/forms'; import { MAT_LABEL_GLOBAL_OPTIONS, ShowOnDirtyErrorStateMatcher, ErrorStateMatcher, FloatLabelType, } from '@angular/material/core'; import { getMatFormFieldDuplicatedHintError, getMatFormFieldMissingControlError, getMatFormFieldPlaceholderConflictError, MatFormField, MatFormFieldModule, } from '@angular/material/form-field'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {MatInputModule} from './index'; import {MatInput} from './input'; describe('MatInput without forms', () => { beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ imports: [ FormsModule, MatFormFieldModule, MatInputModule, NoopAnimationsModule, PlatformModule, ReactiveFormsModule, ], declarations: [ MatInputDateTestController, MatInputHintLabel2TestController, MatInputHintLabelTestController, MatInputInvalidHint2TestController, MatInputInvalidHintTestController, MatInputInvalidPlaceholderTestController, MatInputInvalidTypeTestController, MatInputMissingMatInputTestController, MatInputMultipleHintMixedTestController, MatInputMultipleHintTestController, MatInputNumberTestController, MatInputPasswordTestController, MatInputPlaceholderAttrTestComponent, MatInputPlaceholderElementTestComponent, MatInputPlaceholderRequiredTestComponent, MatInputTextTestController, MatInputWithDisabled, MatInputWithDynamicLabel, MatInputWithId, MatInputWithPrefixAndSuffix, MatInputWithRequired, MatInputWithStaticLabel, MatInputWithType, MatInputWithValueBinding, MatInputTextareaWithBindings, MatInputWithNgIf, MatInputOnPush, MatInputWithReadonlyInput, MatInputWithLabelAndPlaceholder, ], }); TestBed.compileComponents(); })); it('should default to floating labels', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithId); fixture.detectChanges(); let formField = fixture.debugElement.query(By.directive(MatFormField)) .componentInstance as MatFormField; expect(formField.floatLabel).toBe('auto', 'Expected MatInput to set floatingLabel to auto by default.'); })); it('should default to global floating label type', fakeAsync(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ imports: [ FormsModule, MatFormFieldModule, MatInputModule, NoopAnimationsModule ], declarations: [ MatInputWithId ], providers: [{ provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: { float: 'always' } }] }); let fixture = TestBed.createComponent(MatInputWithId); fixture.detectChanges(); let formField = fixture.debugElement.query(By.directive(MatFormField)) .componentInstance as MatFormField; expect(formField.floatLabel).toBe('always', 'Expected MatInput to set floatingLabel to always from global option.'); })); it('should not be treated as empty if type is date', fakeAsync(inject([Platform], (platform: Platform) => { if (!(platform.TRIDENT || (platform.SAFARI && !platform.IOS))) { let fixture = TestBed.createComponent(MatInputDateTestController); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')).nativeElement; expect(el).not.toBeNull(); expect(el.classList.contains('mat-form-field-empty')).toBe(false); } }))); // Safari Desktop and IE don't support type="date" and fallback to type="text". it('should be treated as empty if type is date in Safari Desktop or IE', fakeAsync(inject([Platform], (platform: Platform) => { if (platform.TRIDENT || (platform.SAFARI && !platform.IOS)) { let fixture = TestBed.createComponent(MatInputDateTestController); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')).nativeElement; expect(el).not.toBeNull(); expect(el.classList.contains('mat-form-field-empty')).toBe(true); } }))); it('should treat text input type as empty at init', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputTextTestController); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')).nativeElement; expect(el).not.toBeNull(); expect(el.classList.contains('mat-form-field-empty')).toBe(true); })); it('should treat password input type as empty at init', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputPasswordTestController); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')).nativeElement; expect(el).not.toBeNull(); expect(el.classList.contains('mat-form-field-empty')).toBe(true); })); it('should treat number input type as empty at init', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputNumberTestController); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')).nativeElement; expect(el).not.toBeNull(); expect(el.classList.contains('mat-form-field-empty')).toBe(true); })); it('should not be empty after input entered', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputTextTestController); fixture.detectChanges(); let inputEl = fixture.debugElement.query(By.css('input')); let el = fixture.debugElement.query(By.css('label')).nativeElement; expect(el).not.toBeNull(); expect(el.classList.contains('mat-form-field-empty')).toBe(true, 'should be empty'); inputEl.nativeElement.value = 'hello'; // Simulate input event. inputEl.triggerEventHandler('input', {target: inputEl.nativeElement}); fixture.detectChanges(); el = fixture.debugElement.query(By.css('label')).nativeElement; expect(el.classList.contains('mat-form-field-empty')).toBe(false, 'should not be empty'); })); it('should update the placeholder when input entered', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithStaticLabel); fixture.detectChanges(); let inputEl = fixture.debugElement.query(By.css('input')); let labelEl = fixture.debugElement.query(By.css('label')).nativeElement; expect(labelEl.classList).toContain('mat-form-field-empty'); expect(labelEl.classList).not.toContain('mat-form-field-float'); // 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(labelEl.classList).not.toContain('mat-form-field-empty'); expect(labelEl.classList).not.toContain('mat-form-field-float'); })); it('should not be empty when the value set before view init', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithValueBinding); fixture.detectChanges(); let labelEl = fixture.debugElement.query(By.css('.mat-form-field-label')).nativeElement; expect(labelEl.classList).not.toContain('mat-form-field-empty'); fixture.componentInstance.value = ''; fixture.detectChanges(); expect(labelEl.classList).toContain('mat-form-field-empty'); })); it('should add id', fakeAsync(() => { let fixture = TestBed.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-owns to the label for the associated control', fakeAsync(() => { let fixture = TestBed.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(labelElement.getAttribute('aria-owns')).toBe(inputElement.id); })); it('should add aria-required reflecting the required state', fakeAsync(() => { const fixture = TestBed.createComponent(MatInputWithRequired); fixture.detectChanges(); const inputElement: HTMLInputElement = fixture.debugElement.query(By.css('input')).nativeElement; expect(inputElement.getAttribute('aria-required')) .toBe('false', 'Expected aria-required to reflect required state of false'); fixture.componentInstance.required = true; fixture.detectChanges(); expect(inputElement.getAttribute('aria-required')) .toBe('true', 'Expected aria-required to reflect required state of true'); })); it('should not overwrite existing id', fakeAsync(() => { let fixture = TestBed.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', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputInvalidHintTestController); expect(() => fixture.detectChanges()).toThrowError( wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start'))); })); it('validates there\'s only one hint label per side (attribute)', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputInvalidHint2TestController); expect(() => fixture.detectChanges()).toThrowError( wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start'))); })); it('validates there\'s only one placeholder', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputInvalidPlaceholderTestController); expect(() => fixture.detectChanges()).toThrowError( wrappedErrorMessage(getMatFormFieldPlaceholderConflictError())); })); it('validates that matInput child is present', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputMissingMatInputTestController); expect(() => fixture.detectChanges()).toThrowError( wrappedErrorMessage(getMatFormFieldMissingControlError())); })); it('validates that matInput child is present after initialization', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithNgIf); expect(() => fixture.detectChanges()).not.toThrowError( wrappedErrorMessage(getMatFormFieldMissingControlError())); fixture.componentInstance.renderInput = false; expect(() => fixture.detectChanges()).toThrowError( wrappedErrorMessage(getMatFormFieldMissingControlError())); })); it('validates the type', fakeAsync(() => { let fixture = TestBed.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 = TestBed.createComponent(MatInputHintLabelTestController); fixture.detectChanges(); // If the hint label is empty, expect no label. expect(fixture.debugElement.query(By.css('.mat-hint'))).toBeNull(); fixture.componentInstance.label = 'label'; fixture.detectChanges(); expect(fixture.debugElement.query(By.css('.mat-hint'))).not.toBeNull(); })); it('sets an id on hint labels', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputHintLabelTestController); fixture.componentInstance.label = 'label'; fixture.detectChanges(); let hint = fixture.debugElement.query(By.css('.mat-hint')).nativeElement; expect(hint.getAttribute('id')).toBeTruthy(); })); it('supports hint labels elements', fakeAsync(() => { let fixture = TestBed.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.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 = TestBed.createComponent(MatInputHintLabel2TestController); fixture.componentInstance.label = 'label'; fixture.detectChanges(); let hint = fixture.debugElement.query(By.css('mat-hint')).nativeElement; expect(hint.getAttribute('id')).toBeTruthy(); })); it('supports placeholder attribute', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputPlaceholderAttrTestComponent); fixture.detectChanges(); let inputEl = fixture.debugElement.query(By.css('input')).nativeElement; expect(fixture.debugElement.query(By.css('label'))).toBeNull(); expect(inputEl.placeholder).toBe(''); fixture.componentInstance.placeholder = 'Other placeholder'; fixture.detectChanges(); let labelEl = fixture.debugElement.query(By.css('label')); expect(inputEl.placeholder).toBe('Other placeholder'); expect(labelEl).not.toBeNull(); expect(labelEl.nativeElement.textContent).toMatch('Other placeholder'); expect(labelEl.nativeElement.textContent).not.toMatch(/\*/g); })); it('supports placeholder element', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputPlaceholderElementTestComponent); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')); expect(el).not.toBeNull(); expect(el.nativeElement.textContent).toMatch('Default Placeholder'); fixture.componentInstance.placeholder = 'Other placeholder'; fixture.detectChanges(); el = fixture.debugElement.query(By.css('label')); expect(el).not.toBeNull(); expect(el.nativeElement.textContent).toMatch('Other placeholder'); expect(el.nativeElement.textContent).not.toMatch(/\*/g); })); it('supports placeholder required star', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputPlaceholderRequiredTestComponent); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')); expect(el).not.toBeNull(); expect(el.nativeElement.textContent).toMatch(/hello\s+\*/g); })); it('should hide the required star from screen readers', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputPlaceholderRequiredTestComponent); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('.mat-form-field-required-marker')).nativeElement; expect(el.getAttribute('aria-hidden')).toBe('true'); })); it('hide placeholder required star when set to hide the required marker', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputPlaceholderRequiredTestComponent); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')); expect(el).not.toBeNull(); expect(el.nativeElement.textContent).toMatch(/hello\s+\*/g); fixture.componentInstance.hideRequiredMarker = true; fixture.detectChanges(); expect(el.nativeElement.textContent).toMatch(/hello/g); })); it('supports the disabled attribute as binding', fakeAsync(() => { const fixture = TestBed.createComponent(MatInputWithDisabled); fixture.detectChanges(); const formFieldEl = fixture.debugElement.query(By.css('.mat-form-field')).nativeElement; const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; expect(formFieldEl.classList.contains('mat-form-field-disabled')) .toBe(false, `Expected form field not to start out disabled.`); expect(inputEl.disabled).toBe(false); fixture.componentInstance.disabled = true; fixture.detectChanges(); expect(formFieldEl.classList.contains('mat-form-field-disabled')) .toBe(true, `Expected form field to look disabled after property is set.`); expect(inputEl.disabled).toBe(true); })); it('supports the required attribute as binding', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithRequired); fixture.detectChanges(); let inputEl = fixture.debugElement.query(By.css('input')).nativeElement; expect(inputEl.required).toBe(false); fixture.componentInstance.required = true; fixture.detectChanges(); expect(inputEl.required).toBe(true); })); it('supports the type attribute as binding', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithType); fixture.detectChanges(); let inputEl = fixture.debugElement.query(By.css('input')).nativeElement; expect(inputEl.type).toBe('text'); fixture.componentInstance.type = 'password'; fixture.detectChanges(); expect(inputEl.type).toBe('password'); })); it('supports textarea', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputTextareaWithBindings); fixture.detectChanges(); const textarea: HTMLTextAreaElement = fixture.nativeElement.querySelector('textarea'); expect(textarea).not.toBeNull(); })); it('sets the aria-describedby when a hintLabel is set', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputHintLabelTestController); fixture.componentInstance.label = 'label'; fixture.detectChanges(); let hint = fixture.debugElement.query(By.css('.mat-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 to the id of the mat-hint', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputHintLabel2TestController); fixture.componentInstance.label = 'label'; fixture.detectChanges(); let hint = fixture.debugElement.query(By.css('.mat-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 = TestBed.createComponent(MatInputMultipleHintTestController); fixture.componentInstance.startId = 'start'; fixture.componentInstance.endId = 'end'; fixture.detectChanges(); let input = fixture.debugElement.query(By.css('input')).nativeElement; expect(input.getAttribute('aria-describedby')).toBe('start end'); })); it('sets the aria-describedby when a hintLabel is set, in addition to a mat-hint', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputMultipleHintMixedTestController); fixture.detectChanges(); let hintLabel = fixture.debugElement.query(By.css('.mat-hint:not(.mat-right)')).nativeElement; let endLabel = fixture.debugElement.query(By.css('.mat-hint.mat-right')).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 = TestBed.createComponent(MatInputWithDynamicLabel); fixture.detectChanges(); let inputEl = fixture.debugElement.query(By.css('input')).nativeElement; let formFieldEl = fixture.debugElement.query(By.css('.mat-form-field')).nativeElement; expect(formFieldEl.classList).toContain('mat-form-field-can-float'); expect(formFieldEl.classList).toContain('mat-form-field-should-float'); fixture.componentInstance.shouldFloat = 'auto'; fixture.detectChanges(); expect(formFieldEl.classList).toContain('mat-form-field-can-float'); expect(formFieldEl.classList).not.toContain('mat-form-field-should-float'); // Update the value of the input. inputEl.value = 'Text'; // Fake behavior of the `(input)` event which should trigger a change detection. fixture.detectChanges(); expect(formFieldEl.classList).toContain('mat-form-field-can-float'); expect(formFieldEl.classList).toContain('mat-form-field-should-float'); })); it('should always float the label when floatLabel is set to true', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithDynamicLabel); fixture.detectChanges(); let inputEl = fixture.debugElement.query(By.css('input')).nativeElement; let formFieldEl = fixture.debugElement.query(By.css('.mat-form-field')).nativeElement; expect(formFieldEl.classList).toContain('mat-form-field-can-float'); expect(formFieldEl.classList).toContain('mat-form-field-should-float'); 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(formFieldEl.classList).toContain('mat-form-field-can-float'); expect(formFieldEl.classList).toContain('mat-form-field-should-float'); })); it('should never float the label when floatLabel is set to false', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithDynamicLabel); fixture.componentInstance.shouldFloat = 'never'; fixture.detectChanges(); let inputEl = fixture.debugElement.query(By.css('input')).nativeElement; let labelEl = fixture.debugElement.query(By.css('label')).nativeElement; expect(labelEl.classList).toContain('mat-form-field-empty'); expect(labelEl.classList).not.toContain('mat-form-field-float'); // 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).not.toContain('mat-form-field-empty'); expect(labelEl.classList).not.toContain('mat-form-field-float'); })); it('should be able to toggle the floating label programmatically', fakeAsync(() => { const fixture = TestBed.createComponent(MatInputWithId); fixture.detectChanges(); const formField = fixture.debugElement.query(By.directive(MatFormField)); const containerInstance = formField.componentInstance as MatFormField; const label = formField.nativeElement.querySelector('.mat-form-field-label'); expect(containerInstance.floatLabel).toBe('auto'); expect(label.classList) .toContain('mat-form-field-empty', 'Expected input to be considered empty.'); containerInstance.floatLabel = 'always'; fixture.detectChanges(); expect(label.classList) .not.toContain('mat-form-field-empty', 'Expected input to be considered not empty.'); })); it('should not have prefix and suffix elements when none are specified', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithId); fixture.detectChanges(); let prefixEl = fixture.debugElement.query(By.css('.mat-form-field-prefix')); let suffixEl = fixture.debugElement.query(By.css('.mat-form-field-suffix')); expect(prefixEl).toBeNull(); expect(suffixEl).toBeNull(); })); it('should add prefix and suffix elements when specified', fakeAsync(() => { const fixture = TestBed.createComponent(MatInputWithPrefixAndSuffix); fixture.detectChanges(); const prefixEl = fixture.debugElement.query(By.css('.mat-form-field-prefix')); const suffixEl = fixture.debugElement.query(By.css('.mat-form-field-suffix')); expect(prefixEl).not.toBeNull(); expect(suffixEl).not.toBeNull(); expect(prefixEl.nativeElement.innerText.trim()).toEqual('Prefix'); expect(suffixEl.nativeElement.innerText.trim()).toEqual('Suffix'); })); it('should update empty class when value changes programmatically and OnPush', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputOnPush); fixture.detectChanges(); let component = fixture.componentInstance; let label = fixture.debugElement.query(By.css('.mat-form-field-label')).nativeElement; expect(label.classList).toContain('mat-form-field-empty', 'Input initially empty'); component.formControl.setValue('something'); fixture.detectChanges(); expect(label.classList).not.toContain('mat-form-field-empty', 'Input no longer empty'); })); it('should set the focused class when the input is focused', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputTextTestController); fixture.detectChanges(); let input = fixture.debugElement.query(By.directive(MatInput)) .injector.get<MatInput>(MatInput); let container = fixture.debugElement.query(By.css('mat-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 = TestBed.createComponent(MatInputTextTestController); fixture.detectChanges(); const input = fixture.debugElement.query(By.directive(MatInput)).injector.get(MatInput); const container = fixture.debugElement.query(By.css('mat-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 be able to animate the label up and lock it in position', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputTextTestController); fixture.detectChanges(); let inputContainer = fixture.debugElement.query(By.directive(MatFormField)) .componentInstance as MatFormField; let label = fixture.debugElement.query(By.css('.mat-form-field-label')).nativeElement; expect(inputContainer.floatLabel).toBe('auto'); inputContainer._animateAndLockLabel(); fixture.detectChanges(); expect(inputContainer._shouldAlwaysFloat).toBe(false); expect(inputContainer.floatLabel).toBe('always'); const fakeEvent = Object.assign(createFakeEvent('transitionend'), {propertyName: 'transform'}); label.dispatchEvent(fakeEvent); fixture.detectChanges(); expect(inputContainer._shouldAlwaysFloat).toBe(true); expect(inputContainer.floatLabel).toBe('always'); })); it('should not highlight when focusing a readonly input', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithReadonlyInput); fixture.detectChanges(); let input = fixture.debugElement.query(By.directive(MatInput)).injector.get<MatInput>(MatInput); let container = fixture.debugElement.query(By.css('mat-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(input.focused).toBe(false); expect(container.classList).not.toContain('mat-focused'); })); it('should only show the native placeholder, when there is a label, on focus', () => { const fixture = TestBed.createComponent(MatInputWithLabelAndPlaceholder); fixture.detectChanges(); const container = fixture.debugElement.query(By.css('mat-form-field')).nativeElement; const label = fixture.debugElement.query(By.css('.mat-form-field-label')).nativeElement; const input = fixture.debugElement.query(By.css('input')).nativeElement; expect(container.classList).toContain('mat-form-field-hide-placeholder'); expect(container.classList).not.toContain('mat-form-field-should-float'); expect(label.textContent.trim()).toBe('Label'); expect(input.getAttribute('placeholder')).toBe('Placeholder'); input.value = 'Value'; fixture.detectChanges(); expect(container.classList).not.toContain('mat-form-field-hide-placeholder'); expect(container.classList).toContain('mat-form-field-should-float'); }); it('should always show the native placeholder when floatLabel is set to "always"', () => { const fixture = TestBed.createComponent(MatInputWithLabelAndPlaceholder); fixture.componentInstance.floatLabel = 'always'; 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 show the native placeholder when floatLabel is set to "never"', () => { const fixture = TestBed.createComponent(MatInputWithLabelAndPlaceholder); fixture.componentInstance.floatLabel = 'never'; fixture.detectChanges(); const container = fixture.debugElement.query(By.css('mat-form-field')).nativeElement; const input = fixture.debugElement.query(By.css('input')).nativeElement; expect(container.classList).toContain('mat-form-field-hide-placeholder'); expect(container.classList).not.toContain('mat-form-field-should-float'); input.value = 'Value'; fixture.detectChanges(); expect(container.classList).toContain('mat-form-field-hide-placeholder'); expect(container.classList).not.toContain('mat-form-field-should-float'); }); }); describe('MatInput with forms', () => { beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ imports: [ FormsModule, MatFormFieldModule, MatInputModule, NoopAnimationsModule, PlatformModule, ReactiveFormsModule, ], declarations: [ MatInputWithFormControl, MatInputWithFormErrorMessages, MatInputWithCustomErrorStateMatcher, MatInputWithFormGroupErrorMessages, MatInputZeroTestController, ], }); TestBed.compileComponents(); })); describe('error messages', () => { let fixture: ComponentFixture<MatInputWithFormErrorMessages>; let testComponent: MatInputWithFormErrorMessages; let containerEl: HTMLElement; let inputEl: HTMLElement; beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(MatInputWithFormErrorMessages); fixture.detectChanges(); testComponent = fixture.componentInstance; containerEl = fixture.debugElement.query(By.css('mat-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).toBe(true, 'Expected untouched form control'); expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); expect(inputEl.getAttribute('aria-invalid')) .toBe('false', 'Expected aria-invalid to be set to "false".'); })); it('should display an error message when the input is touched and invalid', fakeAsync(() => { expect(testComponent.formControl.invalid).toBe(true, 'Expected form control to be invalid'); expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); testComponent.formControl.markAsTouched(); fixture.detectChanges(); flush(); expect(containerEl.classList) .toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('mat-error').length) .toBe(1, 'Expected one error message to have been rendered.'); expect(inputEl.getAttribute('aria-invalid')) .toBe('true', 'Expected aria-invalid to be set to "true".'); })); it('should display an error message when the parent form is submitted', fakeAsync(() => { expect(testComponent.form.submitted).toBe(false, 'Expected form not to have been submitted'); expect(testComponent.formControl.invalid).toBe(true, 'Expected form control to be invalid'); expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); dispatchFakeEvent(fixture.debugElement.query(By.css('form')).nativeElement, 'submit'); fixture.detectChanges(); flush(); expect(testComponent.form.submitted).toBe(true, 'Expected form to have been submitted'); expect(containerEl.classList) .toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('mat-error').length) .toBe(1, 'Expected one error message to have been rendered.'); expect(inputEl.getAttribute('aria-invalid')) .toBe('true', 'Expected aria-invalid to be set to "true".'); })); it('should display an error message when the parent form group is submitted', fakeAsync(() => { fixture.destroy(); let groupFixture = TestBed.createComponent(MatInputWithFormGroupErrorMessages); let component: MatInputWithFormGroupErrorMessages; groupFixture.detectChanges(); component = groupFixture.componentInstance; containerEl = groupFixture.debugElement.query(By.css('mat-form-field')).nativeElement; inputEl = groupFixture.debugElement.query(By.css('input')).nativeElement; expect(component.formGroup.invalid).toBe(true, 'Expected form control to be invalid'); expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); expect(inputEl.getAttribute('aria-invalid')) .toBe('false', 'Expected aria-invalid to be set to "false".'); expect(component.formGroupDirective.submitted) .toBe(false, 'Expected form not to have been submitted'); dispatchFakeEvent(groupFixture.debugElement.query(By.css('form')).nativeElement, 'submit'); groupFixture.detectChanges(); flush(); expect(component.formGroupDirective.submitted) .toBe(true, 'Expected form to have been submitted'); expect(containerEl.classList) .toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('mat-error').length) .toBe(1, 'Expected one error message to have been rendered.'); expect(inputEl.getAttribute('aria-invalid')) .toBe('true', 'Expected aria-invalid to be set to "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) .toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('mat-error').length) .toBe(1, 'Expected one error message to have been rendered.'); expect(containerEl.querySelectorAll('mat-hint').length) .toBe(0, 'Expected no hints to be shown.'); testComponent.formControl.setValue('something'); 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) .toBe(0, 'Expected no error messages when the input is valid.'); expect(containerEl.querySelectorAll('mat-hint').length) .toBe(1, 'Expected one hint to be shown once the input is valid.'); })); it('should not hide the hint if there are no error messages', fakeAsync(() => { testComponent.renderError = false; fixture.detectChanges(); expect(containerEl.querySelectorAll('mat-hint').length) .toBe(1, 'Expected one hint to be shown on load.'); testComponent.formControl.markAsTouched(); fixture.detectChanges(); flush(); expect(containerEl.querySelectorAll('mat-hint').length) .toBe(1, 'Expected one hint to still be shown.'); })); it('should set the proper role on the error messages', fakeAsync(() => { testComponent.formControl.markAsTouched(); fixture.detectChanges(); expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert'); })); it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => { let hintId = fixture.debugElement.query(By.css('.mat-hint')).nativeElement.getAttribute('id'); let describedBy = inputEl.getAttribute('aria-describedby'); expect(hintId).toBeTruthy('hint should be shown'); expect(describedBy).toBe(hintId); fixture.componentInstance.formControl.markAsTouched(); fixture.detectChanges(); let errorIds = fixture.debugElement.queryAll(By.css('.mat-error')) .map(el => el.nativeElement.getAttribute('id')).join(' '); describedBy = inputEl.getAttribute('aria-describedby'); expect(errorIds).toBeTruthy('errors should be shown'); expect(describedBy).toBe(errorIds); })); }); describe('custom error behavior', () => { it('should display an error message when a custom error matcher returns true', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputWithCustomErrorStateMatcher); fixture.detectChanges(); let component = fixture.componentInstance; let containerEl = fixture.debugElement.query(By.css('mat-form-field')).nativeElement; const control = component.formGroup.get('name')!; expect(control.invalid).toBe(true, 'Expected form control to be invalid'); expect(containerEl.querySelectorAll('mat-error').length) .toBe(0, 'Expected no error messages'); control.markAsTouched(); fixture.detectChanges(); expect(containerEl.querySelectorAll('mat-error').length) .toBe(0, 'Expected no error messages after being touched.'); component.errorState = true; fixture.detectChanges(); expect(containerEl.querySelectorAll('mat-error').length) .toBe(1, 'Expected one error messages to have been rendered.'); })); it('should display an error message when global error matcher returns true', fakeAsync(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ imports: [ FormsModule, MatFormFieldModule, MatInputModule, NoopAnimationsModule, ReactiveFormsModule, ], declarations: [ MatInputWithFormErrorMessages ], providers: [{provide: ErrorStateMatcher, useValue: {isErrorState: () => true}}] }); let fixture = TestBed.createComponent(MatInputWithFormErrorMessages); fixture.detectChanges(); let containerEl = fixture.debugElement.query(By.css('mat-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(testComponent.formControl.untouched).toBe(true, 'Expected untouched form control'); expect(containerEl.querySelectorAll('mat-error').length).toBe(1, 'Expected an error message'); })); it('should display an error message when using ShowOnDirtyErrorStateMatcher', fakeAsync(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ imports: [ FormsModule, MatFormFieldModule, MatInputModule, NoopAnimationsModule, ReactiveFormsModule, ], declarations: [ MatInputWithFormErrorMessages ], providers: [{provide: ErrorStateMatcher, useClass: ShowOnDirtyErrorStateMatcher}] }); let fixture = TestBed.createComponent(MatInputWithFormErrorMessages); fixture.detectChanges(); let containerEl = fixture.debugElement.query(By.css('mat-form-field')).nativeElement; let testComponent = fixture.componentInstance; expect(testComponent.formControl.invalid).toBe(true, 'Expected form control to be invalid'); expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message'); testComponent.formControl.markAsTouched(); fixture.detectChanges(); expect(containerEl.querySelectorAll('mat-error').length) .toBe(0, 'Expected no error messages when touched'); testComponent.formControl.markAsDirty(); fixture.detectChanges(); expect(containerEl.querySelectorAll('mat-error').length) .toBe(1, 'Expected one error message when dirty'); })); }); it('should update the value when using FormControl.setValue', fakeAsync(() => { let fixture = TestBed.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 = TestBed.createComponent(MatInputWithFormControl); fixture.detectChanges(); const formFieldEl = fixture.debugElement.query(By.css('.mat-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).toContain('mat-form-field-disabled', `Expected form field to look disabled after disable() is called.`); expect(inputEl.disabled).toBe(true); })); it('should not treat the number 0 as empty', fakeAsync(() => { let fixture = TestBed.createComponent(MatInputZeroTestController); fixture.detectChanges(); flush(); fixture.detectChanges(); let el = fixture.debugElement.query(By.css('label')).nativeElement; expect(el).not.toBeNull(); expect(el.classList.contains('mat-form-field-empty')).toBe(false); })); }); @Component({ template: ` <mat-form-field> <input matInput id="test-id" placeholder="test"> </mat-form-field>` }) class MatInputWithId {} @Component({ template: `<mat-form-field><input matInput [disabled]="disabled"></mat-form-field>` }) class MatInputWithDisabled { disabled: boolean; } @Component({ template: `<mat-form-field><input matInput [required]="required"></mat-form-field>` }) class MatInputWithRequired { required: boolean; } @Component({ template: `<mat-form-field><input matInput [type]="type"></mat-form-field>` }) class MatInputWithType { type: string; } @Component({ template: `<mat-form-field [hideRequiredMarker]="hideRequiredMarker"> <input matInput required placeholder="hello"> </mat-form-field>` }) class MatInputPlaceholderRequiredTestComponent { hideRequiredMarker: boolean; } @Component({ template: ` <mat-form-field> <input matInput> <mat-placeholder>{{placeholder}}</mat-placeholder> </mat-form-field>` }) class MatInputPlaceholderElementTestComponent { placeholder: string = 'Default Placeholder'; } @Component({ template: `<mat-form-field><input matInput [formControl]="formControl"></mat-form-field>` }) class MatInputWithFormControl { formControl = new FormControl(); } @Component({ template: `<mat-form-field><input matInput [placeholder]="placeholder"></mat-form-field>` }) class MatInputPlaceholderAttrTestComponent { placeholder: string = ''; } @Component({ template: `<mat-form-field><input matInput><mat-hint>{{label}}</mat-hint></mat-form-field>` }) class MatInputHintLabel2TestController { label: string = ''; } @Component({ template: `<mat-form-field [hintLabel]="label"><input matInput></mat-form-field>` }) class MatInputHintLabelTestController { label: string = ''; } @Component({ template: `<mat-form-field><input matInput type="file"></mat-form-field>` }) class MatInputInvalidTypeTestController {} @Component({ template: ` <mat-form-field> <input matInput placeholder="Hello"> <mat-placeholder>World</mat-placeholder> </mat-form-field>` }) class MatInputInvalidPlaceholderTestController {} @Component({ template: ` <mat-form-field hintLabel="Hello"> <input matInput> <mat-hint>World</mat-hint> </mat-form-field>` }) class MatInputInvalidHint2TestController {} @Component({ template: ` <mat-form-field> <input matInput> <mat-hint>Hello</mat-hint> <mat-hint>World</mat-hint> </mat-form-field>` }) 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>` }) 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>` }) class MatInputMultipleHintMixedTestController {} @Component({ template: ` <mat-form-field> <input matInput type="date" placeholder="Placeholder"> </mat-form-field>` }) class MatInputDateTestController {} @Component({ template: ` <mat-form-field> <input matInput type="text" placeholder="Placeholder"> </mat-form-field>` }) class MatInputTextTestController {} @Component({ template: ` <mat-form-field> <input matInput type="password" placeholder="Placeholder"> </mat-form-field>` }) class MatInputPasswordTestController {} @Component({ template: ` <mat-form-field> <input matInput type="number" placeholder="Placeholder"> </mat-form-field>` }) class MatInputNumberTestController {} @Component({ template: ` <mat-form-field> <input matInput type="number" placeholder="Placeholder" [(ngModel)]="value"> </mat-form-field>` }) class MatInputZeroTestController { value = 0; } @Component({ template: ` <mat-form-field> <input matInput placeholder="Label" [value]="value"> </mat-form-field>` }) class MatInputWithValueBinding { value: string = 'Initial'; } @Component({ template: ` <mat-form-field floatLabel="never"> <input matInput placeholder="Label"> </mat-form-field> ` }) class MatInputWithStaticLabel {} @Component({ template: ` <mat-form-field [floatLabel]="shouldFloat"> <input matInput placeholder="Label"> </mat-form-field>` }) class MatInputWithDynamicLabel { shouldFloat: string = 'always'; } @Component({ template: ` <mat-form-field> <textarea matInput [rows]="rows" [cols]="cols" [wrap]="wrap" placeholder="Snacks"></textarea> </mat-form-field>` }) class MatInputTextareaWithBindings { rows: number = 4; cols: number = 8; wrap: string = 'hard'; } @Component({ template: `<mat-form-field><input></mat-form-field>` }) class MatInputMissingMatInputTestCo