Skip to content
Permalink
9bfb9ba527
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
499 lines (487 sloc) 22.2 KB
/**
* @fileoverview Disallow inherently non-interactive elements to be assigned
* interactive roles.
* @author Jesse Beach
* @author $AUTHOR
*/
// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------
import { RuleTester } from 'eslint';
import { configs } from '../../../src/index';
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
import rule from '../../../src/rules/no-noninteractive-element-to-interactive-role';
import ruleOptionsMapperFactory from '../../__util__/ruleOptionsMapperFactory';
// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
const ruleTester = new RuleTester();
const errorMessage = 'Non-interactive elements should not be assigned interactive roles.';
const expectedError = {
message: errorMessage,
type: 'JSXAttribute',
};
const ruleName = 'jsx-a11y/no-noninteractive-element-to-interactive-role';
const componentsSettings = {
'jsx-a11y': {
components: {
Article: 'article',
Input: 'input',
},
},
};
const alwaysValid = [
{ code: '<TestComponent onClick={doFoo} />' },
{ code: '<Button onClick={doFoo} />' },
/* Interactive elements */
{ code: '<a tabIndex="0" role="button" />' },
{ code: '<a href="http://x.y.z" role="button" />' },
{ code: '<a href="http://x.y.z" tabIndex="0" role="button" />' },
{ code: '<area role="button" />;' },
{ code: '<area role="menuitem" />;' },
{ code: '<button className="foo" role="button" />' },
/* All flavors of input */
{ code: '<input role="button" />' },
{ code: '<input type="button" role="button" />' },
{ code: '<input type="checkbox" role="button" />' },
{ code: '<input type="color" role="button" />' },
{ code: '<input type="date" role="button" />' },
{ code: '<input type="datetime" role="button" />' },
{ code: '<input type="datetime-local" role="button" />' },
{ code: '<input type="email" role="button" />' },
{ code: '<input type="file" role="button" />' },
{ code: '<input type="hidden" role="button" />' },
{ code: '<input type="image" role="button" />' },
{ code: '<input type="month" role="button" />' },
{ code: '<input type="number" role="button" />' },
{ code: '<input type="password" role="button" />' },
{ code: '<input type="radio" role="button" />' },
{ code: '<input type="range" role="button" />' },
{ code: '<input type="reset" role="button" />' },
{ code: '<input type="search" role="button" />' },
{ code: '<input type="submit" role="button" />' },
{ code: '<input type="tel" role="button" />' },
{ code: '<input type="text" role="button" />' },
{ code: '<input type="time" role="button" />' },
{ code: '<input type="url" role="button" />' },
{ code: '<input type="week" role="button" />' },
{ code: '<input type="hidden" role="img" />' },
/* End all flavors of input */
{ code: '<menuitem role="button" />;' },
{ code: '<option className="foo" role="button" />' },
{ code: '<select className="foo" role="button" />' },
{ code: '<textarea className="foo" role="button" />' },
{ code: '<tr role="button" />;' },
{ code: '<tr role="presentation" />;' },
/* Interactive elements */
{ code: '<a tabIndex="0" role="img" />' },
{ code: '<a href="http://x.y.z" role="img" />' },
{ code: '<a href="http://x.y.z" tabIndex="0" role="img" />' },
/* All flavors of input */
{ code: '<input role="img" />' },
{ code: '<input type="img" role="img" />' },
{ code: '<input type="checkbox" role="img" />' },
{ code: '<input type="color" role="img" />' },
{ code: '<input type="date" role="img" />' },
{ code: '<input type="datetime" role="img" />' },
{ code: '<input type="datetime-local" role="img" />' },
{ code: '<input type="email" role="img" />' },
{ code: '<input type="file" role="img" />' },
{ code: '<input type="hidden" role="button" />' },
{ code: '<input type="image" role="img" />' },
{ code: '<input type="month" role="img" />' },
{ code: '<input type="number" role="img" />' },
{ code: '<input type="password" role="img" />' },
{ code: '<input type="radio" role="img" />' },
{ code: '<input type="range" role="img" />' },
{ code: '<input type="reset" role="img" />' },
{ code: '<input type="search" role="img" />' },
{ code: '<input type="submit" role="img" />' },
{ code: '<input type="tel" role="img" />' },
{ code: '<input type="text" role="img" />' },
{ code: '<input type="time" role="img" />' },
{ code: '<input type="url" role="img" />' },
{ code: '<input type="week" role="img" />' },
/* End all flavors of input */
{ code: '<menuitem role="img" />;' },
{ code: '<option className="foo" role="img" />' },
{ code: '<select className="foo" role="img" />' },
{ code: '<textarea className="foo" role="img" />' },
{ code: '<tr role="img" />;' },
/* Interactive elements */
{ code: '<a tabIndex="0" role="listitem" />' },
{ code: '<a href="http://x.y.z" role="listitem" />' },
{ code: '<a href="http://x.y.z" tabIndex="0" role="listitem" />' },
/* All flavors of input */
{ code: '<input role="listitem" />' },
{ code: '<input type="listitem" role="listitem" />' },
{ code: '<input type="checkbox" role="listitem" />' },
{ code: '<input type="color" role="listitem" />' },
{ code: '<input type="date" role="listitem" />' },
{ code: '<input type="datetime" role="listitem" />' },
{ code: '<input type="datetime-local" role="listitem" />' },
{ code: '<input type="email" role="listitem" />' },
{ code: '<input type="file" role="listitem" />' },
{ code: '<input type="image" role="listitem" />' },
{ code: '<input type="month" role="listitem" />' },
{ code: '<input type="number" role="listitem" />' },
{ code: '<input type="password" role="listitem" />' },
{ code: '<input type="radio" role="listitem" />' },
{ code: '<input type="range" role="listitem" />' },
{ code: '<input type="reset" role="listitem" />' },
{ code: '<input type="search" role="listitem" />' },
{ code: '<input type="submit" role="listitem" />' },
{ code: '<input type="tel" role="listitem" />' },
{ code: '<input type="text" role="listitem" />' },
{ code: '<input type="time" role="listitem" />' },
{ code: '<input type="url" role="listitem" />' },
{ code: '<input type="week" role="listitem" />' },
/* End all flavors of input */
{ code: '<menuitem role="listitem" />;' },
{ code: '<option className="foo" role="listitem" />' },
{ code: '<select className="foo" role="listitem" />' },
{ code: '<textarea className="foo" role="listitem" />' },
{ code: '<tr role="listitem" />;' },
/* HTML elements with neither an interactive or non-interactive valence (static) */
{ code: '<acronym role="button" />;' },
{ code: '<address role="button" />;' },
{ code: '<applet role="button" />;' },
{ code: '<audio role="button" />;' },
{ code: '<b role="button" />;' },
{ code: '<base role="button" />;' },
{ code: '<bdi role="button" />;' },
{ code: '<bdo role="button" />;' },
{ code: '<big role="button" />;' },
{ code: '<blink role="button" />;' },
{ code: '<canvas role="button" />;' },
{ code: '<center role="button" />;' },
{ code: '<cite role="button" />;' },
{ code: '<code role="button" />;' },
{ code: '<col role="button" />;' },
{ code: '<colgroup role="button" />;' },
{ code: '<content role="button" />;' },
{ code: '<data role="button" />;' },
{ code: '<datalist role="button" />;' },
{ code: '<del role="button" />;' },
{ code: '<div role="button" />;' },
{ code: '<div className="foo" role="button" />;' },
{ code: '<div className="foo" {...props} role="button" />;' },
{ code: '<div aria-hidden role="button" />;' },
{ code: '<div aria-hidden={true} role="button" />;' },
{ code: '<div role="button" />;' },
{ code: '<div role={undefined} role="button" />;' },
{ code: '<div {...props} role="button" />;' },
{ code: '<div onKeyUp={() => void 0} aria-hidden={false} role="button" />;' },
{ code: '<em role="button" />;' },
{ code: '<embed role="button" />;' },
{ code: '<font role="button" />;' },
{ code: '<frameset role="button" />;' },
{ code: '<head role="button" />;' },
{ code: '<header role="button" />;' },
{ code: '<hgroup role="button" />;' },
{ code: '<html role="button" />;' },
{ code: '<i role="button" />;' },
{ code: '<ins role="button" />;' },
{ code: '<kbd role="button" />;' },
{ code: '<keygen role="button" />;' },
{ code: '<link role="button" />;' },
{ code: '<map role="button" />;' },
{ code: '<meta role="button" />;' },
{ code: '<noembed role="button" />;' },
{ code: '<noscript role="button" />;' },
{ code: '<object role="button" />;' },
{ code: '<param role="button" />;' },
{ code: '<picture role="button" />;' },
{ code: '<q role="button" />;' },
{ code: '<rp role="button" />;' },
{ code: '<rt role="button" />;' },
{ code: '<rtc role="button" />;' },
{ code: '<s role="button" />;' },
{ code: '<samp role="button" />;' },
{ code: '<script role="button" />;' },
{ code: '<small role="button" />;' },
{ code: '<source role="button" />;' },
{ code: '<spacer role="button" />;' },
{ code: '<span role="button" />;' },
{ code: '<strike role="button" />;' },
{ code: '<strong role="button" />;' },
{ code: '<style role="button" />;' },
{ code: '<sub role="button" />;' },
{ code: '<summary role="button" />;' },
{ code: '<sup role="button" />;' },
{ code: '<th role="button" />;' },
{ code: '<title role="button" />;' },
{ code: '<track role="button" />;' },
{ code: '<tt role="button" />;' },
{ code: '<u role="button" />;' },
{ code: '<var role="button" />;' },
{ code: '<video role="button" />;' },
{ code: '<wbr role="button" />;' },
{ code: '<xmp role="button" />;' },
/* HTML elements attributed with an interactive role */
{ code: '<div role="button" />;' },
{ code: '<div role="checkbox" />;' },
{ code: '<div role="columnheader" />;' },
{ code: '<div role="combobox" />;' },
{ code: '<div role="grid" />;' },
{ code: '<div role="gridcell" />;' },
{ code: '<div role="link" />;' },
{ code: '<div role="listbox" />;' },
{ code: '<div role="menu" />;' },
{ code: '<div role="menubar" />;' },
{ code: '<div role="menuitem" />;' },
{ code: '<div role="menuitemcheckbox" />;' },
{ code: '<div role="menuitemradio" />;' },
{ code: '<div role="option" />;' },
{ code: '<div role="progressbar" />;' },
{ code: '<div role="radio" />;' },
{ code: '<div role="radiogroup" />;' },
{ code: '<div role="row" />;' },
{ code: '<div role="rowheader" />;' },
{ code: '<div role="searchbox" />;' },
{ code: '<div role="slider" />;' },
{ code: '<div role="spinbutton" />;' },
{ code: '<div role="switch" />;' },
{ code: '<div role="tab" />;' },
{ code: '<div role="textbox" />;' },
{ code: '<div role="treeitem" />;' },
/* Presentation is a special case role that indicates intentional static semantics */
{ code: '<div role="presentation" />;' },
/* HTML elements attributed with an abstract role */
{ code: '<div role="command" />;' },
{ code: '<div role="composite" />;' },
{ code: '<div role="input" />;' },
{ code: '<div role="landmark" />;' },
{ code: '<div role="range" />;' },
{ code: '<div role="roletype" />;' },
{ code: '<div role="section" />;' },
{ code: '<div role="sectionhead" />;' },
{ code: '<div role="select" />;' },
{ code: '<div role="structure" />;' },
{ code: '<div role="tablist" />;' },
{ code: '<div role="toolbar" />;' },
{ code: '<div role="tree" />;' },
{ code: '<div role="treegrid" />;' },
{ code: '<div role="widget" />;' },
{ code: '<div role="window" />;' },
/* HTML elements with an inherent non-interactive role, assigned an
* interactive role. */
{ code: '<main role="listitem" />;' },
{ code: '<a role="listitem" />' },
{ code: '<a role="listitem" />;' },
{ code: '<a role="button" />' },
{ code: '<a role="button" />;' },
{ code: '<a role="menuitem" />' },
{ code: '<a role="menuitem" />;' },
{ code: '<area role="listitem" />;' },
{ code: '<article role="listitem" />;' },
{ code: '<article role="listitem" />;' },
{ code: '<dd role="listitem" />;' },
{ code: '<dfn role="listitem" />;' },
{ code: '<dt role="listitem" />;' },
{ code: '<fieldset role="listitem" />;' },
{ code: '<figure role="listitem" />;' },
{ code: '<form role="listitem" />;' },
{ code: '<frame role="listitem" />;' },
{ code: '<h1 role="listitem" />;' },
{ code: '<h2 role="listitem" />;' },
{ code: '<h3 role="listitem" />;' },
{ code: '<h4 role="listitem" />;' },
{ code: '<h5 role="listitem" />;' },
{ code: '<h6 role="listitem" />;' },
{ code: '<hr role="listitem" />;' },
{ code: '<img role="listitem" />;' },
{ code: '<li role="listitem" />;' },
{ code: '<li role="presentation" />;' },
{ code: '<nav role="listitem" />;' },
{ code: '<ol role="listitem" />;' },
{ code: '<table role="listitem" />;' },
{ code: '<tbody role="listitem" />;' },
{ code: '<td role="listitem" />;' },
{ code: '<tfoot role="listitem" />;' },
{ code: '<thead role="listitem" />;' },
{ code: '<ul role="listitem" />;' },
/* HTML elements attributed with a non-interactive role */
{ code: '<div role="alert" />;' },
{ code: '<div role="alertdialog" />;' },
{ code: '<div role="application" />;' },
{ code: '<div role="article" />;' },
{ code: '<div role="banner" />;' },
{ code: '<div role="cell" />;' },
{ code: '<div role="complementary" />;' },
{ code: '<div role="contentinfo" />;' },
{ code: '<div role="definition" />;' },
{ code: '<div role="dialog" />;' },
{ code: '<div role="directory" />;' },
{ code: '<div role="document" />;' },
{ code: '<div role="feed" />;' },
{ code: '<div role="figure" />;' },
{ code: '<div role="form" />;' },
{ code: '<div role="group" />;' },
{ code: '<div role="heading" />;' },
{ code: '<div role="img" />;' },
{ code: '<div role="list" />;' },
{ code: '<div role="listitem" />;' },
{ code: '<div role="log" />;' },
{ code: '<div role="main" />;' },
{ code: '<div role="marquee" />;' },
{ code: '<div role="math" />;' },
{ code: '<div role="navigation" />;' },
{ code: '<div role="note" />;' },
{ code: '<div role="region" />;' },
{ code: '<div role="rowgroup" />;' },
{ code: '<div role="search" />;' },
{ code: '<div role="separator" />;' },
{ code: '<div role="scrollbar" />;' },
{ code: '<div role="status" />;' },
{ code: '<div role="table" />;' },
{ code: '<div role="tabpanel" />;' },
{ code: '<div role="term" />;' },
{ code: '<div role="timer" />;' },
{ code: '<div role="tooltip" />;' },
{ code: '<ul role="list" />;' },
/* Custom components */
{ code: '<Article role="button" />' },
{ code: '<Input role="button" />', settings: componentsSettings },
];
const neverValid = [
/* HTML elements with an inherent non-interactive role, assigned an
* interactive role. */
{ code: '<main role="button" />;', errors: [expectedError] },
{ code: '<article role="button" />;', errors: [expectedError] },
{ code: '<article role="button" />;', errors: [expectedError] },
{ code: '<aside role="button" />;', errors: [expectedError] },
{ code: '<blockquote role="button" />;', errors: [expectedError] },
{ code: '<body role="button" />;', errors: [expectedError] },
{ code: '<br role="button" />;', errors: [expectedError] },
{ code: '<caption role="button" />;', errors: [expectedError] },
{ code: '<dd role="button" />;', errors: [expectedError] },
{ code: '<details role="button" />;', errors: [expectedError] },
{ code: '<dir role="button" />;', errors: [expectedError] },
{ code: '<dl role="button" />;', errors: [expectedError] },
{ code: '<dfn role="button" />;', errors: [expectedError] },
{ code: '<dt role="button" />;', errors: [expectedError] },
{ code: '<fieldset role="button" />;', errors: [expectedError] },
{ code: '<figcaption role="button" />;', errors: [expectedError] },
{ code: '<figure role="button" />;', errors: [expectedError] },
{ code: '<footer role="button" />;', errors: [expectedError] },
{ code: '<form role="button" />;', errors: [expectedError] },
{ code: '<frame role="button" />;', errors: [expectedError] },
{ code: '<h1 role="button" />;', errors: [expectedError] },
{ code: '<h2 role="button" />;', errors: [expectedError] },
{ code: '<h3 role="button" />;', errors: [expectedError] },
{ code: '<h4 role="button" />;', errors: [expectedError] },
{ code: '<h5 role="button" />;', errors: [expectedError] },
{ code: '<h6 role="button" />;', errors: [expectedError] },
{ code: '<hr role="button" />;', errors: [expectedError] },
{ code: '<iframe role="button" />;', errors: [expectedError] },
{ code: '<img role="button" />;', errors: [expectedError] },
{ code: '<label role="button" />;', errors: [expectedError] },
{ code: '<legend role="button" />;', errors: [expectedError] },
{ code: '<li role="button" />;', errors: [expectedError] },
{ code: '<mark role="button" />;', errors: [expectedError] },
{ code: '<marquee role="button" />;', errors: [expectedError] },
{ code: '<menu role="button" />;', errors: [expectedError] },
{ code: '<meter role="button" />;', errors: [expectedError] },
{ code: '<nav role="button" />;', errors: [expectedError] },
{ code: '<ol role="button" />;', errors: [expectedError] },
{ code: '<optgroup role="button" />;', errors: [expectedError] },
{ code: '<output role="button" />;', errors: [expectedError] },
{ code: '<pre role="button" />;', errors: [expectedError] },
{ code: '<progress role="button" />;', errors: [expectedError] },
{ code: '<ruby role="button" />;', errors: [expectedError] },
{ code: '<table role="button" />;', errors: [expectedError] },
{ code: '<tbody role="button" />;', errors: [expectedError] },
{ code: '<td role="button" />;', errors: [expectedError] },
{ code: '<tfoot role="button" />;', errors: [expectedError] },
{ code: '<thead role="button" />;', errors: [expectedError] },
{ code: '<time role="button" />;', errors: [expectedError] },
{ code: '<ul role="button" />;', errors: [expectedError] },
/* HTML elements with an inherent non-interactive role, assigned an
* interactive role. */
{ code: '<main role="menuitem" />;', errors: [expectedError] },
{ code: '<article role="menuitem" />;', errors: [expectedError] },
{ code: '<article role="menuitem" />;', errors: [expectedError] },
{ code: '<dd role="menuitem" />;', errors: [expectedError] },
{ code: '<dfn role="menuitem" />;', errors: [expectedError] },
{ code: '<dt role="menuitem" />;', errors: [expectedError] },
{ code: '<fieldset role="menuitem" />;', errors: [expectedError] },
{ code: '<figure role="menuitem" />;', errors: [expectedError] },
{ code: '<form role="menuitem" />;', errors: [expectedError] },
{ code: '<frame role="menuitem" />;', errors: [expectedError] },
{ code: '<h1 role="menuitem" />;', errors: [expectedError] },
{ code: '<h2 role="menuitem" />;', errors: [expectedError] },
{ code: '<h3 role="menuitem" />;', errors: [expectedError] },
{ code: '<h4 role="menuitem" />;', errors: [expectedError] },
{ code: '<h5 role="menuitem" />;', errors: [expectedError] },
{ code: '<h6 role="menuitem" />;', errors: [expectedError] },
{ code: '<hr role="menuitem" />;', errors: [expectedError] },
{ code: '<img role="menuitem" />;', errors: [expectedError] },
{ code: '<nav role="menuitem" />;', errors: [expectedError] },
{ code: '<ol role="menuitem" />;', errors: [expectedError] },
{ code: '<p role="button" />;', errors: [expectedError] },
{ code: '<section role="button" aria-label="Aardvark" />;', errors: [expectedError] },
{ code: '<table role="menuitem" />;', errors: [expectedError] },
{ code: '<tbody role="menuitem" />;', errors: [expectedError] },
{ code: '<td role="menuitem" />;', errors: [expectedError] },
{ code: '<tfoot role="menuitem" />;', errors: [expectedError] },
{ code: '<thead role="menuitem" />;', errors: [expectedError] },
/* Custom components */
{ code: '<Article role="button" />', errors: [expectedError], settings: componentsSettings },
];
const recommendedOptions = (configs.recommended.rules[ruleName][1] || {});
ruleTester.run(`${ruleName}:recommended`, rule, {
valid: [
...alwaysValid,
{ code: '<ul role="menu" />;' },
{ code: '<ul role="menubar" />;' },
{ code: '<ul role="radiogroup" />;' },
{ code: '<ul role="tablist" />;' },
{ code: '<ul role="tree" />;' },
{ code: '<ul role="treegrid" />;' },
{ code: '<ol role="menu" />;' },
{ code: '<ol role="menubar" />;' },
{ code: '<ol role="radiogroup" />;' },
{ code: '<ol role="tablist" />;' },
{ code: '<ol role="tree" />;' },
{ code: '<ol role="treegrid" />;' },
{ code: '<li role="tab" />;' },
{ code: '<li role="menuitem" />;' },
{ code: '<li role="row" />;' },
{ code: '<li role="treeitem" />;' },
{ code: '<Component role="treeitem" />;' },
{ code: '<fieldset role="radiogroup" />;' },
{ code: '<fieldset role="presentation" />;' },
]
.map(ruleOptionsMapperFactory(recommendedOptions))
.map(parserOptionsMapper),
invalid: [
...neverValid,
]
.map(ruleOptionsMapperFactory(recommendedOptions))
.map(parserOptionsMapper),
});
ruleTester.run(`${ruleName}:strict`, rule, {
valid: [
...alwaysValid,
].map(parserOptionsMapper),
invalid: [
...neverValid,
{ code: '<ul role="menu" />;', errors: [expectedError] },
{ code: '<ul role="menubar" />;', errors: [expectedError] },
{ code: '<ul role="radiogroup" />;', errors: [expectedError] },
{ code: '<ul role="tablist" />;', errors: [expectedError] },
{ code: '<ul role="tree" />;', errors: [expectedError] },
{ code: '<ul role="treegrid" />;', errors: [expectedError] },
{ code: '<ol role="menu" />;', errors: [expectedError] },
{ code: '<ol role="menubar" />;', errors: [expectedError] },
{ code: '<ol role="radiogroup" />;', errors: [expectedError] },
{ code: '<ol role="tablist" />;', errors: [expectedError] },
{ code: '<ol role="tree" />;', errors: [expectedError] },
{ code: '<ol role="treegrid" />;', errors: [expectedError] },
{ code: '<li role="tab" />;', errors: [expectedError] },
{ code: '<li role="menuitem" />;', errors: [expectedError] },
{ code: '<li role="row" />;', errors: [expectedError] },
{ code: '<li role="treeitem" />;', errors: [expectedError] },
].map(parserOptionsMapper),
});