mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(input): set aria-invalid on mdInput element (#4757)
* Sets the `aria-invalid` attribute on the `mdInput` node, depending on whether it is invalid. * Moves the `_isErrorState` logic to the input child to avoid a circular dependency.
This commit is contained in:
committed by
mmalerba
parent
be8d3b23d2
commit
0c03946ac6
@@ -596,17 +596,21 @@ describe('MdInputContainer', function () {
|
||||
let fixture: ComponentFixture<MdInputContainerWithFormErrorMessages>;
|
||||
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".');
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
@@ -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()',
|
||||
@@ -210,7 +211,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;
|
||||
@@ -233,6 +236,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) {
|
||||
@@ -275,7 +289,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")',
|
||||
@@ -354,9 +368,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();
|
||||
@@ -390,20 +402,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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user