By Marcy Sutton / @MarcySutton
Senior Front-End Engineer, Deque Systems
…by making the Internet more accessible.
<label for="roundTrip">
<input type="radio" id="roundTrip">
Round Trip
</label>
<button>Button</button>
CSS changes button text to uppercase following the Material Design guidelines, which shows in the accessibility tree
https://www.w3.org/TR/wai-aria/
role="button"
aria-checked="false"
aria-haspopup="true"
aria-label="Close modal"
aria-checked code example and link to documentation https://docs.angularjs.org/api/ngAria
Testing for accessibility in:
Good for:
it('should apply aria-hidden="true" when parent has valid label',
inject(function() {
var el = make('<md-radio-button aria-label="Squishy Avatar" '+
'role="radio" tabindex="0"> '+
'<div class="md-container"></div> '+
'<div class="md-label"> '+
'<md-icon md-svg-icon="android"></md-icon> '+
'</div></md-radio-button>');
expect(el.find('md-icon').attr('aria-hidden')).toEqual('true');
}));
it('closes on escape', inject(function($document, $mdConstant) {
var menu = setup();
openMenu(menu);
expect(getOpenMenuContainer(menu).length).toBe(1);
var openMenuEl = $document[0].querySelector('md-menu-content');
pressKey(openMenuEl, $mdConstant.KEY_CODE.ESCAPE);
waitForMenuClose();
expect(getOpenMenuContainer(menu).length).toBe(0);
}));
Good for:
Save yourself from:
https://github.com/dequelabs/axe-core
†There’s also a supported/enterprise option, Worldspace Attest
<md-toolbar layout="row">
<h1 layout-align-gt-sm="center center" class="md-toolbar-tools">My Favorite Muppets</h1>
</md-toolbar>
<div layout="row" flex class="content-wrapper">
<md-sidenav layout="column" class="md-sidenav-left md-whiteframe-z2" md-component-id="left">
<md-list class="muppet-list">
<md-item ng-repeat="it in muppets">
<md-item-content>
<md-button ng-click="selectMuppet(it)" ng-class="{'selected' : it === selected }">
<img ng-src="{{it.iconurl}}" class="face" alt="">
{{it.name}}
</md-button>
</md-item-content>
</md-item>
</md-list>
</md-sidenav>
<div layout="column" flex class="content-wrapper" id="primary-col">
<md-content layout="column" flex class="md-padding">
<h2>{{selected.name}}</h2>
<p>{{selected.content}}</p>
<div class="cell">
<img ng-src="{{selected.imgurl}}" alt="{{selected.imgalt}}">
</div>
</md-content>
</div>
</div>
var AxeBuilder = require('axe-webdriverjs');
describe('view1', function() {
beforeEach(function() { browser.get('index.html'); });
it('should change Muppets', function() {
element.all(by.css('[ng-click="selectMuppet(it)"]')).first().
sendKeys(protractor.Key.ENTER);
expect(element.all(by.css('#primary-col h2')).first().getText()).
toMatch('Animal');
});
it('should have no accessibility violations', function(done) {
AxeBuilder(browser)
.analyze(function(results) {
if (results.violations.length > 0) {
console.log(results.violations);
}
expect(results.violations.length).toBe(0);
done();
});
});
Testing for accessibility in:
Test method | Rendered | Attached |
---|---|---|
Unit | ||
Keyboard mechanics | X | X |
Labeling | ||
ARIA attributes | ~ | ~ |
End-to-end | ||
Color contrast | X | X |
Event handlers | ~ | ~ |
A11y API Audit | X | X |
describe('mdCheckbox with provided aria-labelledby ', () => {
let checkboxDebugElement: DebugElement;
let checkboxNativeElement: HTMLElement;
let inputElement: HTMLInputElement;
it('should use the provided aria-labelledby', () => {
fixture = TestBed.createComponent(CheckboxWithAriaLabelledby);
checkboxDebugElement = fixture.debugElement.query(By.directive(MdCheckbox));
checkboxNativeElement = checkboxDebugElement.nativeElement;
inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');
fixture.detectChanges();
expect(inputElement.getAttribute('aria-labelledby')).toBe('some-id');
});
/** Simple test component with aria-labelledby set. */
@Component({
template: ` `
})
class CheckboxWithAriaLabelledby {}
it('toggles "aria-checked" on the host element', function() {
builder.createAsync(CheckboxController).then(function(fixture) {
let el = fixture.debugElement.query(By.css('.md-checkbox'));
expect(el.nativeElement.getAttribute('aria-checked')).toEqual('false');
controller = fixture.componentInstance;
changePromise = waitForChange();
controller.isChecked = true;
fixture.detectChanges();
expect(el.nativeElement.getAttribute('aria-checked')).toEqual('true');
});
});
it('should respond to keyboard events', () => {
let fixture = TestBed.createComponent(Board);
fixture.detectChanges();
var event1 = new KeyboardEvent('keydown', { key: '1' });
var event3 = new KeyboardEvent('keydown', { key: '3' });
window.dispatchEvent(event1);
window.dispatchEvent(event3);
fixture.detectChanges();
let board = fixture.nativeElement;
let box = board.querySelectorAll('.ttt-box')[2];
expect(box.textContent).toContain('x');
});
~ with ~
/*----- menu.e2e.ts -------*/
import { MenuPage } from './menu-page';
describe('menu', () => {
let page: MenuPage;
beforeEach(function() {
page = new MenuPage();
});
describe('keyboard events', () => {
beforeEach(() => {
// click start button to avoid tabbing past navigation
page.start().click();
page.pressKey(protractor.Key.TAB);
});
it('should auto-focus the first item when opened with keyboard', () => {
page.pressKey(protractor.Key.ENTER);
page.expectFocusOn(page.items(0));
});
it('should focus subsequent items when down arrow is pressed', () => {
page.pressKey(protractor.Key.ENTER);
page.pressKey(protractor.Key.DOWN);
page.expectFocusOn(page.items(1));
});
});
});
/*----- menu-page.ts -------*/
pressKey(key: any): void {
browser.actions().sendKeys(key).perform();
}
expectFocusOn(el: ElementFinder): void {
expect(browser.driver.switchTo().activeElement().getInnerHtml())
.toBe(el.getInnerHtml());
}
DOM helper getKeyEvent does not use keyCode
https://github.com/angular/angular/issues/9419
import * as axe from 'axe-core';
...
it('should have no accessibility violations', function(done) {
browser.executeScript(axe.source);
// Run A11Y tests in the browsers event loop.
browser.executeAsyncScript((resolve: any) => {
return new Promise<axe.AxeResults>(res => {
axe.a11yCheck(document, {}, resolve)
});
}).then((results: any) => {
if (results.violations.length > 0) {
console.log(results.violations);
}
expect(results.violations.length).toBe(0);
done();
});
});