Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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/

Marcy Sutton

August 26, 2016
Tweet

More Decks by Marcy Sutton

Other Decks in Technology

Transcript

  1. React Rally Edition

    View Slide

  2. View Slide

  3. View Slide

  4. Browser Context

    View Slide

  5. View Slide

  6. “I bound a focus()
    event but the listener
    never fires”
    Why won’t it focus?

    View Slide

  7. View Slide

  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 (
    aria-controls={ this.props.dropID }
    aria-haspopup="true"
    className="aui-button aui-style-default aui-dropdown2-trigger">
    { this.props.children }

    );
    }
    });
    /*
    Given this component definition:

    View Slide

  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:

    View Slide

  10. Cameron Cundiff, Developer at Thoughtbot
    A11yNYC organizer

    View Slide



  11. A thing




    A thing


    Why won’t it focus?
    1. Elements need to be focusable
    2. JAWS
    3. Visibility race condition

    View Slide

  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:

    View Slide

  13. http://bit.ly/thompson-dropdowns

    View Slide

  14. Looking for inspiration?
    https://github.com/davidtheclark/react-aria-menubutton
    Also:
    davidtheclark/react-aria-modal
    davidtheclark/react-aria-tabpanel
    reactjs/react-autocomplete

    View Slide

  15. View Slide

  16. “I want to expose
    accessibility information
    to assistive technology”
    What makes that possible?

    View Slide

  17. View Slide

  18. Alice Boxhall, Software Engineer at Google
    Creator of the Chrome
    Accessibility Developer
    Tools Extension

    View Slide

  19. Where are we?
    1. No JavaScript Land
    2. HTMLElement API
    3. WAI-ARIA
    4. Accessibility APIs

    View Slide

  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

    View Slide

  21. http://bit.ly/chrome-a11y

    View Slide

  22. View Slide

  23. Node.js Context

    View Slide

  24. “I want to execute 

    a browser script 

    in Node.js“
    Why does it choke?

    View Slide

  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

    View Slide

  26. Running aXe with JSDOM:
    var jsdom = require('jsdom'),
    axe = require('axe-core');
    var html = [
    '',
    '',
    'An image without an alt tag! >',
    'Not an h1',
    'blabla',
    '',
    ''
    ].join('\n');
    axe.a11yCheck(html, function(data) {
    console.log(data);
    });

    View Slide

  27. “I bound a focus()
    event but the listener
    never fires”
    Why won’t it focus?

    View Slide

  28. Rainier McCheddarton, Esquire,
    of the Olympic Cheddartons

    View Slide

  29. How do we fix it?
    1. Execute it in a browser
    2. Pass strings instead of objects
    3. Shim undefined objects

    View Slide

  30. var jsdom = require('jsdom');
    var html = [
    '',
    '',
    'An image without an alt tag! >',
    'Not an h1',
    'blabla',
    '',
    ''
    ].join('\n');
    jsdom.env(html, function(err, window) {
    global.window = window;
    global.Node = window.Node;
    global.NodeList = window.NodeList;
    Browser script hand-holding

    View Slide

  31. “I bound a focus()
    event but the listener
    never fires”
    Why won’t it focus?

    View Slide

  32. React Context

    View Slide

  33. “I need to set up
    automated accessibility
    tests in React“
    Do I need a browser?

    View Slide

  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

    View Slide

  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

    View Slide

  36. View Slide

  37. Jesse Beach, Front-End Engineer and Accessibility
    Specialist at Facebook,
    Quail JS test badass

    View Slide

  38. How to configure?
    1. Shallow rendering
    2. Full rendering
    3. Enzyme mount + attachTo
    4. PhantomJS
    5. Selenium Webdriver

    View Slide

  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

    View Slide

  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(, config, function (results) {
    expect(results.violations.length).to.equal(0);
    });
    });
    });
    Enzyme unit test with aXe a11yHelper
    http://bit.ly/axe-a11yCheck

    View Slide

  41. View Slide

  42. “I need faster
    accessibility testing in
    my React workflow“
    Can I supercharge the console?

    View Slide

  43. View Slide

  44. View Slide

  45. How does React-aXe work?
    1. Magic
    2. Injecting aXe into the DOM
    3. Hooking into the component lifecycle

    View Slide

  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!

    View Slide

  47. View Slide

  48. Marcy Sutton, Senior Front-End Engineer at Deque Systems
    twitter.com/marcysutton
    github.com/marcysutton
    http://bit.ly/carmen-sandiego-wayback
    Thanks!

    View Slide