Skip to content

fix(input): set aria-invalid on mdInput element #4757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/lib/input/input-container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -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".');
});
}));

Expand All @@ -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".');
});
}));

Expand All @@ -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');

Expand All @@ -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".');
});
}));

Expand Down
36 changes: 19 additions & 17 deletions src/lib/input/input-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()',
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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")',
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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';
}

/**
Expand Down