From ecd5df3b5b0b7755ad17afbe269e7851f3c3764d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 5 Apr 2016 14:37:31 +0200 Subject: [PATCH 001/447] only add id when we need it for includeSrcNode --- lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index a5f9140..54f5de5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -189,7 +189,7 @@ const reactA11y = (_React, options = {}) => { childrenForTest = children; } - var newProps = assign({}, props, {id: createId(props)}); + var newProps = assign({}, props, options.includeSrcNode ? {id: createId(props)} : {}); var reactEl = _createElement.apply(this, [type, newProps].concat(children)); var failureCB = handleFailure.bind(undefined, options, reactEl); From 6730fadedb3d6f0d38c4d5380f3804e7511012b0 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 5 Apr 2016 17:59:40 +0200 Subject: [PATCH 002/447] clean up code --- lib/after.js | 60 ++++++----- lib/index.js | 299 ++++++++++++++++++++++++++------------------------- 2 files changed, 190 insertions(+), 169 deletions(-) diff --git a/lib/after.js b/lib/after.js index e69dbcc..2d22a63 100644 --- a/lib/after.js +++ b/lib/after.js @@ -1,28 +1,40 @@ -let restoreFunctions = []; - -const after = (host, name, cb) => { - const originalFn = host[name]; - let restoreFn; - - if (originalFn) { - host[name] = function(...args) { - originalFn.apply(this, args); - cb.apply(this, args); - }; - restoreFn = () => host[name] = originalFn; - } else { - host[name] = function(...args) { - cb.apply(this, args); - }; - restoreFn = () => delete host[name]; + +// store how to undo these changes +let restoreFunctions = [] + +// does nothing +const noop = () => null + +const after = function (host, name, cb) { + if ( !host ) { + throw new Error('cannot replace function on undefined') } - restoreFunctions.push(restoreFn); -}; + // save original + const original = host[name] || noop + + // override host + host[name] = function (...args) { + // perform original + original.apply(this, args) + // perform cb + cb.apply(this, args) + } + + // save restoring function + restoreFunctions.push(function () { + host[name] = originalFn + }) +} + +after.restorePatchedMethods = function () { + // perform each restoring function + restoreFunctions.forEach(restore => restore()) + + // clear the list of functions to restore + restoreFunctions = [] +} + +export default after -after.restorePatchedMethods = () => { - restoreFunctions.forEach(restoreFn => restoreFn()); - restoreFunctions = []; -}; -module.exports = after; diff --git a/lib/index.js b/lib/index.js index 54f5de5..03f0cd0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,103 +1,107 @@ -var assign = require('object.assign'); -var assertions = require('./assertions'); -var after = require('./after'); +import assertions from './assertions' +import after from './after' -var shouldRunTest = (testName, options) => { - var exclude = options.exclude || []; +const shouldRunTest = function (testName, options = {} ) { + let { + exclude = [] + , device + } = options - if (options.device == 'mobile') { - exclude = exclude.concat(assertions.mobileExclusions); + if ( device == 'mobile' ) { + exclude = exclude.concat(assertions.mobileExclusions) } - return (exclude.indexOf(testName) == -1); -}; + return exclude.indexOf(testName) === -1 +} -var runTagTests = (tagName, props, children, options, onFailure) => { - var key; - var tagTests = assertions.tags[tagName] || []; +const runTagTests = function (tagName, props, children, options, onFailure) { + const tagTests = assertions.tags[tagName] || [] - for (key in tagTests) { - let testFailed = shouldRunTest(key, options) && - !tagTests[key].test(tagName, props, children); + Object.keys(tagTests).forEach(function (key) { + const testFailed = shouldRunTest(key, options) && + !tagTests[key].test(tagName, props, children) - if (tagTests[key] && testFailed) - onFailure(tagName, props, tagTests[key].msg); - } -}; - -var runPropTests = (tagName, props, children, options, onFailure) => { - var key; - var propTests; - - for (var propName in props) { - if (props[propName] === null || props[propName] === undefined) continue; - - propTests = assertions.props[propName] || []; - - for (key in propTests) { - let testTailed = shouldRunTest(key, options) && - !propTests[key].test(tagName, props, children); - - if (propTests[key] && testTailed) - onFailure(tagName, props, propTests[key].msg); + if (tagTests[key] && testFailed) { + onFailure(tagName, props, tagTests[key].msg) } - } -}; + }) +} + +const runPropTests = function (tagName, props, children, options, onFailure) { + Object.keys(props).forEach(function (propName) { + if ( props[propName] !== null || props[propName] !== undefined ) { + const propTests = assertions.props[propName] || []; + Object.keys(propTests).forEach(function (key) { + const testTailed = shouldRunTest(key, options) && + !propTests[key].test(tagName, props, children) + + if (propTests[key] && testTailed) { + onFailure(tagName, props, propTests[key].msg) + } + }) + } + }) +} -var runLabelTests = (tagName, props, children, options, onFailure) => { - var key; - var renderTests = assertions.render; +var runLabelTests = function (tagName, props, children, options, onFailure) { + const renderTests = assertions.render - for (key in renderTests) { + Object.keys(renderTests).forEach(function (key) { if (shouldRunTest(key, options) && renderTests[key]) { - let failureCB = onFailure.bind( - undefined, tagName, props, renderTests[key].msg); + const failureCB = onFailure + .bind(undefined, tagName, props, renderTests[key].msg) - renderTests[key].test(tagName, props, children, failureCB); + renderTests[key].test(tagName, props, children, failureCB) } + }) +} + +// run all tests +const allTests = [runTagTests, runPropTests, runLabelTests] +const runTests = function (tagName, props, children, options, onFailure) { + allTests.forEach(function (test) { + test(tagName, props, children, options, onFailure) + }) +} + +const shouldShowError = function (failureInfo, options = {}) { + if (typeof options.filterFn === 'function') { + return options.filterFn(failureInfo.tagName, failureInfo.id) + } else { + return true } -}; - -var runTests = (tagName, props, children, options, onFailure) => { - var tests = [runTagTests, runPropTests, runLabelTests]; - tests.map((test) => { - test(tagName, props, children, options, onFailure); - }); -}; - -var shouldShowError = (failureInfo, options) => { - var filterFn = options.filterFn; - if (filterFn) - return filterFn(failureInfo.tagName, failureInfo.id); +} - return true; -}; - -var throwError = (failureInfo, options) => { - if (!shouldShowError(failureInfo, options)) - return; +const throwError = function (failureInfo, options = {}) { + if (!shouldShowError(failureInfo, options)) { + return + } - var error = [failureInfo.tagName, failureInfo.msg]; + const error = [failureInfo.tagName, failureInfo.msg] - if (options.includeSrcNode) - error.push(failureInfo.id); + if (options.includeSrcNode) { + error.push(failureInfo.id) + } - throw new Error(error.join(' ')); -}; + throw new Error(error.join(' ')) +} -var logAfterRender = (component, log) => { - after(component, 'componentDidMount', log); - after(component, 'componentDidUpdate', log); -}; +const logAfterRender = function (component, log) { + after(component, 'componentDidMount', log) + after(component, 'componentDidUpdate', log) +} -var logWarning = (component, failureInfo, options) => { - var includeSrcNode = options.includeSrcNode; +const logWarning = function (component, failureInfo, options = {}) { + const { + includeSrcNode = false + } = options - var warn = () => { - if (!shouldShowError(failureInfo, options)) - return; + const warn = function () { + if (!shouldShowError(failureInfo, options)) { + return + } - var warning = [failureInfo.tagName, failureInfo.msg]; + const warning = [failureInfo.tagName, failureInfo.msg]; if (includeSrcNode && component) { // TODO: @@ -110,99 +114,104 @@ var logWarning = (component, failureInfo, options) => { // Guard against logging null element references should render() // return null or false. // https://facebook.github.io/react/docs/component-api.html#getdomnode - if (includeSrcNode === "asString") - warning.push("Source Node: " + srcNode.outerHTML); - else if (srcNode) - warning.push(srcNode); + if (includeSrcNode === "asString") { + warning.push("Source Node: " + srcNode.outerHTML) + } else if (srcNode) { + warning.push(srcNode) + } } console.warn.apply(console, warning); - }; + } - if (includeSrcNode && component) + if (includeSrcNode && component) { // Cannot log a node reference until the component is in the DOM, // so defer the document.getElementById call until componentDidMount // or componentDidUpdate. - logAfterRender(component._instance, warn); - else - warn(); -}; + logAfterRender(component._instance, warn) + } else { + warn() + } +} -var handleFailure = (options, reactEl, type, props, failureMsg) => { - var includeSrcNode = options && (options.includeSrcNode || false); - var warningPrefix = (options && options.warningPrefix) || ''; - var reactComponent = reactEl._owner; +const handleFailure = function (options = {}, reactEl, type, props, failureMsg) { + const { + includeSrcNode = false + , warningPrefix = '' + , filterFn + , throw: doThrow + } = options - // If a Component instance, use the component's name, - // if a ReactElement instance, use the tag name + id (e.g. div#foo) - var name = reactComponent && reactComponent.getName() || - type + '#' + props.id; + // get the owning component + const reactComponent = reactEl._owner - var failureInfo = { - 'tagName': name , - 'id': props.id, - 'msg': warningPrefix + failureMsg - }; + // if a component instance, use the component's name, + // if a ReactElement instance, use the tag name + id (e.g. div#foo) + const name = reactComponent && reactComponent.getName() || type - var notifyOpts = { - 'includeSrcNode': includeSrcNode, - 'filterFn': options && options.filterFn - }; + const failureInfo = { + tagName: name + , msg: warningPrefix + failureMsg + } - if (options && options.throw) - throwError(failureInfo, notifyOpts); - else - logWarning(reactComponent, failureInfo, notifyOpts); -}; + const notifyOpts = { + includeSrcNode + , filterFn + } + if ( doThrow ) { + throwError(failureInfo, notifyOpts) + } else { + logWarning(reactComponent, failureInfo, notifyOpts) + } +} -var _createElement; +let nextId = 0 +const createId = function(props) { + return (props.id || 'a11y-' + nextId++) +} -var createId = function() { - var nextId = 0; - return (props) => { - return (props.id || 'a11y-' + nextId++); - }; -}(); -let React; +let _React +let _createElement -const reactA11y = (_React, options = {}) => { - const { ReactDOM } = options; - React = _React; +const reactA11y = function (React, options = {}) { + const { + includeSrcNode + , ReactDOM + } = options if (!React && !React.createElement) { - throw new Error('Missing parameter: React'); + throw new Error('Missing parameter: React') } - assertions.setReact(React, ReactDOM); + // save our copy to react + _React = React + _createElement = React.createElement + assertions.setReact(React, ReactDOM) - _createElement = React.createElement; + // replace createElement with our overloaded version + _React.createElement = function (type, props = {}, ...children) { - React.createElement = (type, _props, ...children) => { - var props = _props || {}; + const newProps = Object.assign(props, includeSrcNode ? {id: createId(props)} : {}) + const reactEl = _createElement.apply(this, [type, newProps].concat(children)) + const failureCB = handleFailure.bind(undefined, options, reactEl) - var childrenForTest; + if (typeof type === 'string') { + // only test html elements + const childrenForTest = children.length === 0 + ? props.children || [] + : children - if (children.length === 0) { - childrenForTest = props.children || []; - } else { - childrenForTest = children; + runTests(type, newProps, childrenForTest, options, failureCB) } - var newProps = assign({}, props, options.includeSrcNode ? {id: createId(props)} : {}); - var reactEl = _createElement.apply(this, [type, newProps].concat(children)); - var failureCB = handleFailure.bind(undefined, options, reactEl); - - if (typeof type === 'string') - runTests(type, newProps, childrenForTest, options, failureCB); - - return reactEl; - }; -}; + return reactEl + } +} reactA11y.restoreAll = function() { - React.createElement = _createElement; - after.restorePatchedMethods(); -}; + _React.createElement = _createElement + after.restorePatchedMethods() +} -module.exports = reactA11y; +export default reactA11y From 1c21ab94adea134beae503235355f61fe94e0473 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 5 Apr 2016 18:12:27 +0200 Subject: [PATCH 003/447] move test running code to separate file --- lib/index.js | 66 ++-------------------------------------------------- lib/tests.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 64 deletions(-) create mode 100644 lib/tests.js diff --git a/lib/index.js b/lib/index.js index 03f0cd0..40418c9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,68 +1,6 @@ -import assertions from './assertions' import after from './after' - -const shouldRunTest = function (testName, options = {} ) { - let { - exclude = [] - , device - } = options - - if ( device == 'mobile' ) { - exclude = exclude.concat(assertions.mobileExclusions) - } - - return exclude.indexOf(testName) === -1 -} - -const runTagTests = function (tagName, props, children, options, onFailure) { - const tagTests = assertions.tags[tagName] || [] - - Object.keys(tagTests).forEach(function (key) { - const testFailed = shouldRunTest(key, options) && - !tagTests[key].test(tagName, props, children) - - if (tagTests[key] && testFailed) { - onFailure(tagName, props, tagTests[key].msg) - } - }) -} - -const runPropTests = function (tagName, props, children, options, onFailure) { - Object.keys(props).forEach(function (propName) { - if ( props[propName] !== null || props[propName] !== undefined ) { - const propTests = assertions.props[propName] || []; - Object.keys(propTests).forEach(function (key) { - const testTailed = shouldRunTest(key, options) && - !propTests[key].test(tagName, props, children) - - if (propTests[key] && testTailed) { - onFailure(tagName, props, propTests[key].msg) - } - }) - } - }) -} - -var runLabelTests = function (tagName, props, children, options, onFailure) { - const renderTests = assertions.render - - Object.keys(renderTests).forEach(function (key) { - if (shouldRunTest(key, options) && renderTests[key]) { - const failureCB = onFailure - .bind(undefined, tagName, props, renderTests[key].msg) - - renderTests[key].test(tagName, props, children, failureCB) - } - }) -} - -// run all tests -const allTests = [runTagTests, runPropTests, runLabelTests] -const runTests = function (tagName, props, children, options, onFailure) { - allTests.forEach(function (test) { - test(tagName, props, children, options, onFailure) - }) -} +import assertions from './assertions' +import runTests from './tests' const shouldShowError = function (failureInfo, options = {}) { if (typeof options.filterFn === 'function') { diff --git a/lib/tests.js b/lib/tests.js new file mode 100644 index 0000000..721b39e --- /dev/null +++ b/lib/tests.js @@ -0,0 +1,66 @@ +import assertions from './assertions' + +const shouldRunTest = function (testName, options = {} ) { + let { + exclude = [] + , device + } = options + + if ( device == 'mobile' ) { + exclude = exclude.concat(assertions.mobileExclusions) + } + + return exclude.indexOf(testName) === -1 +} + +const runTagTests = function (tagName, props, children, options, onFailure) { + const tagTests = assertions.tags[tagName] || [] + + Object.keys(tagTests).forEach(function (key) { + const testFailed = shouldRunTest(key, options) && + !tagTests[key].test(tagName, props, children) + + if (tagTests[key] && testFailed) { + onFailure(tagName, props, tagTests[key].msg) + } + }) +} + +const runPropTests = function (tagName, props, children, options, onFailure) { + Object.keys(props).forEach(function (propName) { + if ( props[propName] !== null || props[propName] !== undefined ) { + const propTests = assertions.props[propName] || []; + Object.keys(propTests).forEach(function (key) { + const testTailed = shouldRunTest(key, options) && + !propTests[key].test(tagName, props, children) + + if (propTests[key] && testTailed) { + onFailure(tagName, props, propTests[key].msg) + } + }) + } + }) +} + +var runLabelTests = function (tagName, props, children, options, onFailure) { + const renderTests = assertions.render + + Object.keys(renderTests).forEach(function (key) { + if (shouldRunTest(key, options) && renderTests[key]) { + const failureCB = onFailure + .bind(undefined, tagName, props, renderTests[key].msg) + + renderTests[key].test(tagName, props, children, failureCB) + } + }) +} + +// run all tests +const allTests = [runTagTests, runPropTests, runLabelTests] +const runTests = function (tagName, props, children, options, onFailure) { + allTests.forEach(function (test) { + test(tagName, props, children, options, onFailure) + }) +} + +export default runTests From 538f1f78d83f216c40d55b5dad6e208989107863 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 00:11:43 +0200 Subject: [PATCH 004/447] refactor a lot and use findDOMNode instead of getElementById --- lib/after.js | 7 +++-- lib/index.js | 82 +++++++++++++++++++++++++--------------------------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/lib/after.js b/lib/after.js index 2d22a63..c1817e5 100644 --- a/lib/after.js +++ b/lib/after.js @@ -35,6 +35,9 @@ after.restorePatchedMethods = function () { restoreFunctions = [] } -export default after - +after.render = function (component, fn) { + after(component, 'componentDidMount', fn) + after(component, 'componentDidUpdate', fn) +} +export default after diff --git a/lib/index.js b/lib/index.js index 40418c9..cddf509 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,9 @@ import after from './after' import assertions from './assertions' import runTests from './tests' +import ReactDOM from 'react-dom' +// check if an error is allowed const shouldShowError = function (failureInfo, options = {}) { if (typeof options.filterFn === 'function') { return options.filterFn(failureInfo.tagName, failureInfo.id) @@ -24,54 +26,44 @@ const throwError = function (failureInfo, options = {}) { throw new Error(error.join(' ')) } -const logAfterRender = function (component, log) { - after(component, 'componentDidMount', log) - after(component, 'componentDidUpdate', log) -} const logWarning = function (component, failureInfo, options = {}) { + // omit warning if we don't need to show it + if (!shouldShowError(failureInfo, options)) { + return + } + const { includeSrcNode = false } = options - const warn = function () { - if (!shouldShowError(failureInfo, options)) { - return - } + const { + tagName + , msg + , ref + } = failureInfo - const warning = [failureInfo.tagName, failureInfo.msg]; + const warning = [tagName, msg] - if (includeSrcNode && component) { - // TODO: - // 1) Consider using ReactDOM.findDOMNode() over document.getElementById - // https://github.com/reactjs/react-a11y/issues/54 - // 2) Consider using ref to expand element element reference logging - // to all element (https://github.com/reactjs/react-a11y/issues/55) - let srcNode = document.getElementById(failureInfo.id); + if (includeSrcNode && component) { + const instance = component._instance + // Cannot log a node reference until the component is in the DOM, + // so defer the call until componentDidMount or componentDidUpdate. + after.render(instance, function () { + const srcNode = ReactDOM.findDOMNode(instance.refs[ref]) - // Guard against logging null element references should render() - // return null or false. - // https://facebook.github.io/react/docs/component-api.html#getdomnode if (includeSrcNode === "asString") { - warning.push("Source Node: " + srcNode.outerHTML) + console.warn(...warning, `Source Node: ${srcNode.outerHTML}`) } else if (srcNode) { - warning.push(srcNode) + console.warn(...warning, srcNode) } - } - console.warn.apply(console, warning); - } - - if (includeSrcNode && component) { - // Cannot log a node reference until the component is in the DOM, - // so defer the document.getElementById call until componentDidMount - // or componentDidUpdate. - logAfterRender(component._instance, warn) + }) } else { - warn() + console.warn(...warning) } } -const handleFailure = function (options = {}, reactEl, type, props, failureMsg) { +const failureHandler = (options = {}, reactEl) => function (type, props, failureMsg) { const { includeSrcNode = false , warningPrefix = '' @@ -84,11 +76,12 @@ const handleFailure = function (options = {}, reactEl, type, props, failureMsg) // if a component instance, use the component's name, // if a ReactElement instance, use the tag name + id (e.g. div#foo) - const name = reactComponent && reactComponent.getName() || type + const tagName = reactComponent && reactComponent.getName() || type const failureInfo = { - tagName: name - , msg: warningPrefix + failureMsg + tagName + , ref: props.ref + , msg: warningPrefix.concat(failureMsg) } const notifyOpts = { @@ -103,9 +96,9 @@ const handleFailure = function (options = {}, reactEl, type, props, failureMsg) } } -let nextId = 0 -const createId = function(props) { - return (props.id || 'a11y-' + nextId++) +let nextRef = 0 +const createRef = function (props) { + return (props.ref || 'a11y-' + nextRef++) } @@ -125,22 +118,25 @@ const reactA11y = function (React, options = {}) { // save our copy to react _React = React _createElement = React.createElement + // _ReactDOM = ReactDOM assertions.setReact(React, ReactDOM) // replace createElement with our overloaded version _React.createElement = function (type, props = {}, ...children) { - const newProps = Object.assign(props, includeSrcNode ? {id: createId(props)} : {}) - const reactEl = _createElement.apply(this, [type, newProps].concat(children)) - const failureCB = handleFailure.bind(undefined, options, reactEl) + const ref = createRef(props) + const newProps = typeof type === 'string' ? { ...props, ref } : props + + const reactEl = _createElement(type, newProps, ...children) + const handler = failureHandler(options, reactEl) + // only test html elements if (typeof type === 'string') { - // only test html elements const childrenForTest = children.length === 0 ? props.children || [] : children - runTests(type, newProps, childrenForTest, options, failureCB) + runTests(type, newProps, childrenForTest, options, handler) } return reactEl From 183d2e531cfb5063b2988f6690a68aee6fcbecbb Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 00:14:45 +0200 Subject: [PATCH 005/447] inject ReactDOM from options --- lib/index.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/index.js b/lib/index.js index cddf509..4a057bb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,10 @@ import after from './after' import assertions from './assertions' import runTests from './tests' -import ReactDOM from 'react-dom' + +let _React +let _createElement +let _ReactDOM // check if an error is allowed const shouldShowError = function (failureInfo, options = {}) { @@ -50,7 +53,7 @@ const logWarning = function (component, failureInfo, options = {}) { // Cannot log a node reference until the component is in the DOM, // so defer the call until componentDidMount or componentDidUpdate. after.render(instance, function () { - const srcNode = ReactDOM.findDOMNode(instance.refs[ref]) + const srcNode = _ReactDOM.findDOMNode(instance.refs[ref]) if (includeSrcNode === "asString") { console.warn(...warning, `Source Node: ${srcNode.outerHTML}`) @@ -101,10 +104,6 @@ const createRef = function (props) { return (props.ref || 'a11y-' + nextRef++) } - -let _React -let _createElement - const reactA11y = function (React, options = {}) { const { includeSrcNode @@ -115,11 +114,15 @@ const reactA11y = function (React, options = {}) { throw new Error('Missing parameter: React') } + if (includeSrcNode && !ReactDOM) { + throw new Error('I need ReactDOM option when includeSrcNode is true') + } + // save our copy to react _React = React + _ReactDOM = ReactDOM _createElement = React.createElement - // _ReactDOM = ReactDOM - assertions.setReact(React, ReactDOM) + assertions.setReact(_React, _ReactDOM) // replace createElement with our overloaded version _React.createElement = function (type, props = {}, ...children) { From 6a7745956220e6267e88a9c057d557b77f855964 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 00:46:05 +0200 Subject: [PATCH 006/447] refactor errors and warnings, add outerHTML to error --- lib/index.js | 72 +++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/lib/index.js b/lib/index.js index 4a057bb..a7a9c23 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,36 +6,30 @@ let _React let _createElement let _ReactDOM -// check if an error is allowed -const shouldShowError = function (failureInfo, options = {}) { - if (typeof options.filterFn === 'function') { - return options.filterFn(failureInfo.tagName, failureInfo.id) - } else { - return true - } -} - -const throwError = function (failureInfo, options = {}) { - if (!shouldShowError(failureInfo, options)) { - return +// always resolve to true +const always = () => true + +// throw error with string representation +// of node +const throwError = function (...args) { + const last = args[args.length - 1] + if ( last.outerHTML ) { + args[args.length - 1] = 'Element: \n ' + last.outerHTML } - const error = [failureInfo.tagName, failureInfo.msg] + const error = new Error(args.join(' ')) + error.element = last - if (options.includeSrcNode) { - error.push(failureInfo.id) - } - - throw new Error(error.join(' ')) + throw error } +// just show the warning +const showWarning = function (...args) { + console.warn(...args) +} -const logWarning = function (component, failureInfo, options = {}) { - // omit warning if we don't need to show it - if (!shouldShowError(failureInfo, options)) { - return - } - +// renders the failure message based on options and component lifycycle +const displayFailure = function (component, failureInfo, options = {}, done) { const { includeSrcNode = false } = options @@ -46,8 +40,6 @@ const logWarning = function (component, failureInfo, options = {}) { , ref } = failureInfo - const warning = [tagName, msg] - if (includeSrcNode && component) { const instance = component._instance // Cannot log a node reference until the component is in the DOM, @@ -56,46 +48,46 @@ const logWarning = function (component, failureInfo, options = {}) { const srcNode = _ReactDOM.findDOMNode(instance.refs[ref]) if (includeSrcNode === "asString") { - console.warn(...warning, `Source Node: ${srcNode.outerHTML}`) + return done(tagName, msg, `Source Node: ${srcNode.outerHTML}`) } else if (srcNode) { - console.warn(...warning, srcNode) + return done(tagName, msg, srcNode) } }) } else { - console.warn(...warning) + return done(tagName, msg) } } const failureHandler = (options = {}, reactEl) => function (type, props, failureMsg) { const { includeSrcNode = false + , throw: doThrow = false , warningPrefix = '' - , filterFn - , throw: doThrow + , filterFn = always } = options // get the owning component - const reactComponent = reactEl._owner + const owner = reactEl._owner // if a component instance, use the component's name, // if a ReactElement instance, use the tag name + id (e.g. div#foo) - const tagName = reactComponent && reactComponent.getName() || type + const tagName = owner && owner.getName() || type const failureInfo = { tagName - , ref: props.ref , msg: warningPrefix.concat(failureMsg) + , ref: props.ref } - const notifyOpts = { + const opts = { includeSrcNode - , filterFn } - if ( doThrow ) { - throwError(failureInfo, notifyOpts) - } else { - logWarning(reactComponent, failureInfo, notifyOpts) + // display the failure if it isn't filtered + if ( filterFn(failureInfo) ) { + // how should we handle the message? + const done = doThrow ? throwError : showWarning + displayFailure(owner, failureInfo, opts, done) } } From 59acc8c3a7768416639fc1eeacc188cb5e889ae7 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 01:02:46 +0200 Subject: [PATCH 007/447] fix bug where props were null and triggerd a faulty ref --- lib/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index a7a9c23..156df43 100644 --- a/lib/index.js +++ b/lib/index.js @@ -92,8 +92,8 @@ const failureHandler = (options = {}, reactEl) => function (type, props, failure } let nextRef = 0 -const createRef = function (props) { - return (props.ref || 'a11y-' + nextRef++) +const createRef = function (props = {}) { + return (props || {}).ref || `a11y-${nextRef++}` } const reactA11y = function (React, options = {}) { @@ -119,6 +119,7 @@ const reactA11y = function (React, options = {}) { // replace createElement with our overloaded version _React.createElement = function (type, props = {}, ...children) { + // add a ref when the component is a DOM component (eg. div) const ref = createRef(props) const newProps = typeof type === 'string' ? { ...props, ref } : props From 18d1522dda09af73bc149484c8aa1c0078fefe4d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 01:07:20 +0200 Subject: [PATCH 008/447] remove object.assign dep, as it is no longer needed --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 5c0061e..5d5e355 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,5 @@ "a11y" ], "dependencies": { - "object.assign": "^4.0.3" } -} \ No newline at end of file +} From 30b724dcafac69a4999c9af3370f23c41f2f8212 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 01:09:16 +0200 Subject: [PATCH 009/447] fix bug where restoring original function is a TypeError --- lib/after.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/after.js b/lib/after.js index c1817e5..b57f5d7 100644 --- a/lib/after.js +++ b/lib/after.js @@ -23,7 +23,7 @@ const after = function (host, name, cb) { // save restoring function restoreFunctions.push(function () { - host[name] = originalFn + host[name] = original }) } From 99682cc21fd11450c656c145ae0984e23eaf0115 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 01:25:11 +0200 Subject: [PATCH 010/447] use different mechanism of storing refs, so root-level DOM elements will work --- lib/index.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/index.js b/lib/index.js index 156df43..e611ea6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -37,7 +37,7 @@ const displayFailure = function (component, failureInfo, options = {}, done) { const { tagName , msg - , ref + , refs } = failureInfo if (includeSrcNode && component) { @@ -45,7 +45,8 @@ const displayFailure = function (component, failureInfo, options = {}, done) { // Cannot log a node reference until the component is in the DOM, // so defer the call until componentDidMount or componentDidUpdate. after.render(instance, function () { - const srcNode = _ReactDOM.findDOMNode(instance.refs[ref]) + // unpack the ref + const srcNode = refs.node if (includeSrcNode === "asString") { return done(tagName, msg, `Source Node: ${srcNode.outerHTML}`) @@ -58,7 +59,7 @@ const displayFailure = function (component, failureInfo, options = {}, done) { } } -const failureHandler = (options = {}, reactEl) => function (type, props, failureMsg) { +const failureHandler = (options = {}, reactEl, refs) => function (type, props, failureMsg) { const { includeSrcNode = false , throw: doThrow = false @@ -76,7 +77,7 @@ const failureHandler = (options = {}, reactEl) => function (type, props, failure const failureInfo = { tagName , msg: warningPrefix.concat(failureMsg) - , ref: props.ref + , refs } const opts = { @@ -117,14 +118,19 @@ const reactA11y = function (React, options = {}) { assertions.setReact(_React, _ReactDOM) // replace createElement with our overloaded version - _React.createElement = function (type, props = {}, ...children) { - - // add a ref when the component is a DOM component (eg. div) - const ref = createRef(props) + _React.createElement = function (type, _props = {}, ...children) { + // fix for props = null + const props = _props || {} + + // create a refs object to hold the ref. + // this needs to be an object so that it can be passed + // by reference, and hold chaning state + const refs = {} + const ref = node => refs.node = node const newProps = typeof type === 'string' ? { ...props, ref } : props const reactEl = _createElement(type, newProps, ...children) - const handler = failureHandler(options, reactEl) + const handler = failureHandler(options, reactEl, refs) // only test html elements if (typeof type === 'string') { From 9c1e6dd065da323d22e765e9928cb1abb13dd7d2 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 01:25:41 +0200 Subject: [PATCH 011/447] inject ReactDOM in the tests --- lib/__tests__/index-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/__tests__/index-test.js b/lib/__tests__/index-test.js index ffc3ca1..e26a1c7 100644 --- a/lib/__tests__/index-test.js +++ b/lib/__tests__/index-test.js @@ -597,7 +597,7 @@ describe('includeSrcNode is "asString"', () => { var fixture; before(() => { - a11y(React, { includeSrcNode: "asString" }); + a11y(React, { includeSrcNode: "asString", ReactDOM: ReactDOM }); fixture = document.createElement('div'); fixture.id = 'fixture-1'; document.body.appendChild(fixture); From c1350fc7e2963d4d9338eff6f1b4c353a81e6295 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 01:29:32 +0200 Subject: [PATCH 012/447] pass element id to filterFn to comply with API --- lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index e611ea6..616b25c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -85,7 +85,7 @@ const failureHandler = (options = {}, reactEl, refs) => function (type, props, f } // display the failure if it isn't filtered - if ( filterFn(failureInfo) ) { + if ( filterFn(failureInfo, props.id) ) { // how should we handle the message? const done = doThrow ? throwError : showWarning displayFailure(owner, failureInfo, opts, done) From cdbf6109eec7aeedf7ee6f8d0e5d6a65d430c9a6 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 01:30:16 +0200 Subject: [PATCH 013/447] test just runs test --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d5e355..7c34061 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "homepage": "https://github.com/reactjs/react-a11y/blob/latest/README.md", "bugs": "https://github.com/reactjs/react-a11y/issues", "scripts": { - "test": "jsxhint . && karma start --single-run", + "test": "karma start --single-run", "watch-tests": "npm test -- --watch", "prepublish": "babel lib --out-dir dist", "release": "release" From ff53499a05c22d2b143e64bfa65d30291696333d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 02:02:36 +0200 Subject: [PATCH 014/447] improve docs, add info about fork and future plans --- README.md | 151 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 113 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 4d1312f..e5378a9 100644 --- a/README.md +++ b/README.md @@ -5,89 +5,164 @@ React A11y Warns about potential accessibility issues with your React elements. -![screenshot](http://i.imgur.com/naQTETB.png) +__This is a fork of [`reactjs/react-a11y`][react-a11y], that didn't seem to get +the love it deserves.__ -WIP ---- +The original repo had a lot of open issues and unmerged pull requests. +Eventually, I would like to see all code merged back into the original! + + +## WIP This is a WIP, feel free to explore, open issues, and suggest assertions :) -Installation ------------- +## Installation + +Run: + +```sh +npm install romeovs/react-a11y +``` -`npm install react-a11y` +I want to prevent creating a new `npm` package for the fork, to reduce +confusion and ease possible merging of the forks. However, if there is +enough interest, I will probably do it anyway. -Usage ------ +## Usage In your main application file, require and call the module, you'll start getting warnings in the console as your app renders. ```js -var a11y = require('react-a11y'); -if (ENV === 'development') a11y(React); +if (ENV === 'development') { + const a11y = require('react-a11y').default + a11y(React) +} +``` + +You probably don't want to call it if you're in production. + +## Options + +```js +a11y(React : React, opts : object? ) ``` -You probably don't want to call it if you're in production, and better -yet, alias the module to nothing with webpack in production. +`React` is the React object you want to shim to allow the +accessibility tests. +### `options` + +#### `throw : boolean` If you want it to throw errors instead of just warnings: ``` -a11y(React, { throw: true }); +a11y(React, { throw: true }) ``` +#### `filterFn : (string, string, string) => boolean` You can filter failures by passing a function to the `filterFn` option. The filter function will receive three arguments: the name of the Component instance or ReactElement, the id of the element, and the failure message. -Note: If a ReactElement, the name will be the node type followed by the id -(e.g. div#foo). -``` -var commentListFailures = (name, id, msg) => { +Note: If a ReactElement, the name will be the node type (eg. `div`) + +```js +// only show errors on CommentList +const commentListFailures = function (name, id, msg) { return name === "CommentList"; -}; +} a11y(React, { filterFn: commentListFailures }); ``` +#### `includeSrcNode : boolean` + If you want to log DOM element references for easy lookups in the DOM inspector, -use the `includeSrcNode` option. +use the `includeSrcNode` option. Because the lookup of the DOM nodes requires +`react-dom`, you must also pass that as an option: +```js +import ReactDOM from 'react-dom' +// ... +a11y(React, { + includeSrcNode: true +, ReactDOM: ReactDOM +}) ``` -a11y(React, { throw: true, includeSrcNode: true }); -``` + +If you're using `react-a11y` on the server-side, always set `includeSrcNode` to +`false`. The way DOM-lookups work is that they wait until everything is +rendered into the DOM and emit the warning after DOM update, but this doesn't happen +on the server-side, so no warnings will be shown. + +#### `device : [ string ]` Some test are only relevant for certain device types. For example, if you are building a mobile web app, you can filter out desktop-specific rules by specifying a specific device type: +```js +a11y(React, { + device: ['mobile'] +}) ``` -a11y(React, { device: ['mobile'] }); -``` + +#### `exclude : [string]` It's also possible exclude certain tests: +```js +a11y(React, { + exclude: ['REDUNDANT_ALT'] +}) ``` -a11y(React, { exclude: ['REDUNDANT_ALT'] }); -``` - -ReactDOM --------- -You can pass `ReactDOM` to `a11y` for `React 0.14` compatibility. -``` -a11y(React, { ReactDOM: ReactDOM }); -``` - -Cleaning Up In Tests --------------------- +## Cleaning Up In Tests Use the `restoreAll()` method to clean up mutations made to `React`. -Useful if you are using React-a11y in your test suite. +Useful if you are using `react-a11y` in your test suite: ```js -beforeEach(() => a11y(React)); -afterEach(() => a11y.restoreAll()); +beforeEach(() => a11y(React)) +afterEach(() => a11y.restoreAll()) ``` + +## Differences from upstream [`react-a11y`][react-a11y] + +I will try to stay close to the upstream `react-a11y` API, +and document differences here. + + - To use `includeSrcNode`, one __must__ pass `ReactDOM` as + well. `a11y` will throw an error if you don't do this. + This is because I want to move away from pre-`0.14` React. + +## TO DO + +These are things I need to do in the short run to make the project +usable: + + - [ ] add a build-step that mirrors changes in code-style such as + increased use of ES6 features. + - [ ] add [`eslint`][eslint] config so poeple can collaborate + more easy. + +## Plans + +These are some plans I've dreamt up for `react-a11y`: + + - [ ] **make rules pluggable like [eslint][eslint].** It would be nice + for everyone to be able to write their own rules and inject + them into `react-a11y`. First of all, this would mean less + maintenance for me, since poeple can build their own, and it + would make `react-a11y` a formidable validation tool. + - [ ] **create a nice project page** with documentation, because + that is what poeple like these days. + - [ ] move away from global variables in modules, everything is lamda. + This would make some bugs less likely. + + +[react-a11y]: https://github.com/reactjs/react-a11y +[eslint]: http://eslint.org + From 5debc04a83360bef7e41751ae3ea3fb4b2e7e24c Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 02:03:41 +0200 Subject: [PATCH 015/447] add message to filterFn args, like in docs --- lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 616b25c..9cd4e9d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -85,7 +85,7 @@ const failureHandler = (options = {}, reactEl, refs) => function (type, props, f } // display the failure if it isn't filtered - if ( filterFn(failureInfo, props.id) ) { + if ( filterFn(failureInfo, props.id, failureInfo.msg) ) { // how should we handle the message? const done = doThrow ? throwError : showWarning displayFailure(owner, failureInfo, opts, done) From 95eed1bda764cc27b9299fb9c10f12e8a86095fd Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 02:12:42 +0200 Subject: [PATCH 016/447] add info about fixed issues --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index e5378a9..849f021 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ the love it deserves.__ The original repo had a lot of open issues and unmerged pull requests. Eventually, I would like to see all code merged back into the original! +To see what's changed, look at [Differences from +`react-a11y`](#differences-from-upstream-react-a11y). ## WIP @@ -138,6 +140,21 @@ and document differences here. well. `a11y` will throw an error if you don't do this. This is because I want to move away from pre-`0.14` React. + - Started using `ReactDOM.finDOMNode` instead of `document.getElementById`, as + noted in upstream [#54](https://github.com/reactjs/react-a11y/issues/54). + This fixes a lot of issues from upstream: + - [#54](https://github.com/reactjs/react-a11y/issues/54) Use + React.findDOMNode to log element references + - [#55](https://github.com/reactjs/react-a11y/issues/55) Consider using ref + to log element references when there is a warning + - [#77](https://github.com/reactjs/react-a11y/issues/77) Breaks pure-render + checks + - [#85](https://github.com/reactjs/react-a11y/issues/85) Different + react-a11y ids (server side render) + + - Fixed upstream issue [#102](https://github.com/reactjs/react-a11y/issues/102) + by correctly inferring the component instance. + ## TO DO These are things I need to do in the short run to make the project From e1b426da0678115dca76a220c8becbe5692e0e57 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 02:13:29 +0200 Subject: [PATCH 017/447] remove contribution rules --- CONTRIBUTING.md | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 2667006..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,44 +0,0 @@ -### Tests - -All commits that fix bugs or add features need a test. - -`` Do not merge code without tests.`` - -### Commit Subjects for Public API Changes - -If your patch **changes the API or fixes a bug** please use one of the -following prefixes in your commit subject: - -- `[fixed] ...` -- `[changed] ...` -- `[added] ...` -- `[removed] ...` - -That ensures the subject line of your commit makes it into the -auto-generated changelog. Do not use these tags if your change doesn't -fix a bug and doesn't change the public API. - -Commits with changed, added, or removed, should probably be reviewed by -another collaborator. - -#### When using `[changed]` or `[removed]`... - -Please include an upgrade path with example code in the commit message. -If it doesn't make sense to do this, then it doesn't make sense to use -`[changed]` or `[removed]` :) - -### Docs - -Please update the docs with any API changes, the code and docs should -always be in sync. - -### Development - -- `npm test` will fire up a karma test runner and watch for changes - -### Build - -Please do not include the output of `scripts/build` in your commits, we -only do this when we release. (Also, you probably don't need to build -anyway unless you are fixing something around our global build.) - From 04cb67a8d5ed4ed2a868d8059b2fd706649397d6 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 02:14:17 +0200 Subject: [PATCH 018/447] add info about removed depenency --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 849f021..31fe45f 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,8 @@ and document differences here. - Fixed upstream issue [#102](https://github.com/reactjs/react-a11y/issues/102) by correctly inferring the component instance. + - Removed depency on `object.assign` + ## TO DO These are things I need to do in the short run to make the project From 8cd86b4ad8484569b50124b051280e94cdc33e13 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 02:17:47 +0200 Subject: [PATCH 019/447] remove paragraphs in lists --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 31fe45f..8d59167 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,6 @@ and document differences here. - To use `includeSrcNode`, one __must__ pass `ReactDOM` as well. `a11y` will throw an error if you don't do this. This is because I want to move away from pre-`0.14` React. - - Started using `ReactDOM.finDOMNode` instead of `document.getElementById`, as noted in upstream [#54](https://github.com/reactjs/react-a11y/issues/54). This fixes a lot of issues from upstream: @@ -151,10 +150,8 @@ and document differences here. checks - [#85](https://github.com/reactjs/react-a11y/issues/85) Different react-a11y ids (server side render) - - Fixed upstream issue [#102](https://github.com/reactjs/react-a11y/issues/102) by correctly inferring the component instance. - - Removed depency on `object.assign` ## TO DO From e86bba564ed8668624a6c4ff60dacaeccfab4276 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 02:21:15 +0200 Subject: [PATCH 020/447] enable js for all examples --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d59167..4a39303 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ accessibility tests. #### `throw : boolean` If you want it to throw errors instead of just warnings: -``` +```js a11y(React, { throw: true }) ``` From fa2b0fdd5a395ff448eade605c17d2290753a3d6 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 11:47:30 +0200 Subject: [PATCH 021/447] switch to more modern babel --- .babelrc | 7 +++++++ package.json | 13 +++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .babelrc diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..974a1ca --- /dev/null +++ b/.babelrc @@ -0,0 +1,7 @@ +{ + "presets": [ + "es2015", + "stage-0", + "react" + ] +} diff --git a/package.json b/package.json index 7c34061..00edb1c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "scripts": { "test": "karma start --single-run", "watch-tests": "npm test -- --watch", - "prepublish": "babel lib --out-dir dist", + "build": "babel lib --out-dir dist", + "prepublish": "npm run build", "release": "release" }, "authors": [ @@ -22,9 +23,10 @@ ], "license": "MIT", "devDependencies": { - "babel": "^5.2.17", - "babel-core": "^5.2.17", - "babel-loader": "^5.0.0", + "babel-cli": "^6.6.5", + "babel-preset-es2015": "^6.6.0", + "babel-preset-react": "^6.5.0", + "babel-preset-stage-0": "^6.5.0", "jsx-loader": "^0.12.2", "jsxhint": "^0.8.1", "karma": "^0.13.3", @@ -50,6 +52,5 @@ "react", "a11y" ], - "dependencies": { - } + "dependencies": {} } From c76f72fd18c27449426b43774bb703e5a122ec32 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 11:48:08 +0200 Subject: [PATCH 022/447] remove older react deps --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 00edb1c..39c470c 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "karma-sourcemap-loader": "^0.3.2", "karma-webpack": "^1.7.0", "mocha": "^2.0.1", - "react": "^0.12 || ^0.13 || ^0.14", + "react": "^0.14", "react-dom": "^0.14.7", "rf-release": "0.4.0", "webpack": "^1.4.13" From 19af0f2176ccf0cbb81ebe3caa7923c4e010768b Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 11:52:10 +0200 Subject: [PATCH 023/447] add eslint as linter --- .eslintrc | 368 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 2 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..d47e4ff --- /dev/null +++ b/.eslintrc @@ -0,0 +1,368 @@ + +# use babel parser (requires babel-eslint) +parser: babel-eslint + +# set environmet (to determine which globals are available) +env: + browser: true # browser global variables. + node: true # Node.js global variables and Node.js-specific rules. + es6: true # enable all ECMAScript 6 features except for modules. + +# enable plugins +plugins: + - react + - babel + +# enable all language features +parserOptions: + ecmaFeatures: + arrowFunctions: true # enable arrow functions + binaryLiterals: true # enable binary literals + blockBindings: true # enable let and const (aka block bindings) + classes: true # enable classes + defaultParams: true # enable default function parameters + destructuring: true # enable destructuring + forOf: true # enable for-of loops + generators: true # enable generators + modules: true # enable modules and global strict mode + objectLiteralComputedProperties: true # enable computed object literal property names + objectLiteralDuplicateProperties: true # enable duplicate object literal properties in strict mode + objectLiteralShorthandMethods: true # enable object literal shorthand methods + objectLiteralShorthandProperties: true # enable object literal shorthand properties + octalLiterals: true # enable octal literals + regexUFlag: true # enable the regular expression u flag + regexYFlag: true # enable the regular expression y flag + restParams: true # enable the rest parameters + spread: true # enable the spread operator + superInFunctions: true # enable super references inside of functions + templateStrings: true # enable template strings + unicodeCodePointEscapes: true # enable code point escapes + globalReturn: true # allow return statements in the global scope + jsx: true # enable JSX + +# configure rules +rules: + comma-dangle: + - error + - never + + # Possible Errors + no-cond-assign: off # disallow condition assignments (eg inside if block) + no-console: off # disallow console.log + no-constant-condition: error # disallow if (true) and if (false) + no-control-regex: off # disallow escape codes in regex + no-debugger: off # disallow debugger + no-dupe-args: error # disallow duplicate fn arguments + no-dupe-keys: error # disallow duplicate object keys + no-duplicate-case: error # disallow duplicate case statements + no-empty: warn # disallow empty blocks + no-empty-character-class: warn # siallow empty [] in regex + no-ex-assign: error # cannot assign to error in catch block + no-extra-boolean-cast: error # prevent superfluous boolean casts (eg !!!) + no-extra-parens: off # disallow superfluous parens + no-extra-semi: error # disallow semicolons + no-func-assign: error # disallow faulty function assignments + no-inner-declarations: warn # disallow decalring functions inside nested block + no-invalid-regexp: error # prevent invalid regexes + no-irregular-whitespace: error # prevent spacing characters other than space + no-negated-in-lhs: error # prevent !a in b instead of !(a in b) bug + no-obj-calls: error # prevent the accidental calling of global objects as functions + no-regex-spaces: warn # use / {3}/ instead of / / + no-sparse-arrays: error # disallow [,,] + no-unexpected-multiline: error # comly with ASI + no-unreachable: error # disallow unreachable code + use-isnan: error # never compare to NaN + valid-jsdoc: warn # validate jsdoc comments + valid-typeof: error # ensure string compared to typeof is valid + + # Best Practices + accessor-pairs: off # enforce getter/setter pairs in objects + array-callback-return: error # enforce return statements in callbacks of array’s methods + block-scoped-var: off # treat var statements as if they were block scoped + complexity: warn # specify the maximum cyclomatic complexity allowed in a program + consistent-return: error # require return statements to either always or never specify values + curly: off # specify curly brace conventions for all control statements + default-case: warn # require default case in switch statements + dot-location: # enforce consistent newlines before or after dots + - error + - property + dot-notation: error # encourage use of dot notation whenever possible + eqeqeq: error # require use of === and !== + guard-for-in: warn # ensure for-in loops have an if statement + no-alert: error # disallow use of alert, confirm, and prompt + no-caller: error # disallow use of arguments.caller or arguments.callee + no-case-declarations: error # disallow lexical declarations in case clauses + no-div-regex: error # disallow division operators explicitly at beginning of regular expression + no-else-return: off # disallow else after a return in an if + no-empty-function: warn # disallow use of empty functions + no-empty-pattern: error # disallow use of empty destructuring patterns + no-eq-null: error # disallow comparisons to null without a type-checking operator + no-eval: error # disallow use of eval() + no-extend-native: warn # disallow adding to native types + no-extra-bind: warn # disallow unnecessary function binding + no-extra-label: error # disallow unnecessary labels + no-fallthrough: warn # disallow fallthrough of case statements + no-floating-decimal: off # disallow the use of leading or trailing decimal points in numeric literals + no-implicit-coercion: error # disallow the type conversions with shorter notations + no-implicit-globals: warn # disallow var and named functions in global scope + no-implied-eval: error # disallow use of eval()-like methods + no-invalid-this: error # disallow this keywords outside of classes or class-like objects + no-iterator: off # disallow usage of __iterator__ property + no-labels: error # disallow use of labeled statements + no-lone-blocks: error # disallow unnecessary nested blocks + no-loop-func: error # disallow creation of functions within loops + no-magic-numbers: off # disallow the use of magic numbers + no-multi-spaces: off # disallow use of multiple spaces + no-multi-str: off # disallow use of multiline strings + no-native-reassign: error # disallow reassignments of native objects + no-new: error # disallow use of the new operator when not part of an assignment or comparison + no-new-func: error # disallow use of new operator for Function object + no-new-wrappers: error # disallow creating new instances of String,Number, and Boolean + no-octal: off # disallow use of octal literals + no-octal-escape: off # disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; + no-param-reassign: error # disallow reassignment of function parameters + no-proto: off # disallow usage of __proto__ property + no-redeclare: error # disallow declaring the same variable more than once + no-return-assign: error # disallow use of assignment in return statement + no-script-url: error # disallow use of javascript: urls. + no-self-assign: error # disallow assignments where both sides are exactly the same + no-self-compare: error # disallow comparisons where both sides are exactly the same + no-sequences: error # disallow use of the comma operator + no-throw-literal: warn # restrict what can be thrown as an exception + no-unmodified-loop-condition: warn # disallow unmodified conditions of loops + no-unused-expressions: # disallow usage of expressions in statement position + - warn + - allowShortCircuit: true + allowTernary: true + no-unused-labels: error # disallow unused labels + no-useless-call: error # disallow unnecessary .call() and .apply() + no-useless-concat: error # disallow unnecessary concatenation of literals or template literals + no-useless-escape: error # disallow unnecessary usage of escape character + no-void: error # disallow use of the void operator + no-warning-comments: off # disallow usage of configurable warning terms in comments: e.g. TODO or FIXME + no-with: error # disallow use of the with statement + radix: off # require use of the second argument for parseInt() + vars-on-top: off # require declaration of all vars at the top of their containing scope + wrap-iife: error # require immediate function invocation to be wrapped in parentheses + yoda: # require or disallow Yoda conditions + - error + + # Strict Mode + strict: off # require effective use of strict mode directives + + # Variables + init-declarations: error # enforce or disallow variable initializations at definition + no-catch-shadow: error # disallow the catch clause parameter name being the same as a variable in the outer scope + no-delete-var: error # disallow deletion of variables + no-label-var: off # disallow labels that share a name with a variable + no-restricted-globals: off # restrict usage of specified global variables + no-shadow: warn # disallow declaration of variables already declared in the outer scope + no-shadow-restricted-names: error # disallow shadowing of names such as arguments + no-undef: error # disallow use of undeclared variables unless mentioned in a /*global */ block + no-undef-init: error # disallow use of undefined when initializing variables + no-undefined: off # disallow use of undefined variable + no-unused-vars: warn # disallow declaration of variables that are not used in the code + no-use-before-define: error # disallow use of variables before they are defined + + # Node.js and CommonJS + callback-return: off # enforce return after a callback + global-require: off # enforce require() on top-level module scope + handle-callback-err: warn # enforce error handling in callbacks + no-mixed-requires: off # disallow mixing regular variable and require declarations + no-new-require: off # disallow use of new operator with the require function + no-path-concat: error # disallow string concatenation with __dirname and __filename + no-process-env: off # disallow use of process.env + no-process-exit: off # disallow process.exit() + no-restricted-modules: off # restrict usage of specified modules when loaded by require function + no-sync: off # disallow use of synchronous methods + + # Stylistic Issues + array-bracket-spacing: # enforce spacing inside array brackets + - error + - always + block-spacing: error # enforce spaces inside of single line blocks + brace-style: # enforce one true brace style + - error + - 1tbs + - allowSingleLine: true + camelcase: off # require camel case names + comma-spacing: error # enforce spacing before and after comma + comma-style: # enforce one true comma style + - error + - first + computed-property-spacing: error # require or disallow padding inside computed properties + consistent-this: off # enforce consistent naming when capturing the current execution context + eol-last: off # enforce newline at the end of file, with no multiple empty lines + func-names: off # require function expressions to have a name + func-style: # enforce use of function declarations or expressions + - error + - expression + id-blacklist: off # disallow certain identifiers to prevent them being used + id-length: off # enforce minimum and maximum identifier lengths (variable names, property names etc.) + id-match: off # require identifiers to match the provided regular expression + indent: off # specify tab or space width for your code + jsx-quotes: # specify whether double or single quotes should be used in JSX attributes + - error + - prefer-single + key-spacing: off # enforce spacing between keys and values in object literal properties + keyword-spacing: error # enforce spacing before and after keywords + linebreak-style: error # enforce linebreak style + lines-around-comment: off # enforce empty lines around comments + max-depth: off # specify the maximum depth that blocks can be nested + max-len: # specify the maximum length of a line in your program + - error + - 80 + - 2 + max-nested-callbacks: off # specify the maximum depth callbacks can be nested + max-params: off # specify the number of parameters that can be used in the function declaration + max-statements: off # specify the maximum number of statement allowed in a function + max-statements-per-line: off # specify the maximum number of statements allowed per line + new-cap: off # require a capital letter for constructors + new-parens: error # disallow the omission of parentheses when invoking a constructor with no arguments + newline-after-var: off # require or disallow an empty newline after variable declarations + newline-before-return: error # require newline before return statement + newline-per-chained-call: # enforce newline after each call when chaining the calls + - warn + - ignoreChainWithDepth: 3 + no-array-constructor: off # disallow use of the Array constructor + no-bitwise: warn # disallow use of bitwise operators + no-continue: error # disallow use of the continue statement + no-inline-comments: off # disallow comments inline after code + no-lonely-if: error # disallow if as the only statement in an else block + no-mixed-spaces-and-tabs: error # disallow mixed spaces and tabs for indentation + no-multiple-empty-lines: off # disallow multiple empty lines + no-negated-condition: warn # disallow negated conditions + no-nested-ternary: warn # disallow nested ternary expressions + no-new-object: error # disallow the use of the Object constructor + no-plusplus: error # disallow use of unary operators, ++ and -- + no-restricted-syntax: off # disallow use of certain syntax in code + no-spaced-func: error # disallow space between function identifier and application + no-ternary: off # disallow the use of ternary operators + no-trailing-spaces: error # disallow trailing whitespace at the end of lines + no-underscore-dangle: off # disallow dangling underscores in identifiers + no-unneeded-ternary: error # disallow the use of ternary operators when a simpler alternative exists + no-whitespace-before-property: error # disallow whitespace before properties + object-curly-spacing: off # require or disallow padding inside curly braces + one-var: off # require or disallow one variable declaration per function + one-var-declaration-per-line: off # require or disallow an newline around variable declarations + operator-assignment: warn # require assignment operator shorthand where possible or prohibit it entirely + operator-linebreak: # enforce operators to be placed before or after line breaks + - error + - before + - overrides: + =: after + padded-blocks: off # enforce padding within blocks + quote-props: # require quotes around object literal property names + - error + - as-needed + - keywords: true + quotes: # specify whether backticks, double or single quotes should be used + - error + - single + require-jsdoc: off # require JSDoc comment + semi: # require or disallow use of semicolons instead of ASI + - error + - never + semi-spacing: error # enforce spacing before and after semicolons + sort-imports: off # enforce sorting import declarations within module + sort-vars: off # enforce sorting variables within the same declaration block + space-before-blocks: error # require or disallow a space before blocks + space-before-function-paren: error # require or disallow a space before function opening parenthesis + space-in-parens: off # require or disallow spaces inside parentheses + space-infix-ops: error # require spaces around operators + space-unary-ops: error # require or disallow spaces before/after unary operators + spaced-comment: error # require or disallow a space immediately following the // or /* in a comment + wrap-regex: off # require regex literals to be wrapped in parentheses + + # ECMAScript 6 + arrow-body-style: off # require braces in arrow function body + arrow-parens: off # require parens in arrow function arguments + arrow-spacing: error # require space before/after arrow function’s arrow + constructor-super: warn # ensure calling of super() in constructors + generator-star-spacing: off # enforce spacing around the * in generator functions + no-class-assign: error # disallow modifying variables of class declarations + no-confusing-arrow: warn # disallow arrow functions where they could be confused with comparisons + no-const-assign: error # disallow modifying variables that are declared using const + no-dupe-class-members: error # disallow duplicate name in class members + no-duplicate-imports: error # disallow duplicate module imports + no-new-symbol: error # disallow use of the new operator with the Symbol object + no-restricted-imports: off # restrict usage of specified modules when loaded by import declaration + no-this-before-super: error # disallow use of this/super before calling super() in constructors + no-useless-constructor: error # disallow unnecessary constructor + no-var: error # require let or const instead of var + object-shorthand: off # require method and property shorthand syntax for object literals + prefer-arrow-callback: off # suggest using arrow functions as callbacks + prefer-const: warn # suggest using const declaration for variables that are never reassigned after declared + prefer-reflect: off # suggest using Reflect methods where applicable + prefer-rest-params: error # suggest using the rest parameters instead of arguments + prefer-spread: error # suggest using the spread operator instead of .apply() + prefer-template: error # suggest using template literals instead of strings concatenation + require-yield: off # disallow generator functions that do not have yield + template-curly-spacing: error # enforce spacing around embedded expressions of template strings + yield-star-spacing: # enforce spacing around the * in yield* expressions + - error + - both + + # JSX + react/display-name: off # Prevent missing displayName in a React component definition + react/forbid-prop-types: off # Forbid certain propTypes + react/no-danger: off # Prevent usage of dangerous JSX properties + react/no-deprecated: error # Prevent usage of deprecated methods + react/no-did-mount-set-state: off # Prevent usage of setState in componentDidMount + react/no-did-update-set-state: off # Prevent usage of setState in componentDidUpdate + react/no-direct-mutation-state: error # Prevent direct mutation of this.state + react/no-is-mounted: error # Prevent usage of isMounted + react/no-multi-comp: off # Prevent multiple component definition per file + react/no-set-state: off # Prevent usage of setState + react/no-string-refs: off # Prevent using string references in ref attribute. + react/no-unknown-property: error # Prevent usage of unknown DOM property (fixable) + react/prefer-es6-class: error # Enforce ES5 or ES6 class for React Components + react/prefer-stateless-function: warn # Enforce stateless React Components to be written as a pure function + react/prop-types: off # Prevent missing props validation in a React component definition + react/react-in-jsx-scope: error # Prevent missing React when using JSX + react/require-extension: off # Restrict file extensions that may be required + react/self-closing-comp: warn # Prevent extra closing tags for components without children + react/sort-comp: off # Enforce component methods order + react/sort-prop-types: off # Enforce propTypes declarations alphabetical sorting + react/wrap-multilines: error # Prevent missing parentheses around multilines JSX (fixable) + + react/jsx-boolean-value: error # Enforce boolean attributes notation in JSX (fixable) + react/jsx-closing-bracket-location: error # Validate closing bracket location in JSX (fixable) + react/jsx-curly-spacing: error # Enforce or disallow spaces inside of curly braces in JSX attributes (fixable) + react/jsx-equals-spacing: off # Enforce or disallow spaces around equal signs in JSX attributes (fixable) + react/jsx-handler-names: off # Enforce event handler naming conventions in JSX + react/jsx-indent-props: # Validate props indentation in JSX (fixable) + - error + - 2 + react/jsx-indent: # Validate JSX indentation + - error + - 2 + react/jsx-key: warn # Validate JSX has key prop when in array or iterator + react/jsx-max-props-per-line: off # Limit maximum of props on a single line in JSX + react/jsx-no-bind: warn # Prevent usage of .bind() and arrow functions in JSX props + react/jsx-no-duplicate-props: error # Prevent duplicate props in JSX + react/jsx-no-literals: off # Prevent usage of unwrapped JSX strings + react/jsx-no-undef: error # Disallow undeclared variables in JSX + react/jsx-pascal-case: error # Enforce PascalCase for user-defined JSX components + react/jsx-sort-props: off # Enforce props alphabetical sorting + react/jsx-space-before-closing: error # Validate spacing before closing bracket in JSX (fixable) + react/jsx-uses-react: 1 # Prevent React to be incorrectly marked as unused + react/jsx-uses-vars: 1 # Prevent variables used in JSX to be incorrectly marked as unused + + # Babel + babel/generator-star-spacing: # Handles async/await functions correctly + - error + - both + babel/new-cap: warn # Ignores capitalized decorators (@Decorator) + babel/array-bracket-spacing: # Handles destructuring arrays with flow type in function parameters + - error + - always + babel/object-curly-spacing: # doesn't complain about export x from "mod"; or export * as x from "mod"; + - error + - always + babel/object-shorthand: error # doesn't fail when using object spread (...obj) + babel/arrow-parens: off # Handles async functions correctly + babel/no-await-in-loop: # guard against awaiting async functions inside of a loop + - error + +# vim: ft=yaml diff --git a/package.json b/package.json index 39c470c..2515620 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "scripts": { "test": "karma start --single-run", "watch-tests": "npm test -- --watch", + "lint": "eslint lib", "build": "babel lib --out-dir dist", "prepublish": "npm run build", "release": "release" @@ -24,11 +25,14 @@ "license": "MIT", "devDependencies": { "babel-cli": "^6.6.5", + "babel-eslint": "^6.0.2", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-0": "^6.5.0", + "eslint": "^2.7.0", + "eslint-plugin-babel": "^3.1.0", + "eslint-plugin-react": "^4.2.3", "jsx-loader": "^0.12.2", - "jsxhint": "^0.8.1", "karma": "^0.13.3", "karma-chrome-launcher": "^0.1.7", "karma-cli": "0.0.4", From 7688dcb8afeb14d6f4a4ad9d713581b666e53b3c Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 12:00:41 +0200 Subject: [PATCH 024/447] ignore npm-debug.log --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3b9923c..a6ae2b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -dist +dist/ node_modules/ +npm-debug.log From 73d9ddf4112353f15b40926ab6c15d00d95123a4 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 12:20:29 +0200 Subject: [PATCH 025/447] prepare tests for es6 --- karma.conf.js | 67 ++++++++++++++++++------------------- lib/__tests__/index-test.js | 13 +++---- lib/assertions.js | 2 +- package.json | 8 ++--- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 9da172e..3824caa 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,43 +1,42 @@ -var webpack = require('webpack'); +var webpack = require('webpack') module.exports = function (config) { config.set({ - - browserNoActivityTimeout: 30000, - - browsers: [ process.env.CONTINUOUS_INTEGRATION ? 'Firefox' : 'Chrome' ], - - singleRun: process.env.CONTINUOUS_INTEGRATION === 'true', - - frameworks: [ 'mocha' ], - - files: [ - 'tests.webpack.js' - ], - - preprocessors: { - 'tests.webpack.js': [ 'webpack', 'sourcemap' ] - }, - - reporters: [ 'dots' ], - - webpack: { - devtool: 'inline-source-map', - module: { - loaders: [ - { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } - ] - }, - plugins: [ + browserNoActivityTimeout: 30000 + , browsers: [ process.env.CONTINUOUS_INTEGRATION ? 'Firefox' : 'Chrome' ] + , singleRun: process.env.CONTINUOUS_INTEGRATION === 'true' + , frameworks: [ 'mocha' ] + , plugins: [ + 'karma-mocha' + , 'karma-chrome-launcher' + , 'karma-firefox-launcher' + , 'karma-webpack' + , 'karma-sourcemap-loader' + ] + , files: [ + 'lib/__tests__/*.js' + ] + , preprocessors: { + 'lib/__tests__/*.js': [ 'webpack', 'sourcemap' ] + } + // , reporters: [ 'dots' ] + , webpack: { + devtool: 'inline-source-map' + , module: { + loaders: [{ + test: /\.js$/ + , exclude: /node_modules/ + , loader: 'babel-loader' + }] + } + , plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('test') }) ] - }, - - webpackServer: { + } + , webpackMiddleware: { noInfo: true } - - }); -}; + }) +} diff --git a/lib/__tests__/index-test.js b/lib/__tests__/index-test.js index e26a1c7..f4c6f57 100644 --- a/lib/__tests__/index-test.js +++ b/lib/__tests__/index-test.js @@ -1,9 +1,10 @@ -var React = require('react'); -var ReactDOM = require('react-dom'); -var assert = require('assert'); -var a11y = require('../index'); -var assertions = require('../assertions'); -var _after = require('../after'); +import React from 'react' +import ReactDOM from 'react-dom' +import assert from 'assert' + +import a11y from '../index' +import _after from '../after' +import assertions from '../assertions' var k = () => {}; diff --git a/lib/assertions.js b/lib/assertions.js index 5655647..a0d6d28 100644 --- a/lib/assertions.js +++ b/lib/assertions.js @@ -1,4 +1,4 @@ -var after = require('./after'); +import after from './after' var React; var ReactDOM; diff --git a/package.json b/package.json index 2515620..3f2e877 100644 --- a/package.json +++ b/package.json @@ -26,16 +26,16 @@ "devDependencies": { "babel-cli": "^6.6.5", "babel-eslint": "^6.0.2", + "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-0": "^6.5.0", "eslint": "^2.7.0", "eslint-plugin-babel": "^3.1.0", "eslint-plugin-react": "^4.2.3", - "jsx-loader": "^0.12.2", - "karma": "^0.13.3", + "karma": "^0.13.22", "karma-chrome-launcher": "^0.1.7", - "karma-cli": "0.0.4", + "karma-cli": "^0.1.2", "karma-firefox-launcher": "^0.1.3", "karma-mocha": "^0.1.10", "karma-sourcemap-loader": "^0.3.2", @@ -44,7 +44,7 @@ "react": "^0.14", "react-dom": "^0.14.7", "rf-release": "0.4.0", - "webpack": "^1.4.13" + "webpack": "^1.12.14" }, "tags": [ "accessibility", From 97f58a51e15b4a9975b547e8244f1f5abd1ced0f Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 12:22:49 +0200 Subject: [PATCH 026/447] move tests to tests/ folder --- karma.conf.js | 4 ++-- lib/__tests__/index-test.js => test/index.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename lib/__tests__/index-test.js => test/index.js (99%) diff --git a/karma.conf.js b/karma.conf.js index 3824caa..5b725d1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -14,10 +14,10 @@ module.exports = function (config) { , 'karma-sourcemap-loader' ] , files: [ - 'lib/__tests__/*.js' + 'test/**/*.js' ] , preprocessors: { - 'lib/__tests__/*.js': [ 'webpack', 'sourcemap' ] + 'test/**/*.js': [ 'webpack', 'sourcemap' ] } // , reporters: [ 'dots' ] , webpack: { diff --git a/lib/__tests__/index-test.js b/test/index.js similarity index 99% rename from lib/__tests__/index-test.js rename to test/index.js index f4c6f57..93c7394 100644 --- a/lib/__tests__/index-test.js +++ b/test/index.js @@ -2,9 +2,9 @@ import React from 'react' import ReactDOM from 'react-dom' import assert from 'assert' -import a11y from '../index' -import _after from '../after' -import assertions from '../assertions' +import a11y from '../lib/index' +import _after from '../lib/after' +import assertions from '../lib/assertions' var k = () => {}; From 10be880d72b023946e4f87d0702f8fd394db610d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 12:23:52 +0200 Subject: [PATCH 027/447] remove superfluous test.webpack.js --- tests.webpack.js | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 tests.webpack.js diff --git a/tests.webpack.js b/tests.webpack.js deleted file mode 100644 index 019924e..0000000 --- a/tests.webpack.js +++ /dev/null @@ -1,2 +0,0 @@ -var context = require.context('./lib', true, /-test\.js$/); -context.keys().forEach(context); From 4f2ef9f759b0feea9581dbfabe0690a9b3094891 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 12:31:12 +0200 Subject: [PATCH 028/447] move changelog preview script to package.json --- package.json | 3 ++- scripts/preview-release | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) delete mode 100755 scripts/preview-release diff --git a/package.json b/package.json index 3f2e877..76b4c41 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "lint": "eslint lib", "build": "babel lib --out-dir dist", "prepublish": "npm run build", - "release": "release" + "release": "release", + "preview-release": "changelog -t previes -s" }, "authors": [ "Ryan Florence", diff --git a/scripts/preview-release b/scripts/preview-release deleted file mode 100755 index 40ac20e..0000000 --- a/scripts/preview-release +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -node_modules/rf-release/node_modules/.bin/changelog -t preview -s From 62dfd6022168d0f4fedf640d6ea4c54579bbb120 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 12:39:02 +0200 Subject: [PATCH 029/447] clean up webpack config --- webpack.config.js | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index a92b111..6f00b66 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,10 +1,10 @@ -var webpack = require('webpack'); +var webpack = require('webpack') var plugins = [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') }) -]; +] if (process.env.COMPRESS) { plugins.push( @@ -13,30 +13,29 @@ if (process.env.COMPRESS) { warnings: false } }) - ); + ) } module.exports = { - - output: { - library: 'ReactJS.A11y', - libraryTarget: 'var' - }, - - externals: process.env.NODE_DIST ? {} : { + entry: './lib/index.js' +, output: { + filename: 'react-a11y.js' + , path: './dist' + , library: 'A11y' + , libraryTarget: 'var' + } +, externals: process.env.NODE_DIST ? {} : { react: 'React' - }, - - node: { + } +, node: { buffer: false - }, - - plugins: plugins, - - module: { - loaders: [ - { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } - ] } - -}; +, plugins: plugins +, module: { + loaders: [{ + test: /\.js$/ + , exclude: /node_modules/ + , loader: 'babel-loader' + }] + } +} From 4be5dfe32df3a5e91af447c6f7070c550cc80c9f Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 12:39:13 +0200 Subject: [PATCH 030/447] add bundle script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 76b4c41..8341eed 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "build": "babel lib --out-dir dist", "prepublish": "npm run build", "release": "release", + "bundle": "webpack", "preview-release": "changelog -t previes -s" }, "authors": [ From cf4d14c9e23671ad1edd18c02c1b61a3614630fd Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 12:40:46 +0200 Subject: [PATCH 031/447] mv source files to src, and output to lib. dist remains for webpacked files --- .gitignore | 1 + .npmignore | 3 ++- package.json | 6 +++--- {lib => src}/after.js | 0 {lib => src}/assertions.js | 0 {lib => src}/index.js | 0 {lib => src}/tests.js | 0 7 files changed, 6 insertions(+), 4 deletions(-) rename {lib => src}/after.js (100%) rename {lib => src}/assertions.js (100%) rename {lib => src}/index.js (100%) rename {lib => src}/tests.js (100%) diff --git a/.gitignore b/.gitignore index a6ae2b5..31ccd04 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist/ +lib/ node_modules/ npm-debug.log diff --git a/.npmignore b/.npmignore index d210dc2..6ec9695 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,7 @@ CONTRIBUTING.md bower.json -lib +src +dist karma.conf.js scripts webpack.config.js diff --git a/package.json b/package.json index 8341eed..02d3543 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "react-a11y", "version": "0.3.2", "description": "Warns about potential accessibility issues with your React elements.", - "main": "./dist/index.js", + "main": "./lib/index.js", "repository": { "type": "git", "url": "https://github.com/reactjs/react-a11y.git" @@ -12,8 +12,8 @@ "scripts": { "test": "karma start --single-run", "watch-tests": "npm test -- --watch", - "lint": "eslint lib", - "build": "babel lib --out-dir dist", + "lint": "eslint src", + "build": "babel src --out-dir lib", "prepublish": "npm run build", "release": "release", "bundle": "webpack", diff --git a/lib/after.js b/src/after.js similarity index 100% rename from lib/after.js rename to src/after.js diff --git a/lib/assertions.js b/src/assertions.js similarity index 100% rename from lib/assertions.js rename to src/assertions.js diff --git a/lib/index.js b/src/index.js similarity index 100% rename from lib/index.js rename to src/index.js diff --git a/lib/tests.js b/src/tests.js similarity index 100% rename from lib/tests.js rename to src/tests.js From 321bcd41a5ef674a0f9baa9efd93435bd82d9b69 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 6 Apr 2016 21:01:05 +0200 Subject: [PATCH 032/447] reduce lint --- .eslintrc | 6 ++-- src/index.js | 80 +++++++++++++++++++++++++--------------------------- src/tests.js | 28 ++++++++++-------- 3 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.eslintrc b/.eslintrc index d47e4ff..57c45a1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -177,9 +177,7 @@ rules: no-sync: off # disallow use of synchronous methods # Stylistic Issues - array-bracket-spacing: # enforce spacing inside array brackets - - error - - always + array-bracket-spacing: off # enforce spacing inside array brackets block-spacing: error # enforce spaces inside of single line blocks brace-style: # enforce one true brace style - error @@ -220,7 +218,7 @@ rules: new-cap: off # require a capital letter for constructors new-parens: error # disallow the omission of parentheses when invoking a constructor with no arguments newline-after-var: off # require or disallow an empty newline after variable declarations - newline-before-return: error # require newline before return statement + newline-before-return: off # require newline before return statement newline-per-chained-call: # enforce newline after each call when chaining the calls - warn - ignoreChainWithDepth: 3 diff --git a/src/index.js b/src/index.js index 9cd4e9d..f5f0a91 100644 --- a/src/index.js +++ b/src/index.js @@ -14,7 +14,7 @@ const always = () => true const throwError = function (...args) { const last = args[args.length - 1] if ( last.outerHTML ) { - args[args.length - 1] = 'Element: \n ' + last.outerHTML + args[args.length - 1] = `Element: \n ${last.outerHTML}` } const error = new Error(args.join(' ')) @@ -48,54 +48,50 @@ const displayFailure = function (component, failureInfo, options = {}, done) { // unpack the ref const srcNode = refs.node - if (includeSrcNode === "asString") { - return done(tagName, msg, `Source Node: ${srcNode.outerHTML}`) + if (includeSrcNode === 'asString') { + done(tagName, msg, `Source Node: ${srcNode.outerHTML}`) } else if (srcNode) { - return done(tagName, msg, srcNode) + done(tagName, msg, srcNode) } }) } else { - return done(tagName, msg) + done(tagName, msg) } } -const failureHandler = (options = {}, reactEl, refs) => function (type, props, failureMsg) { - const { - includeSrcNode = false - , throw: doThrow = false - , warningPrefix = '' - , filterFn = always - } = options - - // get the owning component - const owner = reactEl._owner - - // if a component instance, use the component's name, - // if a ReactElement instance, use the tag name + id (e.g. div#foo) - const tagName = owner && owner.getName() || type - - const failureInfo = { - tagName - , msg: warningPrefix.concat(failureMsg) - , refs - } +const failureHandler = (options = {}, reactEl, refs) => + function (type, props, failureMsg) { + const { + includeSrcNode = false + , 'throw': doThrow = false + , warningPrefix = '' + , filterFn = always + } = options + + // get the owning component + const owner = reactEl._owner + + // if a component instance, use the component's name, + // if a ReactElement instance, use the tag name + id (e.g. div#foo) + const tagName = owner && owner.getName() || type + + const failureInfo = { + tagName + , msg: warningPrefix.concat(failureMsg) + , refs + } - const opts = { - includeSrcNode - } + const opts = { + includeSrcNode + } - // display the failure if it isn't filtered - if ( filterFn(failureInfo, props.id, failureInfo.msg) ) { - // how should we handle the message? - const done = doThrow ? throwError : showWarning - displayFailure(owner, failureInfo, opts, done) + // display the failure if it isn't filtered + if ( filterFn(failureInfo, props.id, failureInfo.msg) ) { + // how should we handle the message? + const done = doThrow ? throwError : showWarning + displayFailure(owner, failureInfo, opts, done) + } } -} - -let nextRef = 0 -const createRef = function (props = {}) { - return (props || {}).ref || `a11y-${nextRef++}` -} const reactA11y = function (React, options = {}) { const { @@ -126,7 +122,9 @@ const reactA11y = function (React, options = {}) { // this needs to be an object so that it can be passed // by reference, and hold chaning state const refs = {} - const ref = node => refs.node = node + const ref = function (node) { + refs.node = node + } const newProps = typeof type === 'string' ? { ...props, ref } : props const reactEl = _createElement(type, newProps, ...children) @@ -145,7 +143,7 @@ const reactA11y = function (React, options = {}) { } } -reactA11y.restoreAll = function() { +reactA11y.restoreAll = function () { _React.createElement = _createElement after.restorePatchedMethods() } diff --git a/src/tests.js b/src/tests.js index 721b39e..05daf9b 100644 --- a/src/tests.js +++ b/src/tests.js @@ -1,24 +1,25 @@ import assertions from './assertions' const shouldRunTest = function (testName, options = {} ) { - let { + const { exclude = [] , device } = options - if ( device == 'mobile' ) { - exclude = exclude.concat(assertions.mobileExclusions) - } + const exclusions = device === 'mobile' + ? exclude.concat(assertions.mobileExclusions) + : exclude - return exclude.indexOf(testName) === -1 + return exclusions.indexOf(testName) === -1 } const runTagTests = function (tagName, props, children, options, onFailure) { const tagTests = assertions.tags[tagName] || [] Object.keys(tagTests).forEach(function (key) { - const testFailed = shouldRunTest(key, options) && - !tagTests[key].test(tagName, props, children) + const testFailed = + shouldRunTest(key, options) + && !tagTests[key].test(tagName, props, children) if (tagTests[key] && testFailed) { onFailure(tagName, props, tagTests[key].msg) @@ -29,10 +30,13 @@ const runTagTests = function (tagName, props, children, options, onFailure) { const runPropTests = function (tagName, props, children, options, onFailure) { Object.keys(props).forEach(function (propName) { if ( props[propName] !== null || props[propName] !== undefined ) { - const propTests = assertions.props[propName] || []; + + const propTests = assertions.props[propName] || [] + Object.keys(propTests).forEach(function (key) { - const testTailed = shouldRunTest(key, options) && - !propTests[key].test(tagName, props, children) + const testTailed = + shouldRunTest(key, options) + && !propTests[key].test(tagName, props, children) if (propTests[key] && testTailed) { onFailure(tagName, props, propTests[key].msg) @@ -42,7 +46,7 @@ const runPropTests = function (tagName, props, children, options, onFailure) { }) } -var runLabelTests = function (tagName, props, children, options, onFailure) { +const runLabelTests = function (tagName, props, children, options, onFailure) { const renderTests = assertions.render Object.keys(renderTests).forEach(function (key) { @@ -56,7 +60,7 @@ var runLabelTests = function (tagName, props, children, options, onFailure) { } // run all tests -const allTests = [runTagTests, runPropTests, runLabelTests] +const allTests = [ runTagTests, runPropTests, runLabelTests ] const runTests = function (tagName, props, children, options, onFailure) { allTests.forEach(function (test) { test(tagName, props, children, options, onFailure) From 8f7411a40b3813277d5a122407c9ec0815b97be6 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 7 Apr 2016 14:13:13 +0200 Subject: [PATCH 033/447] update module path --- test/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/index.js b/test/index.js index 93c7394..d0aea16 100644 --- a/test/index.js +++ b/test/index.js @@ -2,9 +2,9 @@ import React from 'react' import ReactDOM from 'react-dom' import assert from 'assert' -import a11y from '../lib/index' -import _after from '../lib/after' -import assertions from '../lib/assertions' +import a11y from '../src/index' +import _after from '../src/after' +import assertions from '../src/assertions' var k = () => {}; From 723b83873ee5639479ad17f426dcf7272ab45d15 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 7 Apr 2016 14:36:08 +0200 Subject: [PATCH 034/447] fix all tests --- src/tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests.js b/src/tests.js index 05daf9b..7d65592 100644 --- a/src/tests.js +++ b/src/tests.js @@ -3,10 +3,10 @@ import assertions from './assertions' const shouldRunTest = function (testName, options = {} ) { const { exclude = [] - , device + , device = [] } = options - const exclusions = device === 'mobile' + const exclusions = device.indexOf('mobile') > -1 ? exclude.concat(assertions.mobileExclusions) : exclude From 9fa1b7d534741d646b135434fd9a0aecfe452b98 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 7 Apr 2016 22:43:39 +0200 Subject: [PATCH 035/447] big refactor - make A11y a separate class and start implementing new options --- src/a11y.js | 139 +++++++++++++++++++++++++++++++++++++++ src/defaults.js | 113 ++++++++++++++++++++++++++++++++ src/index.js | 155 ++++---------------------------------------- src/util/browser.js | 1 + 4 files changed, 266 insertions(+), 142 deletions(-) create mode 100644 src/a11y.js create mode 100644 src/defaults.js create mode 100644 src/util/browser.js diff --git a/src/a11y.js b/src/a11y.js new file mode 100644 index 0000000..8c352b0 --- /dev/null +++ b/src/a11y.js @@ -0,0 +1,139 @@ +import after from './after' +import defs from './defaults' +import browser from './util/browser' + +class A11y { + + /** + * @arg React - The React instance you want to patch + * @arg {object} options - the options + */ + constructor (React, options = {}) { + this.options = defs(options) // extend default opts + this.React = React // save react for undoing patches + this.ReactDOM = opts.ReactDOM // save ReactDOM, we'll need it + + if (!this.React && !this.React.createElement) { + throw new Error('Missing parameter: React') + } + + if (!this.ReactDOM) { + throw new Error('Missing option: ReactDOM') + } + + this.patchReact() + } + + /** + * Patch React, replacing its createElement by our implementation + * so we can run the tests + */ + patchReact () { + + // save old createElement + this._createElement = this.React.createElement + + const that = this + this.React.createElement = function (klass, _props = {}, ...children) { + // fix for props = null + const props = _props || {} + + // create a refs object to hold the ref. + // this needs to be an object so that it can be passed + // by reference, and hold chaning state. + const refs = {} + const ref = function (node) { + refs.node = node + + // maintain behaviour when ref prop was already set + if ( typeof props.ref === 'function' ) { + props.ref(node) + } else if ( typeof props.ref === 'string' ) { + // TODO: fix this + throw new Error('react-a11y does not support string refs yet') + } + } + // TODO: make sure we don't override existing refs or fix it up so it + // does not matter + const newProps = typeof klass === 'string' ? { ...props, ref } : props + + const reactEl = that._createElement(type, newProps, ...children) + + // only test html elements + if (typeof klass === 'string') { + + const handler = that.failureHandler(reactEl, refs) + const childrenForTest = children.length === 0 + ? props.children || [] + : children + + runTests(klass, props, childrenForTest, that.options, handler) + } + + return reactEl + } + } + + /** + * Restore React and all components as if we were never here + */ + restoreAll () { + this.React.createElement = this._createElement + after.restorePatchedMethods() + } + + /** + * Creates a failure handler based on the element that was created + * @arg reactEl - The react element this failure is for + * @arg refs - The object that holds the DOM node (passed by ref) + */ + failureHandler (reactEl, refs) { + const { + reporter + , filterFn + } = this.options + + /** + * @arg {string} type - The HTML tagname of the element + * @arg {object} props - The props that were passed to the element + * @arg {string} msg - The warning message + */ + return function (type, props, msg) { + + // get the owning component (the one that has + // the element in its render fn) + const owner = reactEl._owner + + // if there is an owner, use its name + // if not, use the tagname of the violating elemnent + const displayName = owner && owner.getName || type + + // stop if we're not allowed to proceed + if ( !filterFn(displayName, props.id, msg) ) { + return + } + + // gather all info for the reporter + const info = { + displayName + , msg + } + + // if we need to include the rendered node, we need to wait until + // the owner has rendered + if ( owner && browser ) { + const instance = component._instance + // Cannot log a node reference until the component is in the DOM, + // so defer the call until componentDidMount or componentDidUpdate. + after.render(instance, function () { + // unpack the ref + const DOMNode = refs.node + + reporter({ ...info, DOMNode }) + }) + } else { + reporter(info) + } + } + } +} diff --git a/src/defaults.js b/src/defaults.js new file mode 100644 index 0000000..9a9ac2c --- /dev/null +++ b/src/defaults.js @@ -0,0 +1,113 @@ +import after from './after' + +/** + * Throws an error based on the warning + * If the last argument is a DOM node, it + * coerces it to a string before throwing. + */ +const throwError = function (...args) { + const last = args[args.length - 1] + if ( last.outerHTML ) { + args[args.length - 1] = `Element: \n ${last.outerHTML}` + } + + const error = new Error(args.join(' ')) + error.element = last + + throw error +} + +/** + * Show a warning + */ +const showWarning = function (...args) { + console.warn(...args) +} + +/** + * Creates a reporter function based on deprecated options + * @arg opts - The options passed by the user + */ +const mkReporter = function (opts) { + const { + doThrow = false + , warningPrefix = '' + } = opts + + return function (info) { + const { + msg + , owner + , DOMNode + } = info + + // build warning + const warning = [ + owner + , warningPrefix.concat(msg) + ].concat(DOMNode || []) + + if ( doThrow ) { + throwError(...warning) + } else { + showWarning(...warning) + } + } +} + +/** + * Generate a deprecation warning when a key is present + * in the options object + * @arg {object} opts - the options object under scrutiny + * @arg {string} name - the name of the deprecated option + * @arg {string} msg - an optional reason for the deprecation + */ +const deprecate = function (opts, name, msg = '') { + if ( name in opts ) { + console.warn(`react-a11y: the \`${name}\` options is deprecated. ${msg}`) + } +} + +/** + * Make a certain option mandatory + * @arg {object} opts - the options object under scrutiny + * @arg {string} name - the name of the mandatory option + * @arg {string} msg - an optional reason + */ +const mandatory = function (opts, name, msg = '') { + if ( !(name in opts) ) { + console.warn(`react-a11y: the \`${name}\` option is mandatory. ${msg}`) + } +} + +// always resolve to true +const always = () => true + +// deprecation message +const msg = 'Use the `reporter` option to change the way warnings are displayed." + +/** + * Normalize and validate the options that the user passed in. + * @arg {object} opts - The opts the user passed in + */ +export default function (opts = {}) { + deprecate(opts, 'includeSrcNode', msg) + deprecate(opts, 'throw', msg) + deprecate(opts, 'warningPrefix', msg) + mandatory(opts, 'ReactDOM') + + const { + reporter = mkReporter(opts) // make a reporter based on (deprecated) options + , filterFn = always + , plugins = [] + , rules = {} + } = opts + + return { + filterFn + , reporter + , plugins + , rules + } +} + diff --git a/src/index.js b/src/index.js index f5f0a91..ffa4395 100644 --- a/src/index.js +++ b/src/index.js @@ -1,151 +1,22 @@ -import after from './after' -import assertions from './assertions' -import runTests from './tests' +import A11y from './a11y' -let _React -let _createElement -let _ReactDOM - -// always resolve to true -const always = () => true - -// throw error with string representation -// of node -const throwError = function (...args) { - const last = args[args.length - 1] - if ( last.outerHTML ) { - args[args.length - 1] = `Element: \n ${last.outerHTML}` - } - - const error = new Error(args.join(' ')) - error.element = last - - throw error -} - -// just show the warning -const showWarning = function (...args) { - console.warn(...args) -} - -// renders the failure message based on options and component lifycycle -const displayFailure = function (component, failureInfo, options = {}, done) { - const { - includeSrcNode = false - } = options - - const { - tagName - , msg - , refs - } = failureInfo - - if (includeSrcNode && component) { - const instance = component._instance - // Cannot log a node reference until the component is in the DOM, - // so defer the call until componentDidMount or componentDidUpdate. - after.render(instance, function () { - // unpack the ref - const srcNode = refs.node - - if (includeSrcNode === 'asString') { - done(tagName, msg, `Source Node: ${srcNode.outerHTML}`) - } else if (srcNode) { - done(tagName, msg, srcNode) - } - }) +let instance = false +const a11y = function (...opts) { + if ( instance ) { + throw new Error('react-a11y is already installed') } else { - done(tagName, msg) + instance = new A11y(...opts) } } -const failureHandler = (options = {}, reactEl, refs) => - function (type, props, failureMsg) { - const { - includeSrcNode = false - , 'throw': doThrow = false - , warningPrefix = '' - , filterFn = always - } = options - - // get the owning component - const owner = reactEl._owner - - // if a component instance, use the component's name, - // if a ReactElement instance, use the tag name + id (e.g. div#foo) - const tagName = owner && owner.getName() || type - - const failureInfo = { - tagName - , msg: warningPrefix.concat(failureMsg) - , refs - } - - const opts = { - includeSrcNode - } +a11y.restoreAll = function () { + if ( instance ) { + // restore handlers + instance.restoreAll() - // display the failure if it isn't filtered - if ( filterFn(failureInfo, props.id, failureInfo.msg) ) { - // how should we handle the message? - const done = doThrow ? throwError : showWarning - displayFailure(owner, failureInfo, opts, done) - } + // remove instance + instance = false } - -const reactA11y = function (React, options = {}) { - const { - includeSrcNode - , ReactDOM - } = options - - if (!React && !React.createElement) { - throw new Error('Missing parameter: React') - } - - if (includeSrcNode && !ReactDOM) { - throw new Error('I need ReactDOM option when includeSrcNode is true') - } - - // save our copy to react - _React = React - _ReactDOM = ReactDOM - _createElement = React.createElement - assertions.setReact(_React, _ReactDOM) - - // replace createElement with our overloaded version - _React.createElement = function (type, _props = {}, ...children) { - // fix for props = null - const props = _props || {} - - // create a refs object to hold the ref. - // this needs to be an object so that it can be passed - // by reference, and hold chaning state - const refs = {} - const ref = function (node) { - refs.node = node - } - const newProps = typeof type === 'string' ? { ...props, ref } : props - - const reactEl = _createElement(type, newProps, ...children) - const handler = failureHandler(options, reactEl, refs) - - // only test html elements - if (typeof type === 'string') { - const childrenForTest = children.length === 0 - ? props.children || [] - : children - - runTests(type, newProps, childrenForTest, options, handler) - } - - return reactEl - } -} - -reactA11y.restoreAll = function () { - _React.createElement = _createElement - after.restorePatchedMethods() } -export default reactA11y +export default a11y diff --git a/src/util/browser.js b/src/util/browser.js new file mode 100644 index 0000000..5dec7e1 --- /dev/null +++ b/src/util/browser.js @@ -0,0 +1 @@ +export default (typeof window !== 'undefined') From 43b52a84ea97b8c096b2b476c120bbb86a6d99d8 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 7 Apr 2016 22:59:16 +0200 Subject: [PATCH 036/447] lint it out --- src/defaults.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/defaults.js b/src/defaults.js index 9a9ac2c..ff29116 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -1,9 +1,8 @@ -import after from './after' - /** * Throws an error based on the warning * If the last argument is a DOM node, it * coerces it to a string before throwing. + * @returns {undefined} */ const throwError = function (...args) { const last = args[args.length - 1] @@ -19,6 +18,7 @@ const throwError = function (...args) { /** * Show a warning + * @returns {undefined} */ const showWarning = function (...args) { console.warn(...args) @@ -26,7 +26,8 @@ const showWarning = function (...args) { /** * Creates a reporter function based on deprecated options - * @arg opts - The options passed by the user + * @arg {object} opts - The options passed by the user + * @returns {function} The reporter */ const mkReporter = function (opts) { const { @@ -61,6 +62,7 @@ const mkReporter = function (opts) { * @arg {object} opts - the options object under scrutiny * @arg {string} name - the name of the deprecated option * @arg {string} msg - an optional reason for the deprecation + * @returns {undefined} */ const deprecate = function (opts, name, msg = '') { if ( name in opts ) { @@ -73,6 +75,7 @@ const deprecate = function (opts, name, msg = '') { * @arg {object} opts - the options object under scrutiny * @arg {string} name - the name of the mandatory option * @arg {string} msg - an optional reason + * @returns {undefined} */ const mandatory = function (opts, name, msg = '') { if ( !(name in opts) ) { @@ -84,11 +87,12 @@ const mandatory = function (opts, name, msg = '') { const always = () => true // deprecation message -const msg = 'Use the `reporter` option to change the way warnings are displayed." +const msg = 'Use the `reporter` option to change how warnings are displayed.' /** * Normalize and validate the options that the user passed in. * @arg {object} opts - The opts the user passed in + * @returns {object} the validated options */ export default function (opts = {}) { deprecate(opts, 'includeSrcNode', msg) @@ -97,7 +101,7 @@ export default function (opts = {}) { mandatory(opts, 'ReactDOM') const { - reporter = mkReporter(opts) // make a reporter based on (deprecated) options + reporter = mkReporter(opts) // make a reporter based on options , filterFn = always , plugins = [] , rules = {} From 9a291cde95bc206c608ffd61e16f7c7491b0dbde Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 7 Apr 2016 23:05:09 +0200 Subject: [PATCH 037/447] lint it out --- src/a11y.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/a11y.js b/src/a11y.js index 8c352b0..7936871 100644 --- a/src/a11y.js +++ b/src/a11y.js @@ -1,32 +1,31 @@ import after from './after' import defs from './defaults' import browser from './util/browser' +import test from './test' -class A11y { +export default class A11y { /** - * @arg React - The React instance you want to patch + * @arg {object} React - The React instance you want to patch * @arg {object} options - the options + * @returns {A11y} The react-a11y instance */ constructor (React, options = {}) { - this.options = defs(options) // extend default opts - this.React = React // save react for undoing patches - this.ReactDOM = opts.ReactDOM // save ReactDOM, we'll need it + this.options = defs(options) // extend default opts + this.React = React // save react for undoing patches + this.ReactDOM = this.options.ReactDOM if (!this.React && !this.React.createElement) { throw new Error('Missing parameter: React') } - if (!this.ReactDOM) { - throw new Error('Missing option: ReactDOM') - } - this.patchReact() } /** * Patch React, replacing its createElement by our implementation * so we can run the tests + * @returns {undefined} */ patchReact () { @@ -57,7 +56,7 @@ class A11y { // does not matter const newProps = typeof klass === 'string' ? { ...props, ref } : props - const reactEl = that._createElement(type, newProps, ...children) + const reactEl = that._createElement(klass, newProps, ...children) // only test html elements if (typeof klass === 'string') { @@ -67,7 +66,7 @@ class A11y { ? props.children || [] : children - runTests(klass, props, childrenForTest, that.options, handler) + test(klass, props, childrenForTest, that.options, handler) } return reactEl @@ -76,6 +75,7 @@ class A11y { /** * Restore React and all components as if we were never here + * @returns {undefined} */ restoreAll () { this.React.createElement = this._createElement @@ -84,8 +84,9 @@ class A11y { /** * Creates a failure handler based on the element that was created - * @arg reactEl - The react element this failure is for - * @arg refs - The object that holds the DOM node (passed by ref) + * @arg {object} reactEl - The react element this failure is for + * @arg {object} refs - The object that holds the DOM node (passed by ref) + * @returns {function} A handler that knows everything it needs to know */ failureHandler (reactEl, refs) { const { @@ -97,6 +98,7 @@ class A11y { * @arg {string} type - The HTML tagname of the element * @arg {object} props - The props that were passed to the element * @arg {string} msg - The warning message + * @returns {undefined} */ return function (type, props, msg) { @@ -122,7 +124,7 @@ class A11y { // if we need to include the rendered node, we need to wait until // the owner has rendered if ( owner && browser ) { - const instance = component._instance + const instance = owner._instance // Cannot log a node reference until the component is in the DOM, // so defer the call until componentDidMount or componentDidUpdate. after.render(instance, function () { From 1d72f13cd78a66a7120dfacd726dde3452ffc3ea Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 7 Apr 2016 23:05:31 +0200 Subject: [PATCH 038/447] add comments --- src/util/browser.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/browser.js b/src/util/browser.js index 5dec7e1..a830196 100644 --- a/src/util/browser.js +++ b/src/util/browser.js @@ -1 +1,3 @@ +// this is only true when in a browser-like environment +// (or someone seriously messed up) export default (typeof window !== 'undefined') From 5a49049488d4d1dddbcdb345804903cc2aa76db0 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 7 Apr 2016 23:05:52 +0200 Subject: [PATCH 039/447] add command for mocha testing (no-browser) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 02d3543..f916cca 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "bugs": "https://github.com/reactjs/react-a11y/issues", "scripts": { "test": "karma start --single-run", + "test-node": "mocha --compilers js:babel-register", "watch-tests": "npm test -- --watch", "lint": "eslint src", "build": "babel src --out-dir lib", From e73e23e1dbb29d425e41111e6f729c6ca8e212f8 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 7 Apr 2016 23:43:15 +0200 Subject: [PATCH 040/447] add new testing code, that uses rules --- src/assertions.js | 256 ------------------------------------ src/rules/img-uses-alt.js | 30 +++++ src/rules/index.js | 8 ++ src/rules/label-uses-for.js | 14 ++ src/test.js | 91 +++++++++++++ src/tests.js | 70 ---------- 6 files changed, 143 insertions(+), 326 deletions(-) delete mode 100644 src/assertions.js create mode 100644 src/rules/img-uses-alt.js create mode 100644 src/rules/index.js create mode 100644 src/rules/label-uses-for.js create mode 100644 src/test.js delete mode 100644 src/tests.js diff --git a/src/assertions.js b/src/assertions.js deleted file mode 100644 index a0d6d28..0000000 --- a/src/assertions.js +++ /dev/null @@ -1,256 +0,0 @@ -import after from './after' - -var React; -var ReactDOM; - -exports.setReact = function(_React, _ReactDOM) { - React = _React; - ReactDOM = _ReactDOM || React; -}; - -var INTERACTIVE = { - 'button': true, - 'input' (props) { - return props.type != 'hidden'; - }, - 'textarea': true, - 'select': true, - 'option': true, - 'a' (props) { - var hasHref = typeof props.href === 'string'; - var hasTabIndex = props.tabIndex != null; - return (hasHref || !hasHref && hasTabIndex); - } -}; - -const presentationRoles = ['presentation', 'none']; - -var isHiddenFromAT = (props) => { - return props['aria-hidden'] == 'true'; -}; - -var hasAlt = (props) => { - return typeof props.alt === 'string'; -}; - -var isInteractive = (tagName, props) => { - var tag = INTERACTIVE[tagName]; - return (typeof tag === 'function') ? tag(props) : tag; -}; - -var getComponents = (children) => { - var childComponents = []; - React.Children.forEach(children, function(child) { - if (child && typeof child.type === 'function') - childComponents.push(child); - }); - return childComponents; -}; - -var hasLabel = (node) => { - var text = node.tagName.toLowerCase() == 'img' ? node.alt : node.textContent; - var hasTextContent = text.trim().length > 0; - - var images = node.querySelectorAll('img[alt]'); - images = Array.prototype.slice.call(images); - - var hasAltText = (images.filter((image) => { - return image.alt.length > 0; - }).length) > 0; - - return hasTextContent || hasAltText; -}; - -var assertLabel = function(node, context, failureCB) { - if (context.passed) - return; - - context.passed = hasLabel(node); - - if (!context.passed && context.totalChildren == (++context.childrenTested)) - failureCB(); -}; - -var hasChildTextNode = (props, children, failureCB) => { - var hasText = false; - var childComponents = getComponents(children); - var nChildComponents = childComponents.length; - var hasChildComponents = nChildComponents > 0; - var nCurrentComponent = 0; - var context; - - if (hasChildComponents) - context = { totalChildren: childComponents.length, childrenTested: 0 }; - - React.Children.forEach(children, (child) => { - if (hasText) - return; - else if (child === null) - return; - else if (typeof child === 'undefined') - return; - else if (typeof child === 'string' || typeof child === 'number') - hasText = true; - else if (child.type === 'img' && child.props.alt) - hasText = true; - else if (child.props && child.props.children) - hasText = hasChildTextNode(child.props, child.props.children, failureCB); - else if (typeof child.type === 'function') { - // There can be false negatives if one of the children is a Component, - // as Components' children are inaccessible until it's is rendered. - // To account for this, check each Component's HTML after it's - // been mounted. - after(child.type.prototype, 'componentDidMount', function() { - assertLabel(ReactDOM.findDOMNode(this), context, failureCB); - }); - - // Return true because the label check is now going to be async - // (due to the componentDidMount listener) and we want to avoid - // pre-maturely calling the failure callback. - hasText = (nChildComponents == ++nCurrentComponent); - } - }); - return hasText; -}; - -exports.mobileExclusions = [ - 'NO_TABINDEX', - 'BUTTON_ROLE_SPACE', - 'BUTTON_ROLE_ENTER', - 'TABINDEX_REQUIRED_WHEN_ARIA_HIDDEN' -]; - -exports.props = { - onClick: { - NO_ROLE: { - msg: 'You have a click handler on a non-interactive element but no `role` DOM property. It will be unclear what this element is supposed to do to a screen-reader user. http://www.w3.org/TR/wai-aria/roles#role_definitions', - test (tagName, props, children) { - if (isHiddenFromAT(props)) - return true; - - return !(!isInteractive(tagName, props) && !props.role); - } - }, - - NO_TABINDEX: { - msg: 'You have a click handler on a non-interactive element but no `tabIndex` DOM property. The element will not be navigable or interactive by keyboard users. http://www.w3.org/TR/wai-aria-practices/#focus_tabindex', - test (tagName, props, children) { - if (isHiddenFromAT(props)) - return true; - - return !( - !isInteractive(tagName, props) && - props.tabIndex == null // tabIndex={0} is valid - ); - } - }, - - BUTTON_ROLE_SPACE: { - msg: 'You have `role="button"` but did not define an `onKeyDown` handler. Add it, and have the "Space" key do the same thing as an `onClick` handler.', - test (tagName, props, children) { - if (isHiddenFromAT(props)) - return true; - - return !(props.role === 'button' && !props.onKeyDown); - } - }, - - BUTTON_ROLE_ENTER: { - msg: 'You have `role="button"` but did not define an `onKeyDown` handler. Add it, and have the "Enter" key do the same thing as an `onClick` handler.', - test (tagName, props, children) { - if (isHiddenFromAT(props)) - return true; - - return !(props.role === 'button' && !props.onKeyDown); - } - } - }, - - 'aria-hidden': { - 'TABINDEX_REQUIRED_WHEN_ARIA_HIDDEN': { - msg: 'You have `aria-hidden="true"` applied to an interactive element but have not removed it from the tab flow. This could result in a hidden tab stop for users of screen readers.', - test (tagName, props, children) { - return !( - (isInteractive(tagName, props) || (tagName == 'a' && !props.href)) && - props['aria-hidden'] == 'true' && - props.tabIndex != '-1' - ); - } - } - } -}; - -exports.tags = { - a: { - HASH_HREF_NEEDS_BUTTON: { - msg: 'You have an anchor with `href="#"` and no `role` DOM property. Add `role="button"` or better yet, use a `; - }); - }); - - it('does not warn if there are deeply nested text node children', () => { - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('does not error if there are undefined children', () => { - var undefChild; - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('does not error if there are null children', () => { - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('does not warn if there is an image with an alt attribute', () => { - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('warns if an image without alt is the only content', () => { - expectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('does not warn if an image without alt is accompanied by text', () => { - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('does not warn if a hidden input', () => { - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('warns if a visible input', () => { - expectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('warns if an anchor has a tabIndex but no href', () => { - expectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('warns if an anchor has an href', () => { - expectWarning(assertions.render.NO_LABEL.msg, () => { - ; - }); - }); - - it('does not warn when the label text is inside a child component', () => { - var Foo = React.createClass({ - render: function() { - return ( -
- foo -
- ); - } - }); - - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('does not warn when the label is an image with alt text', () => { - var Foo = React.createClass({ - render: function() { - return ( - foo - ); - } - }); - - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('warns when the label is an image without alt text', () => { - var Foo = React.createClass({ - render: function() { - return ( - - ); - } - }); - - expectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('does not warn when the label is an image with alt text nested inside a child component', () => { - var Foo = React.createClass({ - render: function() { - return ( -
- foo -
- ); - } - }); - - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('warns when an image without alt text is nested inside a child component', () => { - var Foo = React.createClass({ - render: function() { - return ( -
- -
- ); - } - }); - - expectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('does not warn when there is an image without alt text with a sibling text node', () => { - var Foo = React.createClass({ - render: function() { - return ( -
- Foo -
- ); - } - }); - - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('warns when a child is a component without text content', () => { - var Bar = React.createClass({ - render: () => { - return ( -
- ); - } - }); - - expectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('does not warn as long as one child component has label text', () => { - var Bar = React.createClass({ - render: () => { - return ( -
- ); - } - }); - - var Foo = React.createClass({ - render: function() { - return ( -
- foo -
- ); - } - }); - - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('warns if no child components have label text', () => { - var Bar = React.createClass({ - render: () => { - return ( -
- ); - } - }); - - var Foo = React.createClass({ - render: function() { - return ( -
- ); - } - }); - - expectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('does not error when the component has a componentDidMount callback', () => { - var Bar = React.createClass({ - _privateProp: 'bar', - - componentDidMount: function() { - return this._privateProp; - }, - render: () => { - return ( -
- ); - } - }); - - expectWarning(assertions.render.NO_LABEL.msg, () => { - ReactDOM.render(
, fixture); - }); - }); - - it('does not warn when the label is a number', () => { - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { -
{1111}; - }); - }); -}); - -describe('includeSrcNode is "asString"', () => { - var fixture; - - before(() => { - a11y(React, { includeSrcNode: "asString", ReactDOM: ReactDOM }); - fixture = document.createElement('div'); - fixture.id = 'fixture-1'; - document.body.appendChild(fixture); - }); - - after(() => { - a11y.restoreAll(); - fixture = document.getElementById('fixture-1'); - if (fixture) - document.body.removeChild(fixture); - }); - - it('returns the outerHTML as a string in the error message', () => { - var Bar = React.createClass({ - _privateProp: 'bar', - - componentDidMount: function() { - return this._privateProp; - }, - render: () => { - return ( -
- ); - } - }); - - var msgs = captureWarnings(() => {ReactDOM.render(, fixture);}); - var regex = /^Source Node: <(\w+) .+>.*<\/\1>/; - var matches = msgs[assertions.render.NO_LABEL.msg].match(regex); - assert.equal(matches[1], "div"); - }); -}); - -describe('filterFn', () => { - before(() => { - var barOnly = (name, id, msg) => { - return id === "bar"; - }; - - a11y(React, { filterFn: barOnly }); - }); - - after(() => { - a11y.restoreAll(); - }); - - describe('when the source element has been filtered out', () => { - it('does not warn', () => { - doNotExpectWarning(assertions.tags.img.MISSING_ALT.msg, () => { - ; - }); - }); - }); - - describe('when there are filtered results', () => { - it('warns', () => { - expectWarning(assertions.tags.img.MISSING_ALT.msg, () => { -
- - -
; - }); - }); - }); -}); - -describe('device is set to mobile', () => { - before(() => { - a11y(React, { device: ['mobile'] }); - }); - - after(() => { - a11y.restoreAll(); - }); - - describe('when role="button"', () => { - it('does not require onKeyDown', () => { - doNotExpectWarning(assertions.props.onClick.BUTTON_ROLE_SPACE.msg, () => { - ; - }); - }); - - it('does not require onKeyDown', () => { - doNotExpectWarning(assertions.props.onClick.BUTTON_ROLE_ENTER.msg, () => { - ; - }); - }); - }); -}); - -describe('exclusions', () => { - before(() => { - a11y(React, { exclude: ['REDUNDANT_ALT'] }); - }); - - after(() => { - a11y.restoreAll(); - }); - - describe('when REDUNDANT_ALT is excluded', () => { - it('does not warn when the word "image" in the alt attribute', () => { - doNotExpectWarning(assertions.tags.img.REDUNDANT_ALT.msg, () => { - image of a cat; - }); - }); - }); -}); - -describe('warningPrefix', () => { - let warningPrefix = 'react-a11y ERROR:'; - before(() => { - a11y(React, { warningPrefix }); - }); - - after(() => { - a11y.restoreAll(); - }); - - it('adds the prefix to each warning message', () => { - expectWarning(warningPrefix + assertions.tags.img.MISSING_ALT.msg, () => { -
- - -
; - }); - }); -}); - -describe('testing children', () => { - before(() => { - a11y(React, { exclude: ['REDUNDANT_ALT'] }); - }); - - after(() => { - a11y.restoreAll(); - }); - - describe('when children is passed down in props', () => { - it('calls each test with the children', () => { - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - React.createElement('a', {href: 'google.com', children: 'Google'}); - }); - }); - }); - - describe('when children is passed down separately from props', () => { - it('calls each test with the children', () => { - doNotExpectWarning(assertions.render.NO_LABEL.msg, () => { - React.createElement('a', {href: 'google.com'}, 'Google'); - }); - }); - }); -}); From 7f72837b75fb29e8d23aa8d5e11bf8261ade5d6d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 8 Apr 2016 12:08:54 +0200 Subject: [PATCH 052/447] make tests more async --- test/rules/img-uses-alt.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/rules/img-uses-alt.js b/test/rules/img-uses-alt.js index d8cc1b0..8cb7615 100644 --- a/test/rules/img-uses-alt.js +++ b/test/rules/img-uses-alt.js @@ -4,14 +4,13 @@ import { expect } from 'chai' import A11y from '../../src/a11y' const warns = function (rule, title, re, el) { - it(title, () => { - let ok = false - + it(title, done => { const a11y = new A11y(React, { ReactDOM , reporter ({ props, msg }) { - console.log(props, msg) - ok = msg && re.test(msg) + const ok = msg && re.test(msg) + expect(ok).to.be.true + done() } , rules: { 'img-uses-alt': 1 @@ -19,7 +18,6 @@ const warns = function (rule, title, re, el) { }) el() - expect(ok).to.be.true a11y.restoreAll() }) From 41875662cfcc206d2cf7b26ca69d04920a20b24d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 8 Apr 2016 12:10:22 +0200 Subject: [PATCH 053/447] add rule tests for karma --- karma.conf.js | 1 + 1 file changed, 1 insertion(+) diff --git a/karma.conf.js b/karma.conf.js index fbf040f..fcc6867 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -15,6 +15,7 @@ module.exports = function (config) { ] , files: [ 'test/browser/**/*.js' + , 'test/rules/**/*.js' , 'test/*.js' ] , preprocessors: { From efd44740aca95093aff83cdbd31b4d6233987859 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 8 Apr 2016 14:44:10 +0200 Subject: [PATCH 054/447] rename defaults to options as it messes with some editors --- src/a11y.js | 10 +++++----- src/{defaults.js => options.js} | 0 test/{defaults.js => options.js} | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/{defaults.js => options.js} (100%) rename test/{defaults.js => options.js} (97%) diff --git a/src/a11y.js b/src/a11y.js index a94677a..2664e44 100644 --- a/src/a11y.js +++ b/src/a11y.js @@ -1,7 +1,7 @@ -import after from './after' -import defs from './defaults' -import browser from './util/browser' -import Suite from './test' +import after from './after' +import validate from './options' +import browser from './util/browser' +import Suite from './test' export default class A11y { @@ -11,7 +11,7 @@ export default class A11y { * @returns {A11y} The react-a11y instance */ constructor (React, options = {}) { - this.options = defs(options) // extend default opts + this.options = validate(options) // extend default opts this.React = React // save react for undoing patches this.ReactDOM = this.options.ReactDOM diff --git a/src/defaults.js b/src/options.js similarity index 100% rename from src/defaults.js rename to src/options.js diff --git a/test/defaults.js b/test/options.js similarity index 97% rename from test/defaults.js rename to test/options.js index e28a6a3..e0e8a59 100644 --- a/test/defaults.js +++ b/test/options.js @@ -1,6 +1,6 @@ import { expect } from 'chai' import ReactDOM from 'react-dom' -import defs from '../src/defaults' +import defs from '../src/options' // minimal valid options const valid = { From 3bb49102312c9a7dace94e1935ad71f6ab31a773 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 8 Apr 2016 15:22:21 +0200 Subject: [PATCH 055/447] add helper method for sync testing, and write test helpers --- src/a11y.js | 13 +++++++++-- test/rules/helpers.js | 53 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 test/rules/helpers.js diff --git a/src/a11y.js b/src/a11y.js index 2664e44..df44e12 100644 --- a/src/a11y.js +++ b/src/a11y.js @@ -19,7 +19,8 @@ export default class A11y { throw new Error('Missing parameter: React') } - this.suite = new Suite(React, this.options) + this.__sync = false + this.suite = new Suite(React, this.options) this.patchReact() } @@ -123,7 +124,7 @@ export default class A11y { // if we need to include the rendered node, we need to wait until // the owner has rendered - if ( owner && browser ) { + if ( owner && browser && !this.__sync ) { const instance = owner._instance // Cannot log a node reference until the component is in the DOM, // so defer the call until componentDidMount or componentDidUpdate. @@ -145,4 +146,12 @@ export default class A11y { } } } + + /** + * Force A11y in sync mode, DOMNodes might be omitted + * @arg {boolean} sync - wether or not to force sync mode + */ + __forceSync (sync = true) { + this.__sync = !!sync + } } diff --git a/test/rules/helpers.js b/test/rules/helpers.js new file mode 100644 index 0000000..7e09b28 --- /dev/null +++ b/test/rules/helpers.js @@ -0,0 +1,53 @@ +import React from 'react' +import ReactDOM from 'react' +import A11y from '../../src/a11y' +import { expect } from 'chai' + +const onWarn = function (rule, el, cb) { + return function (done) { + let called = false + const a11y = new A11y(React, { + ReactDOM + , reporter (info) { + called = true + a11y.restoreAll() + cb(info) + } + , rules: { + [rule]: 1 + } + }) + + + // force a11y into sync mode dfor test + a11y.__forceSync(true) + + // create the el + el() + + // because of sync mode, we can callback here + done() + } +} + +export const warns = function (rule, title, re, el) { + it(title, onWarn(rule, el, function (info) { + const { + msg + } = info + + expect(msg).to.match(re) + })) +} + +const doesntWarn = function (rule, title, re, el) { + it(title, onWarn(rule, el, function (info) { + const { + msg + } = info + + expect(msg).to.not.match(re) + }, true)) +} + +export const doesnt = { warn: doesntWarn } From 96b6855521798939f7a82773878b873edcc4dc01 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 8 Apr 2016 15:22:48 +0200 Subject: [PATCH 056/447] add tests for NOT warnign and for label-uses-for --- test/rules/img-uses-alt.js | 41 +++++++++++++++--------------------- test/rules/label-uses-for.js | 22 +++++++++++++++++++ 2 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 test/rules/label-uses-for.js diff --git a/test/rules/img-uses-alt.js b/test/rules/img-uses-alt.js index 8cb7615..bab9a4b 100644 --- a/test/rules/img-uses-alt.js +++ b/test/rules/img-uses-alt.js @@ -1,27 +1,8 @@ -import React from 'react' -import ReactDOM from 'react' -import { expect } from 'chai' -import A11y from '../../src/a11y' - -const warns = function (rule, title, re, el) { - it(title, done => { - const a11y = new A11y(React, { - ReactDOM - , reporter ({ props, msg }) { - const ok = msg && re.test(msg) - expect(ok).to.be.true - done() - } - , rules: { - 'img-uses-alt': 1 - } - }) - - el() - - a11y.restoreAll() - }) -} +import React from 'react' +import { + warns +, doesnt +} from './helpers' describe('img-uses-alt', () => { @@ -31,11 +12,23 @@ describe('img-uses-alt', () => { , () => ) + doesnt.warn('img-uses-alt' + , 'doesn\'t warn when there is an `alt` prop' + , /img does not have an `alt` prop/ + , () => nice + ) + warns('img-uses-alt' , 'warns when the `alt` prop is empty' , /empty string/ , () => ) + doesnt.warn('img-uses-alt' + , 'doesn\'t warn when the `alt` prop is empty' + , /empty string/ + , () => + ) + }) diff --git a/test/rules/label-uses-for.js b/test/rules/label-uses-for.js new file mode 100644 index 0000000..20c3ef0 --- /dev/null +++ b/test/rules/label-uses-for.js @@ -0,0 +1,22 @@ +import React from 'react' +import { + warns +, doesnt +} from './helpers' + +describe('label-uses-for', () => { + + warns('label-uses-for' + , 'warns when there is no `htmlFor` prop' + , /using htmlFor/ + , () =>