diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index db4aa44902..b05317b5d9 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -21,6 +21,7 @@ | document-title | Ensures each HTML document contains a non-empty <title> element | cat.text-alternatives, wcag2a, wcag242 | true | | duplicate-id | Ensures every id attribute value is unique | cat.parsing, wcag2a, wcag411 | true | | empty-heading | Ensures headings have discernible text | cat.name-role-value, best-practice | true | +| focus-order-semantics | Ensures elements placed in the focus order have an appropriate aria role for interactive content | cat.keyboard, best-practice, experimental | true | | frame-title-unique | Ensures <iframe> and <frame> elements contain a unique title attribute | cat.text-alternatives, best-practice | true | | frame-title | Ensures <iframe> and <frame> elements contain a non-empty title attribute | cat.text-alternatives, wcag2a, wcag241, section508, section508.22.i | true | | heading-order | Ensures the order of headings is semantically correct | cat.semantics, best-practice | true | diff --git a/lib/checks/aria/has-widget-role.js b/lib/checks/aria/has-widget-role.js new file mode 100644 index 0000000000..c4ebb8b6e3 --- /dev/null +++ b/lib/checks/aria/has-widget-role.js @@ -0,0 +1,6 @@ +var role = node.getAttribute('role'); +if (role === null) { + return false; +} +var roleType = axe.commons.aria.getRoleType(role); +return roleType === 'widget' || roleType === 'composite'; diff --git a/lib/checks/aria/has-widget-role.json b/lib/checks/aria/has-widget-role.json new file mode 100644 index 0000000000..181efcea92 --- /dev/null +++ b/lib/checks/aria/has-widget-role.json @@ -0,0 +1,12 @@ +{ + "id": "has-widget-role", + "evaluate": "has-widget-role.js", + "options": [], + "metadata": { + "impact": "minor", + "messages": { + "pass": "Element has a widget role.", + "fail": "Element does not have a widget role." + } + } +} diff --git a/lib/checks/aria/valid-scrollable-semantics.js b/lib/checks/aria/valid-scrollable-semantics.js new file mode 100644 index 0000000000..2f7a891131 --- /dev/null +++ b/lib/checks/aria/valid-scrollable-semantics.js @@ -0,0 +1,61 @@ +/** + * A map from HTML tag names to a boolean which reflects whether it is + * appropriate for scrollable elements found in the focus order. + */ +const VALID_TAG_NAMES_FOR_SCROLLABLE_REGIONS = { + ARTICLE: true, + ASIDE: true, + NAV: true, + SECTION: true +}; + +/** + * A map from each landmark role to a boolean which reflects whether it is + * appropriate for scrollable elements found in the focus order. + */ +const VALID_ROLES_FOR_SCROLLABLE_REGIONS = { + banner: false, + complementary: true, + contentinfo: true, + form: true, + main: true, + navigation: true, + region: true, + search: false +}; + +/** + * @param {HTMLElement} node + * @return {Boolean} Whether the element has a tag appropriate for a scrollable + * region. + */ +function validScrollableTagName(node) { + // Some elements with nonsensical roles will pass this check, but should be + // flagged by other checks. + var tagName = node.tagName.toUpperCase(); + return VALID_TAG_NAMES_FOR_SCROLLABLE_REGIONS[tagName] || false; +} + +/** + * @param {HTMLElement} node + * @return {Boolean} Whether the node has a role appropriate for a scrollable + * region. + */ +function validScrollableRole(node) { + var role = node.getAttribute('role'); + if (!role) { + return false; + } + return VALID_ROLES_FOR_SCROLLABLE_REGIONS[role.toLowerCase()] || false; +} + +/** + * @param {HTMLElement} node + * @return {Boolean} Whether the element would have valid semantics if it were a + * scrollable region. + */ +function validScrollableSemantics(node) { + return validScrollableRole(node) || validScrollableTagName(node); +} + +return validScrollableSemantics(node); diff --git a/lib/checks/aria/valid-scrollable-semantics.json b/lib/checks/aria/valid-scrollable-semantics.json new file mode 100644 index 0000000000..1db8995c66 --- /dev/null +++ b/lib/checks/aria/valid-scrollable-semantics.json @@ -0,0 +1,12 @@ +{ + "id": "valid-scrollable-semantics", + "evaluate": "valid-scrollable-semantics.js", + "options": [], + "metadata": { + "impact": "minor", + "messages": { + "pass": "Element has valid semantics for an element in the focus order.", + "fail": "Element has invalid semantics for an element in the focus order." + } + } +} diff --git a/lib/commons/dom/is-focusable.js b/lib/commons/dom/is-focusable.js index 753f056e43..6ba5dc71c0 100644 --- a/lib/commons/dom/is-focusable.js +++ b/lib/commons/dom/is-focusable.js @@ -70,3 +70,18 @@ dom.isNativelyFocusable = function(el) { } return false; }; + +/** + * Determines if an element is in the focus order, but would not be if its + * tabindex were unspecified. + * @method insertedIntoFocusOrder + * @memberof axe.commons.dom + * @instance + * @param {HTMLElement} el The HTMLElement + * @return {Boolean} True if the element is in the focus order but wouldn't be + * if its tabindex were removed. Else, false. + */ +dom.insertedIntoFocusOrder = function(el) { + return (el.tabIndex > -1 && dom.isFocusable(el) && + !dom.isNativelyFocusable(el)); +}; diff --git a/lib/rules/focus-order-semantics.json b/lib/rules/focus-order-semantics.json new file mode 100644 index 0000000000..385aeba536 --- /dev/null +++ b/lib/rules/focus-order-semantics.json @@ -0,0 +1,13 @@ +{ + "id": "focus-order-semantics", + "selector": "div, h1, h2, h3, h4, h5, h6, [role=heading], p, span", + "matches": "inserted-into-focus-order-matches.js", + "tags": ["cat.keyboard", "best-practice", "experimental"], + "metadata": { + "description": "Ensures elements in the focus order have an appropriate role", + "help": "Elements in the focus order need a role appropriate for interactive content" + }, + "all": [], + "any": ["has-widget-role", "valid-scrollable-semantics"], + "none": [] +} diff --git a/lib/rules/inserted-into-focus-order-matches.js b/lib/rules/inserted-into-focus-order-matches.js new file mode 100644 index 0000000000..36805e2d1a --- /dev/null +++ b/lib/rules/inserted-into-focus-order-matches.js @@ -0,0 +1 @@ +return axe.commons.dom.insertedIntoFocusOrder(node); diff --git a/test/checks/aria/has-widget-role.js b/test/checks/aria/has-widget-role.js new file mode 100644 index 0000000000..6397f65c5c --- /dev/null +++ b/test/checks/aria/has-widget-role.js @@ -0,0 +1,452 @@ +describe('has-widget-role', function () { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var node; + var checkContext = axe.testUtils.MockCheckContext(); + + + afterEach(function () { + node.innerHTML = ''; + checkContext._data = null; + }); + + it('should return false for elements with no role', function() { + node = document.createElement('div'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return false for elements with nonsensical roles', function() { + node = document.createElement('div'); + node.setAttribute('role', 'buttonbuttonbutton'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + // Widget roles + it('should return true for role=button', function() { + node = document.createElement('div'); + node.setAttribute('role', 'button'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=checkbox', function() { + node = document.createElement('div'); + node.setAttribute('role', 'checkbox'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=gridcell', function() { + node = document.createElement('div'); + node.setAttribute('role', 'gridcell'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=link', function() { + node = document.createElement('div'); + node.setAttribute('role', 'link'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=menuitem', function() { + node = document.createElement('div'); + node.setAttribute('role', 'menuitem'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=menuitemcheckbox', function() { + node = document.createElement('div'); + node.setAttribute('role', 'menuitemcheckbox'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=menuitemradio', function() { + node = document.createElement('div'); + node.setAttribute('role', 'menuitemradio'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=option', function() { + node = document.createElement('div'); + node.setAttribute('role', 'option'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=progressbar', function() { + node = document.createElement('div'); + node.setAttribute('role', 'progressbar'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=radio', function() { + node = document.createElement('div'); + node.setAttribute('role', 'radio'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=scrollbar', function() { + node = document.createElement('div'); + node.setAttribute('role', 'scrollbar'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=searchbox', function() { + node = document.createElement('div'); + node.setAttribute('role', 'searchbox'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=slider', function() { + node = document.createElement('div'); + node.setAttribute('role', 'slider'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=spinbutton', function() { + node = document.createElement('div'); + node.setAttribute('role', 'spinbutton'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=switch', function() { + node = document.createElement('div'); + node.setAttribute('role', 'switch'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=tab', function() { + node = document.createElement('div'); + node.setAttribute('role', 'tab'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=tabpanel', function() { + node = document.createElement('div'); + node.setAttribute('role', 'tabpanel'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=textbox', function() { + node = document.createElement('div'); + node.setAttribute('role', 'textbox'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=treeitem', function() { + node = document.createElement('div'); + node.setAttribute('role', 'treeitem'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + // Composite widget roles + it('should return true for role=combobox', function() { + node = document.createElement('div'); + node.setAttribute('role', 'combobox'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=grid', function() { + node = document.createElement('div'); + node.setAttribute('role', 'grid'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=listbox', function() { + node = document.createElement('div'); + node.setAttribute('role', 'listbox'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=menu', function() { + node = document.createElement('div'); + node.setAttribute('role', 'menu'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=menubar', function() { + node = document.createElement('div'); + node.setAttribute('role', 'menubar'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=radiogroup', function() { + node = document.createElement('div'); + node.setAttribute('role', 'radiogroup'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=tablist', function() { + node = document.createElement('div'); + node.setAttribute('role', 'tablist'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=tree', function() { + node = document.createElement('div'); + node.setAttribute('role', 'tree'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=treegrid', function() { + node = document.createElement('div'); + node.setAttribute('role', 'treegrid'); + fixture.appendChild(node); + assert.isTrue(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + + + + it ('should return false for role=application', function() { + node = document.createElement('div'); + node.setAttribute('role', 'application'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=article', function() { + node = document.createElement('div'); + node.setAttribute('role', 'article'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=cell', function() { + node = document.createElement('div'); + node.setAttribute('role', 'cell'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=columnheader', function() { + node = document.createElement('div'); + node.setAttribute('role', 'columnheader'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=definition', function() { + node = document.createElement('div'); + node.setAttribute('role', 'definition'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=directory', function() { + node = document.createElement('div'); + node.setAttribute('role', 'directory'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=document', function() { + node = document.createElement('div'); + node.setAttribute('role', 'document'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=feed', function() { + node = document.createElement('div'); + node.setAttribute('role', 'feed'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=figure', function() { + node = document.createElement('div'); + node.setAttribute('role', 'figure'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=group', function() { + node = document.createElement('div'); + node.setAttribute('role', 'group'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=heading', function() { + node = document.createElement('div'); + node.setAttribute('role', 'heading'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=img', function() { + node = document.createElement('div'); + node.setAttribute('role', 'img'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=list', function() { + node = document.createElement('div'); + node.setAttribute('role', 'list'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=listitem', function() { + node = document.createElement('div'); + node.setAttribute('role', 'listitem'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=math', function() { + node = document.createElement('div'); + node.setAttribute('role', 'math'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=none', function() { + node = document.createElement('div'); + node.setAttribute('role', 'none'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=note', function() { + node = document.createElement('div'); + node.setAttribute('role', 'note'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=presentation', function() { + node = document.createElement('div'); + node.setAttribute('role', 'presentation'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=row', function() { + node = document.createElement('div'); + node.setAttribute('role', 'row'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=rowgroup', function() { + node = document.createElement('div'); + node.setAttribute('role', 'rowgroup'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=rowheader', function() { + node = document.createElement('div'); + node.setAttribute('role', 'rowheader'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=table', function() { + node = document.createElement('div'); + node.setAttribute('role', 'table'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=term', function() { + node = document.createElement('div'); + node.setAttribute('role', 'term'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=toolbar', function() { + node = document.createElement('div'); + node.setAttribute('role', 'toolbar'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + // Landmark Roles + it ('should return false for role=banner', function() { + node = document.createElement('div'); + node.setAttribute('role', 'banner'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=complementary', function() { + node = document.createElement('div'); + node.setAttribute('role', 'complementary'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=contentinfo', function() { + node = document.createElement('div'); + node.setAttribute('role', 'contentinfo'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=form', function() { + node = document.createElement('div'); + node.setAttribute('role', 'form'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=main', function() { + node = document.createElement('div'); + node.setAttribute('role', 'main'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=navigation', function() { + node = document.createElement('div'); + node.setAttribute('role', 'navigation'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=region', function() { + node = document.createElement('div'); + node.setAttribute('role', 'region'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); + + it ('should return false for role=search', function() { + node = document.createElement('div'); + node.setAttribute('role', 'search'); + fixture.appendChild(node); + assert.isFalse(checks['has-widget-role'].evaluate.call(checkContext, node)); + }); +}); diff --git a/test/checks/aria/valid-scrollable-semantics.js b/test/checks/aria/valid-scrollable-semantics.js new file mode 100644 index 0000000000..bfcbe9c0da --- /dev/null +++ b/test/checks/aria/valid-scrollable-semantics.js @@ -0,0 +1,91 @@ +describe('valid-scrollable-semantics', function () { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var checkContext = axe.testUtils.MockCheckContext(); + + afterEach(function () { + fixture.innerHTML = ''; + checkContext._data = null; + }); + + it('should return false for role=banner', function() { + var node = document.createElement('div'); + node.setAttribute('role', '"banner'); + fixture.appendChild(node); + assert.isFalse(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return false for role=search', function() { + var node = document.createElement('div'); + node.setAttribute('role', 'search'); + fixture.appendChild(node); + assert.isFalse(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=form', function() { + var node = document.createElement('div'); + node.setAttribute('role', 'form'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=navigation', function() { + var node = document.createElement('div'); + node.setAttribute('role', 'navigation'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=complementary', function() { + var node = document.createElement('div'); + node.setAttribute('role', 'complementary'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=contentinfo', function() { + var node = document.createElement('div'); + node.setAttribute('role', 'contentinfo'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=main', function() { + var node = document.createElement('div'); + node.setAttribute('role', 'main'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for role=region', function() { + var node = document.createElement('div'); + node.setAttribute('role', 'region'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for nav elements', function() { + var node = document.createElement('nav'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for section elements', function() { + var node = document.createElement('section'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for article elements', function() { + var node = document.createElement('article'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); + + it('should return true for aside elements', function() { + var node = document.createElement('aside'); + fixture.appendChild(node); + assert.isTrue(checks['valid-scrollable-semantics'].evaluate.call(checkContext, node)); + }); +}); diff --git a/test/commons/dom/is-focusable.js b/test/commons/dom/is-focusable.js index 13ab1e353a..56c3a92058 100644 --- a/test/commons/dom/is-focusable.js +++ b/test/commons/dom/is-focusable.js @@ -1,146 +1,589 @@ -describe('dom.isFocusable', function () { - 'use strict'; +describe('is-focusable', function() { - var fixture = document.getElementById('fixture'); + function hideByClipping (el) { + el.style.cssText = 'position: absolute !important;' + + ' clip: rect(0px 0px 0px 0px); /* IE6, IE7 */' + + ' clip: rect(0px, 0px, 0px, 0px);'; + } - afterEach(function () { - document.getElementById('fixture').innerHTML = ''; - }); + function hideByMovingOffScreen (el) { + el.style.cssText = 'position:absolute;' + + ' left:-10000px;' + + ' top:auto;' + + ' width:1px;' + + ' height:1px;' + + ' overflow:hidden;'; + } + var fixtureSetup = axe.testUtils.fixtureSetup; - it('should return true for visible, enabled textareas', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + describe('dom.isFocusable', function () { + 'use strict'; - assert.isTrue(axe.commons.dom.isFocusable(el)); + var fixture = document.getElementById('fixture'); - }); + afterEach(function () { + document.getElementById('fixture').innerHTML = ''; + }); - it('should return true for visible, enabled selects', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return true for visible, enabled textareas', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isTrue(axe.commons.dom.isFocusable(el)); + assert.isTrue(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return true for visible, enabled buttons', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return true for visible, enabled selects', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isTrue(axe.commons.dom.isFocusable(el)); + assert.isTrue(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return true for visible, enabled, non-hidden inputs', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return true for visible, enabled buttons', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isTrue(axe.commons.dom.isFocusable(el)); + assert.isTrue(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return false for disabled elements', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return true for visible, enabled, non-hidden inputs', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isFocusable(el)); + assert.isTrue(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return false for hidden inputs', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return false for disabled elements', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isFocusable(el)); + assert.isFalse(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return false for hidden inputs with tabindex', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return false for hidden inputs', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isFocusable(el)); + assert.isFalse(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return false for hidden buttons with tabindex', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return false for hidden inputs with tabindex', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isFocusable(el)); + assert.isFalse(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return false for disabled buttons with tabindex', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return false for hidden buttons with tabindex', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isFocusable(el)); + assert.isFalse(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return false for non-visible elements', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return false for disabled buttons with tabindex', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isFocusable(el)); + assert.isFalse(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return true for an anchor with an href', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return false for non-visible elements', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isTrue(axe.commons.dom.isFocusable(el)); + assert.isFalse(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return false for an anchor with no href', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return true for an anchor with an href', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isFocusable(el)); + assert.isTrue(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return true for a div with a tabindex with spaces', function () { - fixture.innerHTML = '
'; - var el = document.getElementById('target'); + it('should return false for an anchor with no href', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isTrue(axe.commons.dom.isFocusable(el)); + assert.isFalse(axe.commons.dom.isFocusable(el)); - }); + }); - it('should return true for a div with a tabindex', function () { - fixture.innerHTML = ''; - var el = document.getElementById('target'); + it('should return true for a div with a tabindex with spaces', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); - assert.isTrue(axe.commons.dom.isFocusable(el)); + assert.isTrue(axe.commons.dom.isFocusable(el)); - }); + }); + + it('should return true for a div with a tabindex', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); + + assert.isTrue(axe.commons.dom.isFocusable(el)); + + }); + + it('should return false for a div with a non-numeric tabindex', function () { + fixture.innerHTML = ''; + var el = document.getElementById('target'); + + assert.isFalse(axe.commons.dom.isFocusable(el)); + + }); + + it('should return true for a details element', function () { + fixture.innerHTML = 'Detail
Detail
Detail
Detail