Skip to content

Commit

Permalink
feat(datepcker): Add aria attribute #8080 (#8199)
Browse files Browse the repository at this point in the history
  • Loading branch information
IvayloG authored Sep 25, 2020
1 parent 1f61938 commit 625b3ef
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes for each version of this project will be documented in this file.

## 9.1.18

### General
- `IgxDatePicker`
- Added `aria-labelledby` property for the input field. This will ensure the users of assistive technologies will also know what component is used for, upon input focus.

## 9.1.16

### General
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, ViewChild, ElementRef, EventEmitter, QueryList } from '@angular/core';
import { async, fakeAsync, TestBed, tick, flush, ComponentFixture } from '@angular/core/testing';
import { Component, ViewChild, ElementRef, EventEmitter, QueryList, Renderer2, DebugElement } from '@angular/core';
import { fakeAsync, TestBed, tick, flush, ComponentFixture, async } from '@angular/core/testing';
import { FormsModule, FormGroup, FormBuilder, ReactiveFormsModule, Validators, NgControl } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
Expand Down Expand Up @@ -280,6 +280,43 @@ describe('IgxDatePicker', () => {

});

describe('ARIA Tests', () => {
let labelID: string;
let inputLabelledBy: string;
let dom: DebugElement;

it('ARIA Test for a picker with an input group template', () => {
const fixture = TestBed.createComponent(IgxDatePickerRetemplatedComponent);
fixture.detectChanges();
dom = fixture.debugElement;

labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
expect(inputLabelledBy).toEqual(labelID);
});

it('ARIA Test for picker with a dialog mode', () => {
const fixture = TestBed.createComponent(IgxDatePickerTestComponent);
fixture.detectChanges();
dom = fixture.debugElement;

labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
expect(inputLabelledBy).toEqual(labelID);
});


it('ARIA Test for picker with a dropdown mode', () => {
const fixture = TestBed.createComponent(IgxDatePickerOpeningComponent);
fixture.detectChanges();
dom = fixture.debugElement;

labelID = dom.query(By.directive(IgxLabelDirective)).nativeElement.id;
inputLabelledBy = dom.query(By.directive(IgxInputDirective)).nativeElement.getAttribute('aria-labelledby');
expect(inputLabelledBy).toEqual(labelID);
});
});

describe('DatePicker with passed date', () => {
// configureTestSuite();
let fixture: ComponentFixture<IgxDatePickerWithPassedDateComponent>;
Expand Down Expand Up @@ -1416,6 +1453,7 @@ describe('IgxDatePicker', () => {
let moduleRef;
let injector;
let inputGroup: IgxInputGroupComponent;
let renderer2: Renderer2;

beforeEach(() => {
ngModel = {
Expand All @@ -1438,11 +1476,14 @@ describe('IgxDatePicker', () => {
};
moduleRef = {};
injector = { get: () => ngModel };

inputGroup = new IgxInputGroupComponent(null, null);
renderer2 = jasmine.createSpyObj('Renderer2', ['setAttribute'], [{}, 'aria-labelledby', 'test-label-id-1']);
spyOn(renderer2, 'setAttribute').and.callFake(() => {});
});

it('should initialize date picker with required correctly', () => {
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
datePicker['_inputGroup'] = inputGroup;
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
ngModel.control.validator = () => ({ required: true });
Expand All @@ -1455,7 +1496,7 @@ describe('IgxDatePicker', () => {
});

it('should initialize date picker with required correctly with user template input-group', () => {
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
datePicker['_inputGroupUserTemplate'] = inputGroup;
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
ngModel.control.validator = () => ({ required: true });
Expand All @@ -1468,7 +1509,7 @@ describe('IgxDatePicker', () => {
});

it('should update inputGroup isRequired correctly', () => {
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector, renderer2);
datePicker['_inputGroup'] = inputGroup;
datePicker['_inputDirectiveUserTemplates'] = new QueryList();
datePicker.ngOnInit();
Expand Down Expand Up @@ -1565,14 +1606,16 @@ export class IgxDatePickerNgModelComponent {
<igx-date-picker>
<ng-template igxDatePickerTemplate let-displayData="displayData">
<igx-input-group>
<label igxLabel>Date</label>
<label igxLabel>Custom Date Label</label>
<input igxInput [value]="displayData" required />
</igx-input-group>
</ng-template>
</igx-date-picker>
`
})
export class IgxDatePickerRetemplatedComponent { }
export class IgxDatePickerRetemplatedComponent {
@ViewChild(IgxDatePickerComponent, { static: true }) public datePicker: IgxDatePickerComponent;
}

@Component({
template: `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
Injector,
AfterViewChecked,
ContentChildren,
QueryList
QueryList, Renderer2
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl, NG_VALIDATORS, ValidationErrors } from '@angular/forms';
import {
Expand All @@ -32,7 +32,13 @@ import {
isDateInRanges
} from '../calendar/public_api';
import { IgxIconModule } from '../icon/public_api';
import { IgxInputGroupModule, IgxInputDirective, IgxInputGroupComponent, IgxInputState } from '../input-group/public_api';
import {
IgxInputGroupModule,
IgxInputDirective,
IgxInputGroupComponent,
IgxInputState,
IgxLabelDirective
} from '../input-group/public_api';
import { Subject, fromEvent, animationFrameScheduler, interval, Subscription } from 'rxjs';
import { filter, takeUntil, throttle } from 'rxjs/operators';
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
Expand Down Expand Up @@ -385,7 +391,8 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
public element: ElementRef,
private _cdr: ChangeDetectorRef,
private _moduleRef: NgModuleRef<any>,
private _injector: Injector) { }
private _injector: Injector,
private _renderer: Renderer2) { }

/**
* Gets the input group template.
Expand Down Expand Up @@ -638,6 +645,13 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
@ContentChildren(IgxInputDirective, { descendants: true })
private _inputDirectiveUserTemplates: QueryList<IgxInputDirective>;

@ViewChild(IgxLabelDirective)
protected _labelDirective: IgxLabelDirective;

/** @hidden @internal */
@ContentChild(IgxLabelDirective)
public _labelDirectiveUserTemplate: IgxLabelDirective;

/**
* @hidden
*/
Expand Down Expand Up @@ -783,6 +797,11 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
return this._inputDirective || this._inputDirectiveUserTemplates.first || null;
}

/** @hidden @internal */
public get labelDirective(): IgxLabelDirective {
return this._labelDirective || this._labelDirectiveUserTemplate || null;
}

/** @hidden @internal */
public ngOnInit(): void {
this._positionSettings = {
Expand Down Expand Up @@ -899,6 +918,10 @@ export class IgxDatePickerComponent implements IDatePicker, ControlValueAccessor
}
// TODO: persist validation state when dynamically changing 'dropdown' to 'dialog' ot vice versa.
// For reference -> it is currently persisted if a user template is passed (as template is not recreated)

if (this.labelDirective) {
this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
}
}

protected onStatusChanged() {
Expand Down

0 comments on commit 625b3ef

Please sign in to comment.