diff --git a/src/lib/input/input-container.spec.ts b/src/lib/input/input-container.spec.ts index 584ea076fb65..f5693902a124 100644 --- a/src/lib/input/input-container.spec.ts +++ b/src/lib/input/input-container.spec.ts @@ -596,17 +596,21 @@ describe('MdInputContainer', function () { let fixture: ComponentFixture; let testComponent: MdInputContainerWithFormErrorMessages; let containerEl: HTMLElement; + let inputEl: HTMLElement; beforeEach(() => { fixture = TestBed.createComponent(MdInputContainerWithFormErrorMessages); fixture.detectChanges(); testComponent = fixture.componentInstance; containerEl = fixture.debugElement.query(By.css('md-input-container')).nativeElement; + inputEl = fixture.debugElement.query(By.css('input')).nativeElement; }); it('should not show any errors if the user has not interacted', () => { expect(testComponent.formControl.untouched).toBe(true, 'Expected untouched form control'); expect(containerEl.querySelectorAll('md-error').length).toBe(0, 'Expected no error messages'); + 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', async(() => { @@ -621,6 +625,8 @@ describe('MdInputContainer', function () { .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('md-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".'); }); })); @@ -638,6 +644,8 @@ describe('MdInputContainer', function () { .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('md-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".'); }); })); @@ -650,9 +658,12 @@ describe('MdInputContainer', function () { groupFixture.detectChanges(); component = groupFixture.componentInstance; containerEl = groupFixture.debugElement.query(By.css('md-input-container')).nativeElement; + inputEl = groupFixture.debugElement.query(By.css('input')).nativeElement; expect(component.formGroup.invalid).toBe(true, 'Expected form control to be invalid'); expect(containerEl.querySelectorAll('md-error').length).toBe(0, 'Expected no error messages'); + 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'); @@ -666,6 +677,8 @@ describe('MdInputContainer', function () { .toContain('mat-input-invalid', 'Expected container to have the invalid CSS class.'); expect(containerEl.querySelectorAll('md-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".'); }); })); diff --git a/src/lib/input/input-container.ts b/src/lib/input/input-container.ts index c7ffb4d47f5d..ebacbaf5f3ef 100644 --- a/src/lib/input/input-container.ts +++ b/src/lib/input/input-container.ts @@ -112,6 +112,7 @@ export class MdSuffix {} '[disabled]': 'disabled', '[required]': 'required', '[attr.aria-describedby]': 'ariaDescribedby || null', + '[attr.aria-invalid]': '_isErrorState()', '(blur)': '_onBlur()', '(focus)': '_onFocus()', '(input)': '_onInput()', @@ -209,7 +210,9 @@ export class MdInputDirective { constructor(private _elementRef: ElementRef, private _renderer: Renderer2, - @Optional() @Self() public _ngControl: NgControl) { + @Optional() @Self() public _ngControl: NgControl, + @Optional() private _parentForm: NgForm, + @Optional() private _parentFormGroup: FormGroupDirective) { // Force setter to be called in case id was not specified. this.id = this.id; @@ -232,6 +235,17 @@ export class MdInputDirective { // FormsModule or ReactiveFormsModule, because Angular forms also listens to input events. } + /** Whether the input is in an error state. */ + _isErrorState(): boolean { + const control = this._ngControl; + const isInvalid = control && control.invalid; + const isTouched = control && control.touched; + const isSubmitted = (this._parentFormGroup && this._parentFormGroup.submitted) || + (this._parentForm && this._parentForm.submitted); + + return !!(isInvalid && (isTouched || isSubmitted)); + } + /** Make sure the input is a supported type. */ private _validateType() { if (MD_INPUT_INVALID_TYPES.indexOf(this._type) !== -1) { @@ -274,7 +288,7 @@ export class MdInputDirective { // Remove align attribute to prevent it from interfering with layout. '[attr.align]': 'null', '[class.mat-input-container]': 'true', - '[class.mat-input-invalid]': '_isErrorState()', + '[class.mat-input-invalid]': '_mdInputChild._isErrorState()', '[class.mat-focused]': '_mdInputChild.focused', '[class.ng-untouched]': '_shouldForward("untouched")', '[class.ng-touched]': '_shouldForward("touched")', @@ -352,9 +366,7 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC constructor( public _elementRef: ElementRef, - private _changeDetectorRef: ChangeDetectorRef, - @Optional() private _parentForm: NgForm, - @Optional() private _parentFormGroup: FormGroupDirective) { } + private _changeDetectorRef: ChangeDetectorRef) { } ngAfterContentInit() { this._validateInputChild(); @@ -388,20 +400,10 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC /** Focuses the underlying input. */ _focusInput() { this._mdInputChild.focus(); } - /** Whether the input container is in an error state. */ - _isErrorState(): boolean { - const control = this._mdInputChild._ngControl; - const isInvalid = control && control.invalid; - const isTouched = control && control.touched; - const isSubmitted = (this._parentFormGroup && this._parentFormGroup.submitted) || - (this._parentForm && this._parentForm.submitted); - - return !!(isInvalid && (isTouched || isSubmitted)); - } - /** Determines whether to display hints or errors. */ _getDisplayedMessages(): 'error' | 'hint' { - return (this._errorChildren.length > 0 && this._isErrorState()) ? 'error' : 'hint'; + let input = this._mdInputChild; + return (this._errorChildren.length > 0 && input._isErrorState()) ? 'error' : 'hint'; } /**