Where in the Stack is Carmen Sanfrancisco? React Rally Edition

Where in the Stack is Carmen Sanfrancisco? React Rally Edition

We're going on an adventure through the JavaScript jungle. Node.js, browser JS, virtual DOM...where in the world are we and what can we do? In my work on automated accessibility testing, I've found the lines to be quite blurry in different parts of the web development stack. For example, when writing APIs in Node.js to be executed in a browser, it’s not always clear what your options are. We’ll look closely at some of these details as they relate to React.js and learn a lot about testing for accessibility in the process, encouraging development of websites that everyone can use.

Editorial note: Keynote PDFs don't include videos or the awesome $10 theme song. For those, you can watch the video when it comes out or look at these huge, inaccessible HTML slides. http://marcysutton.com/slides/carmen-sanfrancisco-reactrally/

1cd529dea0aa4dda3b1242a350163a6e?s=128

Marcy Sutton

August 26, 2016
Tweet

Transcript

  1. 2.
  2. 3.
  3. 5.
  4. 7.
  5. 8.

    /* * Creates a trigger button. Just pass in the

    uniqueID that will * correspond with the dropdown this will toggle */ var AUIDropdown2Trigger = React.createClass({ render() { return ( <a href={ '#' + this.props.dropID } aria-controls={ this.props.dropID } aria-haspopup="true" className="aui-button aui-style-default aui-dropdown2-trigger"> { this.props.children } </a> ); } }); /* Given this component definition:
  6. 9.

    /* * Manages the Dropdown2 menu */ var AUIDropdown2 =

    React.createClass({ componentDidMount() { let dropdown = this.refs.dropdown; AJS.$(dropdown).on('aui-dropdown2-show', () => { ReactDOM.render(this.props.children, dropdown); dropdown.querySelector('a').focus(); }); AJS.$(dropdown).on('aui-dropdown2-hide', () => { ReactDOM.unmountComponentAtNode(this.refs.dropdown); }); }, Component definition, part deux:
  7. 11.

    <ul> <li> <a>A thing</a> </li> </ul> <ul> <li> <a href="/some-url">A

    thing</a> </li> </ul> Why won’t it focus? 1. Elements need to be focusable 2. JAWS 3. Visibility race condition
  8. 12.

    /* * Manages the Dropdown2 menu */ var AUIDropdown2 =

    React.createClass({ componentDidMount() { let dropdown = this.refs.dropdown; AJS.$(dropdown).on('aui-dropdown2-show', () => { ReactDOM.render(this.props.children, dropdown, () => { this.refs.dropdown.querySelector('a').focus(); }); }); AJS.$(dropdown).on('aui-dropdown2-hide', () => { React.unmountComponentAtNode(dropdown); }); }, Fix it with ReactDOM.render:
  9. 15.
  10. 17.
  11. 18.
  12. 19.

    Where are we? 1. No JavaScript Land 2. HTMLElement API

    3. WAI-ARIA 4. Accessibility APIs
  13. 20.

    role: what does it do? role="button" state: what state is

    it in? aria-expanded="false" property: what’s the nature of it? aria-haspopup="true" aria-label="Close modal" WAI-ARIA https://www.w3.org/TR/wai-aria/states_and_properties
  14. 22.
  15. 24.

    “I want to execute 
 a browser script 
 in

    Node.js“ Why does it choke?
  16. 25.

    (function axeFunction(window) { var global = window; var document =

    window.document; var axe = axe || {}; axe.version = "2.0.5"; if (typeof define === "function" && define.amd) { define([], function() { "use strict"; return axe; }); } if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === "object" && module.exports && typeof axeFunction.toString === "function") { axe.source = "(" + axeFunction.toString() + ")(this, this.document);"; module.exports = axe; } if (typeof window.getComputedStyle === "function") { window.axe = axe; } Accessibility testing with axe-core http://github.com/dequelabs/axe-core
  17. 26.

    Running aXe with JSDOM: var jsdom = require('jsdom'), axe =

    require('axe-core'); var html = [ '<html>', '<body>', '<p>An image without an alt tag! <img src="some.jpg" / ></p>', '<h2>Not an h1</h2>', '<h5>blabla</h5>', '</body>', '</html>' ].join('\n'); axe.a11yCheck(html, function(data) { console.log(data); });
  18. 29.

    How do we fix it? 1. Execute it in a

    browser 2. Pass strings instead of objects 3. Shim undefined objects
  19. 30.

    var jsdom = require('jsdom'); var html = [ '<html>', '<body>',

    '<p>An image without an alt tag! <img src="some.jpg" / ></p>', '<h2>Not an h1</h2>', '<h5>blabla</h5>', '</body>', '</html>' ].join('\n'); jsdom.env(html, function(err, window) { global.window = window; global.Node = window.Node; global.NodeList = window.NodeList; Browser script hand-holding
  20. 34.

    •Programmatically perform user tasks •Ensure keyboard support •Catch low-hanging ARIA/markup

    bugs •Tradeoffs: speed vs. web platform gaps •Use an API for more test coverage
  21. 35.

    describe('Modal', function() { it('should close on Esc key event', function()

    { var requestCloseCallback = sinon.spy(); var modal = renderModal({ isOpen: true, shouldCloseOnOverlayClick: true, onRequestClose: requestCloseCallback, }); equal(modal.props.isOpen, true); assert.doesNotThrow(function() { Simulate.keyDown(modal.portal.refs.content, { key: "Esc", keyCode: 27, which: 27 }) }); ok(requestCloseCallback.called) // Check if event is passed to onRequestClose callback. var event = requestCloseCallback.getCall(0).args[0]; ok(event); ok(event.constructor); equal(event.constructor.name, 'SyntheticEvent'); }); }); React Modal
  22. 36.
  23. 38.

    How to configure? 1. Shallow rendering 2. Full rendering 3.

    Enzyme mount + attachTo 4. PhantomJS 5. Selenium Webdriver
  24. 39.

    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); var oldNode = global.Node; global.Node = node.ownerDocument.defaultView.Node; axeCore.a11yCheck(node, config, function(results) { global.Node = oldNode; document.body.removeChild(div); callback(results); }); } Enzyme a11yHelper http://bit.ly/a11yHelper
  25. 40.

    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 = { "rules": { "color-contrast": { enabled: false } } }; a11yHelper.testEnzymeComponent(<App/>, config, function (results) { expect(results.violations.length).to.equal(0); }); }); }); Enzyme unit test with aXe a11yHelper http://bit.ly/axe-a11yCheck
  26. 41.
  27. 43.
  28. 44.
  29. 45.

    How does React-aXe work? 1. Magic 2. Injecting aXe into

    the DOM 3. Hooking into the component lifecycle
  30. 46.

    Where did we go in the stack? •In screen readers,

    focusing elements sometimes requires a delay. •Accessibility APIs expose information to assistive technologies. •Browser scripts’ global objects need shimming to include in Node.js. •Automatically test accessibility with Enzyme’s mount + attachTo method, PhantomJS and Selenium Webdriver. •Test accessibility even faster with React-aXe!
  31. 47.