-
Notifications
You must be signed in to change notification settings - Fork 20
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
Add axe accessibility testing #33
Changes from all commits
3bd7524
2ecfaac
50453e5
46c86e3
5636164
40bd6cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
### Running tests | ||
|
||
The default rake task runs all tests: | ||
``` | ||
bundle exec rake | ||
``` | ||
|
||
Javascript is tested using Jasmine and the [Jasmine gem](https://github.com/pivotal/jasmine-gem). Tests can be run either in the browser or on the command line via the dummy app’s tasks: | ||
```sh | ||
# browser | ||
bundle exec rake app:jasmine | ||
|
||
# command line | ||
bundle exec rake app:jasmine:ci | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
(function (window, document, axe) { | ||
window.GOVUK = window.GOVUK || {} | ||
|
||
function AccessibilityTest (selector, callback) { | ||
if (typeof callback !== 'function') { | ||
return | ||
} | ||
|
||
if (!document.querySelector(selector)) { | ||
return callback('No selector "' + selector + '" found') | ||
} | ||
|
||
var axeOptions = { | ||
restoreScroll: true, | ||
include: [selector] | ||
} | ||
|
||
axe.run(axeOptions, function (err, results) { | ||
if (err) { | ||
callback('aXe Error: ' + err) | ||
} | ||
|
||
var violations = (typeof results === 'undefined') ? [] : results.violations | ||
|
||
if (violations.length === 0) { | ||
return callback('No accessibility issues found') | ||
} | ||
|
||
var errorText = ( | ||
'\n' + 'Accessibility issues at ' + | ||
results.url + '\n\n' + | ||
violations.map(function (violation) { | ||
var help = 'Problem: ' + violation.help | ||
var helpUrl = 'Try fixing it with this help: ' + _formatHelpUrl(violation.helpUrl) | ||
var htmlAndTarget = violation.nodes.map(_renderNode).join('\n\n') | ||
|
||
return [ | ||
help, | ||
htmlAndTarget, | ||
helpUrl | ||
].join('\n\n\n') | ||
}).join('\n\n- - -\n\n') | ||
) | ||
callback(undefined, errorText) | ||
}) | ||
} | ||
|
||
var _formatHelpUrl = function (helpUrl) { | ||
if (axe.version.indexOf('alpha') === -1) { | ||
console.warn('Deprecation warning: helpUrl formatting is no longer needed so can be deleted') | ||
return helpUrl | ||
} | ||
return helpUrl.replace('3.0.0-alpha', '2.3') | ||
} | ||
|
||
var _renderNode = function (node) { | ||
return ( | ||
' Check the HTML:\n' + | ||
' `' + node.html + '`\n' + | ||
' found with the selector:\n' + | ||
' "' + node.target.join(', ') + '"' | ||
) | ||
} | ||
|
||
var _throwUncaughtError = function (error) { | ||
// aXe swallows throw errors so we pass it through a setTimeout | ||
// so that it's not in aXe's context | ||
setTimeout(function () { | ||
throw new Error(error) | ||
}, 0) | ||
} | ||
|
||
window.GOVUK.AccessibilityTest = AccessibilityTest | ||
|
||
// Cut the mustard at IE9+ since aXe only works with those browsers. | ||
// http://responsivenews.co.uk/post/18948466399/cutting-the-mustard | ||
if (document.addEventListener) { | ||
document.addEventListener('DOMContentLoaded', function () { | ||
AccessibilityTest('[data-module="test-a11y"]', function (err, results) { | ||
if (err) { | ||
return | ||
} | ||
_throwUncaughtError(results) | ||
}) | ||
}) | ||
} | ||
})(window, document, window.axe) |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -166,4 +166,3 @@ html { | |
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
<div class="component-guide-preview"> | ||
<div data-module="test-a11y" class="component-guide-preview"> | ||
<%= render @component_doc.partial_path, fixture.html_safe_data %> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* global describe, afterEach, it, expect */ | ||
|
||
var TEST_SELECTOR = '.js-test-a11y' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need updating too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had some troubles with using a consistent data attribute, I don't think axe likes it as much when outputting the targeting. Since this spec test the behaviour rather than how it works in the guide, this is fine. |
||
|
||
var AccessibilityTest = window.GOVUK.AccessibilityTest | ||
|
||
function addToDom (html, style) { | ||
var div = document.createElement('div') | ||
var htmlToInject = '' | ||
htmlToInject += '<div class="' + TEST_SELECTOR.replace('.', '') + '">' | ||
if (style) { | ||
htmlToInject += '<style>' + style + '</style>' | ||
} | ||
htmlToInject += html + '</div>' | ||
div.innerHTML = htmlToInject | ||
document.getElementsByTagName('body')[0].appendChild(div) | ||
} | ||
|
||
function removeFromDom (selector) { | ||
var nodeToRemove = document.querySelector(selector) | ||
if (nodeToRemove) { | ||
nodeToRemove.parentNode.removeChild(nodeToRemove) | ||
} | ||
} | ||
|
||
function renderErrorMessage (option) { | ||
var url = window.location.href | ||
var message = '' | ||
if (!option.skipHeader) { | ||
message += '\nAccessibility issues at ' + url + '\n\n' | ||
} | ||
message += ( | ||
'Problem: ' + option.problem + '\n' + | ||
'\n' + | ||
'\n' + | ||
' Check the HTML:\n' + | ||
' `' + option.html + '`\n' + | ||
' found with the selector:\n' + | ||
' "' + option.selector + '"\n' + | ||
'\n' + | ||
'\n' + | ||
'Try fixing it with this help: ' + option.helpUrl | ||
) | ||
return message | ||
} | ||
|
||
describe('AccessibilityTest', function () { | ||
afterEach(function () { | ||
removeFromDom(TEST_SELECTOR) | ||
}) | ||
|
||
it('should do nothing if no callback is specified', function () { | ||
AccessibilityTest(TEST_SELECTOR) | ||
}) | ||
|
||
it('should error if no selector is found', function (done) { | ||
AccessibilityTest(TEST_SELECTOR, function (err, result) { | ||
expect(result).toBe(undefined) | ||
expect(err).toBe('No selector "' + TEST_SELECTOR + '" found') | ||
done() | ||
}) | ||
}) | ||
|
||
it('should throw if there\'s a contrast issue', function (done) { | ||
addToDom('<a href="#">Low contrast</a>', 'a { background: white; color: #ddd }') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice 👍 |
||
|
||
AccessibilityTest(TEST_SELECTOR, function (err, result) { | ||
if (err) { | ||
throw err | ||
} | ||
|
||
var errorMessage = renderErrorMessage({ | ||
problem: 'Elements must have sufficient color contrast', | ||
html: '<a href="#" style="">Low contrast</a>', | ||
selector: TEST_SELECTOR + ' > a', | ||
helpUrl: 'https://dequeuniversity.com/rules/axe/2.3/color-contrast?application=axeAPI' | ||
}) | ||
|
||
expect(result).toBe(errorMessage) | ||
|
||
done() | ||
}) | ||
}) | ||
|
||
it('should throw if there\'s a alt tag issue', function (done) { | ||
addToDom('<img src="">') | ||
|
||
AccessibilityTest(TEST_SELECTOR, function (err, result) { | ||
if (err) { | ||
throw err | ||
} | ||
|
||
var errorMessage = renderErrorMessage({ | ||
problem: 'Images must have alternate text', | ||
html: '<img src="">', | ||
selector: TEST_SELECTOR + ' > img', | ||
helpUrl: 'https://dequeuniversity.com/rules/axe/2.3/image-alt?application=axeAPI' | ||
}) | ||
|
||
expect(result).toBe(errorMessage) | ||
|
||
done() | ||
}) | ||
}) | ||
|
||
it('should throw on multiple issues', function (done) { | ||
addToDom('<img src=""><a href="#">Low contrast</a>', 'a { background: white; color: #ddd }') | ||
|
||
AccessibilityTest(TEST_SELECTOR, function (err, result) { | ||
if (err) { | ||
throw err | ||
} | ||
|
||
var errorMessage = ( | ||
renderErrorMessage({ | ||
problem: 'Elements must have sufficient color contrast', | ||
html: '<a href="#" style="">Low contrast</a>', | ||
selector: TEST_SELECTOR + ' > a', | ||
helpUrl: 'https://dequeuniversity.com/rules/axe/2.3/color-contrast?application=axeAPI' | ||
}) + | ||
'\n\n- - -\n\n' + | ||
renderErrorMessage({ | ||
skipHeader: true, | ||
problem: 'Images must have alternate text', | ||
html: '<img src="">', | ||
selector: TEST_SELECTOR + ' > img', | ||
helpUrl: 'https://dequeuniversity.com/rules/axe/2.3/image-alt?application=axeAPI' | ||
}) | ||
) | ||
|
||
expect(result).toBe(errorMessage) | ||
|
||
done() | ||
}) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whitespace here: run through standardjs linter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some space 👍