Skip to content

Instantly share code, notes, and snippets.

@marcysutton
Last active August 23, 2021 08:20
Show Gist options
  • Save marcysutton/835c2fd90fadb631414b39523d5bda61 to your computer and use it in GitHub Desktop.
Save marcysutton/835c2fd90fadb631414b39523d5bda61 to your computer and use it in GitHub Desktop.
React A11y Testing
import {expect} from 'chai';
import App from '../app/components/App';
import a11yHelper from "./a11yHelper";
describe('Accessibility', function () {
this.timeout(10000);
it('Has no errors', function () {
let config = {};
// let config = {
// "rules": {
// "color-contrast": { enabled: false }
// }
// };
a11yHelper.testEnzymeComponent(<App/>, config, function (results) {
expect(results.violations.length).to.equal(0);
});
});
});
import React from 'react';
import {findDOMNode, render} from 'react-dom';
import {mount} from 'enzyme';
import axeCore from 'axe-core';
var a11yHelper = {};
/**
* Test a component with React's Test Utils.
*
* @param {any} app - Your app reference.
* @param {object} config (optional) - An aXe config object to enable/disable rules. See
* [axe.a11yCheck](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter).
* @param {function} callback - A callback function to execute when aXe returns.
*/
a11yHelper.testReactComponent = function(app, config, callback) {
let div = document.createElement('div');
document.body.appendChild(div);
this.wrapper = render(app, div);
let node = findDOMNode(div);
if (typeof config === 'function') {
config = {};
}
this.run(node, config, callback);
document.body.removeChild(div);
}
/**
* Test a component with Enzyme.
*
* @param {any} app - Your app reference.
* @param {object} config (optional) - An aXe config object to enable/disable rules. See
* [axe.a11yCheck](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter).
* @param {function} callback - A callback function to execute when aXe returns.
*/
a11yHelper.testEnzymeComponent = function (app, config, callback) {
let div = document.createElement('div');
document.body.appendChild(div);
let wrapper = mount(app, { attachTo: div });
let node = findDOMNode(wrapper.component);
if (typeof config === 'function') {
config = {};
}
this.run(node, config, callback);
document.body.removeChild(div);
}
/**
* Run an aXe audit.
* @private
*
* @param {object} node - A node reference from your app.
* @param {object} config - An aXe config or empty object.
* @param {function} callback - A callback function to execute when aXe returns.
*/
a11yHelper.run = function(node, config, callback) {
var oldNode = global.Node;
global.Node = node.ownerDocument.defaultView.Node;
axeCore.run(node, config, function(err, results) {
global.Node = oldNode;
if (err instanceof Error) {
return err;
}
a11yHelper.report(results);
// return to the test expectation
callback(results);
});
}
/**
* Report results in a readable fashion.
* @private
* @param {object} results - The aXe [results object](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#results-object).
*/
a11yHelper.report = function(results) {
// output some useful information
let failureNotice = '';
if (results.violations.length > 0) {
failureNotice += 'Accessibility violations:\n';
results.violations.forEach(function(violation) {
failureNotice += violation.description + '\n';
failureNotice += 'HTML Nodes: \n';
violation.nodes.forEach(function(node) {
failureNotice += node.html + '\n';
});
});
console.log(failureNotice);
}
}
module.exports = a11yHelper;
@goksu
Copy link

goksu commented Nov 9, 2017

This is a very clear and helpful. Thank you!

@rachel-church
Copy link

What is global.Node and why is it being set to node.ownerDocument.defaultView.Node?

@clottman
Copy link

clottman commented Oct 7, 2019

I changed line 43 to let node = wrapper.getDOMNode(); and then this worked! Thanks Marcy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment