diff --git a/karma.conf.js b/karma.conf.js index bb1f22e..f3d7f60 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -25,7 +25,7 @@ module.exports = function (config) { devtool: 'inline-source-map', module: { loaders: [ - { test: /\.js$/, loader: 'babel-loader' } + { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime' } ] }, plugins: [ diff --git a/lib/__tests__/index-test.js b/lib/__tests__/index-test.js index 000f44e..ad760a6 100644 --- a/lib/__tests__/index-test.js +++ b/lib/__tests__/index-test.js @@ -125,7 +125,7 @@ describe('tags', () => { }); it('dissallows the word "image" in the alt attribute', () => { - expectWarning(assertions.tags.img.REDUDANT_ALT.msg, () => { + expectWarning(assertions.tags.img.REDUNDANT_ALT.msg, () => { image of a cat; }); }); @@ -524,4 +524,24 @@ describe('device is set to mobile', () => { }); }); }); -}); \ No newline at end of file +}); + +describe('exclusions', () => { + var createElement = React.createElement; + + before(() => { + a11y(React, { exclude: ['REDUNDANT_ALT'] }); + }); + + after(() => { + React.createElement = createElement; + }); + + 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; + }); + }); + }); +}); diff --git a/lib/assertions.js b/lib/assertions.js index 764c45d..a6534a5 100644 --- a/lib/assertions.js +++ b/lib/assertions.js @@ -21,8 +21,6 @@ var INTERACTIVE = { } }; -const DEVICE = { 'DESKTOP': 'desktop', 'MOBILE': 'mobile' }; - var hasAlt = (props) => { return typeof props.alt === 'string'; }; @@ -117,7 +115,6 @@ exports.props = { }, NO_TABINDEX: { - device: DEVICE.DESKTOP, 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) { return !( @@ -128,7 +125,6 @@ exports.props = { }, BUTTON_ROLE_SPACE: { - device: DEVICE.DESKTOP, 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) { return !(props.role === 'button' && !props.onKeyDown); @@ -136,7 +132,6 @@ exports.props = { }, BUTTON_ROLE_ENTER: { - device: DEVICE.DESKTOP, 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) { return !(props.role === 'button' && !props.onKeyDown); @@ -170,7 +165,7 @@ exports.tags = { } }, - REDUDANT_ALT: { + REDUNDANT_ALT: { // TODO: have some way to set localization strings to match against msg: 'Screen-readers already announce `img` tags as an image, you don\'t need to use the word "image" in the description', test (tagName, props, children) { diff --git a/lib/index.js b/lib/index.js index de5df9f..95539d7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,20 +1,29 @@ var assertions = require('./assertions'); var after = require('./after'); -var deviceMatches = (test, deviceFilter) => { - if (!test.device) - return true; +const mobileExclusions = [ + 'NO_TABINDEX', + 'BUTTON_ROLE_SPACE', + 'BUTTON_ROLE_ENTER' +]; + +var shouldRunTest = (testName, options) => { + var exclude = options.exclude || []; + + if (options.device == 'mobile') { + exclude = new Set(exclude.concat(mobileExclusions)); + exclude = [...exclude]; + } - return (deviceFilter.indexOf(test.device) != -1); + return (exclude.indexOf(testName) == -1); }; -var runTagTests = (tagName, props, children, deviceFilter, onFailure) => { +var runTagTests = (tagName, props, children, options, onFailure) => { var key; var tagTests = assertions.tags[tagName] || []; for (key in tagTests) { - let shouldRunTest = deviceMatches(tagTests[key], deviceFilter); - let testFailed = shouldRunTest && + let testFailed = shouldRunTest(key, options) && !tagTests[key].test(tagName, props, children); if (tagTests[key] && testFailed) @@ -22,7 +31,7 @@ var runTagTests = (tagName, props, children, deviceFilter, onFailure) => { } }; -var runPropTests = (tagName, props, children, deviceFilter, onFailure) => { +var runPropTests = (tagName, props, children, options, onFailure) => { var key; var propTests; @@ -32,8 +41,7 @@ var runPropTests = (tagName, props, children, deviceFilter, onFailure) => { propTests = assertions.props[propName] || []; for (key in propTests) { - let shouldRunTest = deviceMatches(propTests[key], deviceFilter); - let testTailed = shouldRunTest && + let testTailed = shouldRunTest(key, options) && !propTests[key].test(tagName, props, children); if (propTests[key] && testTailed) @@ -42,12 +50,12 @@ var runPropTests = (tagName, props, children, deviceFilter, onFailure) => { } }; -var runLabelTests = (tagName, props, children, deviceFilter, onFailure) => { +var runLabelTests = (tagName, props, children, options, onFailure) => { var key; var renderTests = assertions.render; for (key in renderTests) { - if (renderTests[key]) { + if (shouldRunTest(key, options) && renderTests[key]) { let failureCB = onFailure.bind( undefined, tagName, props, renderTests[key].msg); @@ -56,10 +64,10 @@ var runLabelTests = (tagName, props, children, deviceFilter, onFailure) => { } }; -var runTests = (tagName, props, children, deviceFilter, onFailure) => { +var runTests = (tagName, props, children, options, onFailure) => { var tests = [runTagTests, runPropTests, runLabelTests]; tests.map((test) => { - test(tagName, props, children, deviceFilter, onFailure); + test(tagName, props, children, options, onFailure); }); }; @@ -168,17 +176,17 @@ var reactA11y = (React, options) => { assertions.setReact(React); _createElement = React.createElement; - var deviceFilter = options && options.device || ['desktop']; React.createElement = (type, _props, ...children) => { var props = _props || {}; + options = options || {}; props.id = createId(props); var reactEl = _createElement.apply(this, [type, props].concat(children)); var failureCB = handleFailure.bind(undefined, options, reactEl); if (typeof type === 'string') - runTests(type, props, children, deviceFilter, failureCB); + runTests(type, props, children, options, failureCB); return reactEl; }; diff --git a/package.json b/package.json index 33b4074..e2d5b70 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "test": "jsxhint . && karma start --single-run", "watch-tests": "npm test -- --watch", - "prepublish": "babel -d dist lib", + "prepublish": "babel --optional runtime lib --out-dir dist", "release": "release" }, "authors": [ @@ -25,6 +25,7 @@ "babel": "^5.2.17", "babel-core": "^5.2.17", "babel-loader": "^5.0.0", + "babel-runtime": "^5.1.11", "jsx-loader": "^0.12.2", "jsxhint": "^0.8.1", "karma": "^0.12.28", diff --git a/webpack.config.js b/webpack.config.js index 74a5e0a..c7934d8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -35,7 +35,7 @@ module.exports = { module: { loaders: [ - { test: /\.js$/, loader: 'jsx-loader?harmony' } + { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime' } ] }