Skip to content

Commit

Permalink
Add accessibility testing via aXe
Browse files Browse the repository at this point in the history
These tests will look for the class '.js-test-a11y' and run axe against them,
it'll then throw an error to be caught in dev tools or better in an applications'
integration tests.
  • Loading branch information
NickColley committed Aug 24, 2017
1 parent 739ef94 commit 3d454df
Show file tree
Hide file tree
Showing 15 changed files with 443 additions and 10 deletions.
15 changes: 15 additions & 0 deletions DEVELOPMENT.md
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
```
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,6 @@ This will create a template, scss file and yml documentation file for a new comp
## Licence

[MIT Licence](LICENCE.md)

## Development
For documentation on how to update and develop this gem see [DEVELOPMENT.md](./DEVELOPMENT.md)
8 changes: 2 additions & 6 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)

APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
load 'rails/tasks/engine.rake'


load 'rails/tasks/engine.rake'
load 'rails/tasks/statistics.rake'



require 'bundler/gem_tasks'

require 'rake/testtask'

Rake::TestTask.new(:test) do |t|
Expand All @@ -32,4 +28,4 @@ namespace :assets do
end
end

task default: :spec
task default: [:spec, 'app:jasmine:ci']
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
(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, blah, boo) {
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: ' + 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 _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('.js-test-a11y', function (err, results) {
if (err) {
return
}
_throwUncaughtError(results)
})
})
}
})(window, document, window.axe)
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require_tree ./vendor
//= require_tree .

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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 class="js-test-a11y component-guide-preview">
<%= render @component_doc.partial_path, fixture.html_safe_data %>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<% if @component_fixtures.length > 1 %>
<h2 class="preview-title"><a href="<%= component_fixture_path(@component_doc.id, fixture.id) %>"><%= fixture.name %></a></h2>
<% end %>
<%= render @component_doc.partial_path, fixture.html_safe_data %>
<div class="js-test-a11y">
<%= render @component_doc.partial_path, fixture.html_safe_data %>
</div>
</div>
<% end %>
3 changes: 2 additions & 1 deletion govuk_publishing_components.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Gem::Specification.new do |s|
s.homepage = "https://github.com/alphagov/govuk_publishing_components"
s.license = "MIT"

s.files = Dir["{app,config,db,lib}/**/*", "LICENCE.md", "Rakefile", "README.md"]
s.files = Dir["{app,config,db,lib}/**/*", "DEVELOPMENT.md", "LICENCE.md", "Rakefile", "README.md"]

s.add_dependency "rails", ">= 5.0.0.1"
s.add_dependency "slimmer"
Expand All @@ -25,6 +25,7 @@ Gem::Specification.new do |s|
s.add_development_dependency "govuk-lint", "~> 2.1.0"
s.add_development_dependency "rspec", "~> 3.6"
s.add_development_dependency "capybara", "~> 2.14.4"
s.add_development_dependency "jasmine", "~> 2.4.0"

# Needed to load slimmer test helpers
# https://github.com/alphagov/slimmer/issues/201
Expand Down
7 changes: 7 additions & 0 deletions spec/component_guide/component_guide_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@
end
end

it 'has accessibility testing hooks' do
visit '/component-guide/test-component'
expect(page).to have_selector('.component-guide-preview.js-test-a11y')
end

it 'displays the accessibility acceptance criteria of a component as html using static’s govspeak component' do
visit '/component-guide/test-component'
within ".component-accessibility-criteria" do
Expand All @@ -132,6 +137,7 @@
expect(page).to have_selector('.component-guide-preview-page .test-component-with-params', text: 'Some value')
expect(page).to have_selector('.component-guide-preview-page .preview-title', text: 'Another fixture')
expect(page).to have_selector('.component-guide-preview-page .test-component-with-params', text: 'A different value')
expect(page).to have_selector('.component-guide-preview-page .js-test-a11y')
end

it 'loads a preview page for a specific fixture' do
Expand All @@ -142,5 +148,6 @@

expect(page).to have_no_selector('.component-guide-preview-page .preview-title')
expect(page).to have_selector('.component-guide-preview-page .test-component-with-params', text: 'A different value')
expect(page).to have_selector('.component-guide-preview-page .js-test-a11y')
end
end
139 changes: 139 additions & 0 deletions spec/javascripts/govuk_publishing_components/AccessibilityTestSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* global jasmine, describe, afterEach, beforeEach, it, expect */

var AccessibilityTest = window.GOVUK.AccessibilityTest

function addToDom (html, style) {
var div = document.createElement('div')
var htmlToInject = ''
if (style) {
htmlToInject += '<style>' + style + '</style>'
}
htmlToInject += '<div class="js-test-a11y">' + 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 () {
var selector = '.js-test-a11y'

beforeEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 11000
})

afterEach(function () {
removeFromDom(selector)
})

it('should do nothing if no callback is specified', function () {
AccessibilityTest(selector)
})

it('should error if no selector is found', function (done) {
AccessibilityTest(selector, function (err, result) {
expect(result).toBe(undefined)
expect(err).toBe('No selector "' + 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 }')

AccessibilityTest(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: '.js-test-a11y > a',
helpUrl: 'https://dequeuniversity.com/rules/axe/3.0.0-alpha/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(selector, function (err, result) {
if (err) {
throw err
}

var errorMessage = renderErrorMessage({
problem: 'Images must have alternate text',
html: '<img src="">',
selector: '.js-test-a11y > img',
helpUrl: 'https://dequeuniversity.com/rules/axe/3.0.0-alpha/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(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: '.js-test-a11y > a',
helpUrl: 'https://dequeuniversity.com/rules/axe/3.0.0-alpha/color-contrast?application=axeAPI'
}) +
'\n\n- - -\n\n' +
renderErrorMessage({
skipHeader: true,
problem: 'Images must have alternate text',
html: '<img src="">',
selector: '.js-test-a11y > img',
helpUrl: 'https://dequeuniversity.com/rules/axe/3.0.0-alpha/image-alt?application=axeAPI'
})
)

expect(result).toBe(errorMessage)

done()
})
})
})
Loading

0 comments on commit 3d454df

Please sign in to comment.