Skip to content
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

fix(slider): improve accessibility by correcting ARIA attributes and tab index - master #14793

Merged
merged 11 commits into from
Oct 22, 2024
Merged
132 changes: 100 additions & 32 deletions projects/igniteui-angular/src/lib/slider/slider.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('IgxSlider', () => {
SliderMinMaxComponent,
SliderTestComponent,
SliderWithLabelsComponent,
RangeSliderTestComponent,
RangeSliderWithLabelsComponent,
RangeSliderWithCustomTemplateComponent,
SliderTicksComponent,
Expand Down Expand Up @@ -897,22 +898,6 @@ describe('IgxSlider', () => {
expect(slider.upperBound).toBe(100);
expect(slider.lowerBound).toBe(0);
});

it('aria properties should be successfully applied', () => {
const sliderElement = fixture.nativeElement.querySelector('igx-slider');
const sliderRole = fixture.nativeElement.querySelector('igx-slider[role="slider"]');

expect(sliderElement).toBeDefined();
expect(sliderRole).toBeDefined();

const minValue = parseInt(sliderElement.getAttribute('aria-valuemin'), 10);
const maxValue = parseInt(sliderElement.getAttribute('aria-valuemax'), 10);
const readOnly = sliderElement.getAttribute('aria-readonly');

expect(minValue).toBe(slider.minValue);
expect(maxValue).toBe(slider.maxValue);
expect(readOnly).toBe('false');
});
});

describe('Slider type: Range - List View', () => {
Expand Down Expand Up @@ -1196,22 +1181,6 @@ describe('IgxSlider', () => {
expect(slider.upperBound).toBe(slider.maxValue);
expect(slider.lowerBound).toBe(slider.minValue);
});

it('aria properties should be successfully applied', () => {
const sliderElement = fixture.nativeElement.querySelector('igx-slider');
const sliderRole = fixture.nativeElement.querySelector('igx-slider[role="slider"]');

expect(sliderElement).toBeDefined();
expect(sliderRole).toBeDefined();

const minValue = parseInt(sliderElement.getAttribute('aria-valuemin'), 10);
const maxValue = parseInt(sliderElement.getAttribute('aria-valuemax'), 10);
const readOnly = sliderElement.getAttribute('aria-readonly');

expect(minValue).toBe(slider.minValue);
expect(maxValue).toBe(slider.maxValue);
expect(readOnly).toBe('false');
});
});

describe('General Tests', () => {
Expand Down Expand Up @@ -1849,6 +1818,94 @@ describe('IgxSlider', () => {
}));
});

describe('Accessibility: ARIA Attributes', () => {
let fixture: ComponentFixture<SliderInitializeTestComponent>;
let slider: IgxSliderComponent;

beforeEach(() => {
fixture = TestBed.createComponent(SliderInitializeTestComponent);
slider = fixture.componentInstance.slider;
fixture.detectChanges();
});

it('aria properties should be successfully applied', () => {
const sliderElement = fixture.nativeElement.querySelector('igx-slider');
const sliderRole = fixture.nativeElement.querySelector('igx-slider[role="slider"]');

expect(sliderElement).toBeDefined();
expect(sliderRole).toBeDefined();

const minValue = parseInt(sliderElement.getAttribute('aria-valuemin'), 10);
const maxValue = parseInt(sliderElement.getAttribute('aria-valuemax'), 10);
const readOnly = sliderElement.getAttribute('aria-readonly');

expect(minValue).toBe(slider.minValue);
expect(maxValue).toBe(slider.maxValue);
expect(readOnly).toBe('false');
});

it('should maintain accessibility attributes when slider is disabled', () => {
const sliderElement = fixture.nativeElement.querySelector('igx-slider');
const sliderRole = fixture.nativeElement.querySelector('igx-slider[role="slider"]');

expect(sliderElement).toBeDefined();
expect(sliderRole).toBeDefined();

slider.disabled = true;
fixture.detectChanges();

const disabled = sliderElement.getAttribute('aria-readonly');

expect(disabled).toBe('true');
});

it('should apply aria-valuenow to the thumbs', fakeAsync(() => {
fixture = TestBed.createComponent(RangeSliderTestComponent);
slider = fixture.componentInstance.slider;
fixture.detectChanges();
tick();

const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;

expect(thumbFrom.getAttribute('aria-valuenow')).toBe(String(slider.lowerLabel));
expect(thumbTo.getAttribute('aria-valuenow')).toBe(String(slider.upperLabel));
}));

it('should update aria-valuenow when the slider value changes', fakeAsync(() => {
fixture = TestBed.createComponent(RangeSliderTestComponent);
slider = fixture.componentInstance.slider;
fixture.detectChanges();
tick();

const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;

expect(thumbFrom.getAttribute('aria-valuenow')).toBe(String(slider.lowerLabel));
expect(thumbTo.getAttribute('aria-valuenow')).toBe(String(slider.upperLabel));

slider.value = {
lower: 30,
upper: 70
};
fixture.detectChanges();
tick();

expect(thumbFrom.getAttribute('aria-valuenow')).toBe('30');
expect(thumbTo.getAttribute('aria-valuenow')).toBe('70');
}));

it('should have correct aria-labelledby references for each thumb', () => {
fixture = TestBed.createComponent(RangeSliderTestComponent);
fixture.detectChanges();

const thumbFrom = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
const thumbTo = fixture.debugElement.query(By.css(THUMB_TO_CLASS)).nativeElement;

expect(thumbFrom.getAttribute('aria-labelledby')).toBe('slider-label-from');
expect(thumbTo.getAttribute('aria-labelledby')).toBe('slider-label-to');
});
});

const verifySecondaryTicsLabelsAreHidden = (ticks, hidden) => {
const allTicks = Array.from(ticks.nativeElement.querySelectorAll(`${SLIDER_GROUP_TICKS_CLASS}`));
Expand Down Expand Up @@ -1983,6 +2040,17 @@ class SliderWithLabelsComponent {
@ViewChild(IgxSliderComponent, { read: IgxSliderComponent, static: true }) public slider: IgxSliderComponent;
}

@Component({
template: `<igx-slider #slider [type]="type">
</igx-slider>`,
standalone: true,
imports: [IgxSliderComponent]
})
class RangeSliderTestComponent {
@ViewChild(IgxSliderComponent, { static: true }) public slider: IgxSliderComponent;
public type = IgxSliderType.RANGE;
}

@Component({
template: `
<igx-slider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ export class IgxSliderThumbComponent implements OnInit, OnDestroy {
@HostBinding('attr.z-index')
public zIndex = 0;

@HostBinding('attr.aria-valuenow')
public get ariaValueNow() {
return this.value;
}

@HostBinding('attr.aria-labelledby')
georgianastasov marked this conversation as resolved.
Show resolved Hide resolved
public get ariaLabelledbyAttr() {
return this.type === SliderHandle.FROM ? 'slider-label-from' : 'slider-label-to';
}

@HostBinding('class.igx-slider-thumb-to--focused')
public focused = false;

Expand Down
Loading