diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b0991be76..c9d25f1a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [3.1.2](https://github.com/dequelabs/axe-core/compare/v3.0.3...v3.1.2) (2018-09-07) + + +### Bug Fixes + +* **i18n:** Update Japanese locale ([#1107](https://github.com/dequelabs/axe-core/issues/1107)) ([8138e55](https://github.com/dequelabs/axe-core/commit/8138e55)) +* autocomplete appropriate to handle state terms ([#1121](https://github.com/dequelabs/axe-core/issues/1121)) ([35a4d11](https://github.com/dequelabs/axe-core/commit/35a4d11)) +* banner comment in generated axe files ([#1112](https://github.com/dequelabs/axe-core/issues/1112)) ([e4788bf](https://github.com/dequelabs/axe-core/commit/e4788bf)) +* ignore invalid and allow redundant role in aria-allowed-role ([#1118](https://github.com/dequelabs/axe-core/issues/1118)) ([a0f9b31](https://github.com/dequelabs/axe-core/commit/a0f9b31)) + + + ## [3.1.1](https://github.com/dequelabs/axe-core/compare/v3.0.3...v3.1.1) (2018-08-28) diff --git a/Gruntfile.js b/Gruntfile.js index 813b3e0cea..3512ef2963 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -231,7 +231,7 @@ module.exports = function(grunt) { quote_style: 1 }, output: { - comments: /^!/ + comments: /^\/*! aXe/ } } }, @@ -243,9 +243,8 @@ module.exports = function(grunt) { }; }), options: { - preserveComments: function(node, comment) { - // preserve comments that start with a bang - return /^!/.test(comment.value); + output: { + comments: /^\/*! aXe/ }, mangle: { reserved: ['commons', 'utils', 'axe', 'window', 'document'] diff --git a/bower.json b/bower.json index 82168a85bc..9d7b766d94 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "3.1.1", + "version": "3.1.2", "contributors": [ { "name": "David Sturley", diff --git a/doc/examples/jest_react/package.json b/doc/examples/jest_react/package.json index a6140f977c..21373ab344 100644 --- a/doc/examples/jest_react/package.json +++ b/doc/examples/jest_react/package.json @@ -17,8 +17,8 @@ "babel-jest": "^23.0.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", - "enzyme": "^3.4.4", - "enzyme-adapter-react-16": "^1.2.0", + "enzyme": "^3.5.0", + "enzyme-adapter-react-16": "^1.3.0", "jest": "^22.4.0", "jest-cli": "^23.0.1", "react": "^16.4.0", diff --git a/lib/checks/forms/autocomplete-appropriate.js b/lib/checks/forms/autocomplete-appropriate.js index 259dea94dd..43358f0b42 100644 --- a/lib/checks/forms/autocomplete-appropriate.js +++ b/lib/checks/forms/autocomplete-appropriate.js @@ -38,9 +38,13 @@ const autocomplete = node.getAttribute('autocomplete'); const autocompleteTerms = autocomplete .split(/\s+/g) .map(term => term.toLowerCase()); + const purposeTerm = autocompleteTerms[autocompleteTerms.length - 1]; -const allowedTypes = allowedTypesMap[purposeTerm]; +if (axe.commons.text.autocomplete.stateTerms.includes(purposeTerm)) { + return true; +} +const allowedTypes = allowedTypesMap[purposeTerm]; if (typeof allowedTypes === 'undefined') { return node.type === 'text'; } diff --git a/lib/commons/aria/get-element-unallowed-roles.js b/lib/commons/aria/get-element-unallowed-roles.js index 2fbb6dd337..b371dee71a 100644 --- a/lib/commons/aria/get-element-unallowed-roles.js +++ b/lib/commons/aria/get-element-unallowed-roles.js @@ -1,4 +1,46 @@ /* global aria */ + +/** + * Returns all roles applicable to element in a list + * + * @method getRoleSegments + * @private + * @param {Element} node + * @returns {Array} Roles list or empty list + */ + +function getRoleSegments(node) { + let roles = []; + + if (!node) { + return roles; + } + + if (node.hasAttribute('role')) { + const nodeRoles = axe.utils.tokenList( + node.getAttribute('role').toLowerCase() + ); + roles = roles.concat(nodeRoles); + } + + if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) { + const epubRoles = axe.utils + .tokenList( + node + .getAttributeNS('http://www.idpf.org/2007/ops', 'type') + .toLowerCase() + ) + .map(role => `doc-${role}`); + + roles = roles.concat(epubRoles); + } + + // filter invalid roles + roles = roles.filter(role => axe.commons.aria.isValidRole(role)); + + return roles; +} + /** * gets all unallowed roles for a given node * @method getElementUnallowedRoles @@ -9,38 +51,8 @@ */ aria.getElementUnallowedRoles = function getElementUnallowedRoles( node, - allowImplicit + allowImplicit = true ) { - /** - * Get roles applied to a given node - * @param {HTMLElement} node HTMLElement - * @return {Array} return an array of roles applied to the node, if no roles, return an empty array. - */ - // TODO: not moving this to outer namespace yet, work with wilco to see overlap with his PR(WIP) - aria.getRole - function getRoleSegments(node) { - let roles = []; - if (!node) { - return roles; - } - if (node.hasAttribute('role')) { - const nodeRoles = axe.utils.tokenList( - node.getAttribute('role').toLowerCase() - ); - roles = roles.concat(nodeRoles); - } - if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) { - const epubRoles = axe.utils - .tokenList( - node - .getAttributeNS('http://www.idpf.org/2007/ops', 'type') - .toLowerCase() - ) - .map(role => `doc-${role}`); - roles = roles.concat(epubRoles); - } - return roles; - } - const tagName = node.nodeName.toUpperCase(); // by pass custom elements @@ -53,24 +65,26 @@ aria.getElementUnallowedRoles = function getElementUnallowedRoles( // stores all roles that are not allowed for a specific element most often an element only has one explicit role const unallowedRoles = roleSegments.filter(role => { - if (!axe.commons.aria.isValidRole(role)) { - // do not check made-up/ fake roles + // if role and implicit role are same, when allowImplicit: true + // ignore as it is a redundant role + if (allowImplicit && role === implicitRole) { return false; } - // check if an implicit role may be set explicit following a setting - if (!allowImplicit && role === implicitRole) { - // edge case: setting implicit role row on tr element is allowed when child of table[role='grid'] - if ( - !( - role === 'row' && - tagName === 'TR' && - axe.utils.matchesSelector(node, 'table[role="grid"] > tr') - ) - ) { - return true; - } + // Edge case: + // setting implicit role row on tr element is allowed when child of table[role='grid'] + if ( + !allowImplicit && + !( + role === 'row' && + tagName === 'TR' && + axe.utils.matchesSelector(node, 'table[role="grid"] > tr') + ) + ) { + return true; } + + // check if role is allowed on element if (!aria.isAriaRoleAllowedOnElement(node, role)) { return true; } diff --git a/lib/commons/aria/get-role.js b/lib/commons/aria/get-role.js index ae9aa3ce3b..1ab0bae5d8 100644 --- a/lib/commons/aria/get-role.js +++ b/lib/commons/aria/get-role.js @@ -28,6 +28,7 @@ aria.getRole = function getRole( } return aria.isValidRole(role, { allowAbstract: abstracts }); }); + const explicitRole = validRoles[0]; // Get the implicit role, if permitted diff --git a/lib/commons/aria/index.js b/lib/commons/aria/index.js index d5cd6580b2..90abf48c8f 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -420,7 +420,15 @@ lookupTable.role = { }, nameFrom: ['author'], context: null, - unsupported: false + unsupported: false, + allowedElements: [ + { + tagName: 'INPUT', + attributes: { + TYPE: 'TEXT' + } + } + ] }, command: { nameFrom: ['author'], @@ -1687,7 +1695,15 @@ lookupTable.role = { nameFrom: ['author'], context: null, implicit: ['input[type="search"]'], - unsupported: false + unsupported: false, + allowedElements: [ + { + tagName: 'INPUT', + attributes: { + TYPE: 'TEXT' + } + } + ] }, section: { nameFrom: ['author', 'contents'], @@ -1756,7 +1772,15 @@ lookupTable.role = { nameFrom: ['author'], context: null, implicit: ['input[type="number"]'], - unsupported: false + unsupported: false, + allowedElements: [ + { + tagName: 'INPUT', + attributes: { + TYPE: 'TEXT' + } + } + ] }, status: { type: 'widget', @@ -2148,13 +2172,6 @@ lookupTable.elementsAllowedNoRole = [ TYPE: 'TEL' } }, - { - tagName: 'INPUT', - condition: elementConditions.CANNOT_HAVE_LIST_ATTRIBUTE, - attributes: { - TYPE: 'TEXT' - } - }, { tagName: 'INPUT', attributes: { @@ -2368,6 +2385,10 @@ lookupTable.evaluateRoleForElement = { return out; case 'radio': return role === 'menuitemradio'; + case 'text': + return ( + role === 'combobox' || role === 'searchbox' || role === 'spinbutton' + ); default: return false; } diff --git a/lib/rules/aria-allowed-role-matches.js b/lib/rules/aria-allowed-role-matches.js new file mode 100644 index 0000000000..b3375967a3 --- /dev/null +++ b/lib/rules/aria-allowed-role-matches.js @@ -0,0 +1,7 @@ +return ( + axe.commons.aria.getRole(node, { + noImplicit: true, + dpub: true, + fallback: true + }) !== null +); diff --git a/lib/rules/aria-allowed-role.json b/lib/rules/aria-allowed-role.json index ad71e25d1e..3c0c8941a0 100644 --- a/lib/rules/aria-allowed-role.json +++ b/lib/rules/aria-allowed-role.json @@ -2,6 +2,7 @@ "id": "aria-allowed-role", "excludeHidden": false, "selector": "[role]", + "matches": "aria-allowed-role-matches.js", "tags": [ "cat.aria", "best-practice" diff --git a/locales/ja.json b/locales/ja.json index 58edcd3ed2..887c849026 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -93,6 +93,14 @@ "description": "各HTMLドキュメントに空でない要素が含まれていることを確認してください", "help": "ドキュメントにはナビゲーションを補助するために<title>要素がなければなりません" }, + "duplicate-id-active": { + "description": "有効な要素のid属性値が一意であることを確認してください", + "help": "有効な要素のIDは一意でなければなりません" + }, + "duplicate-id-aria": { + "description": "ARIAおよびラベルに使用されているid属性値が一意であることを確認してください", + "help": "ARIAおよびラベルに使用されているIDは一意でなければなりません" + }, "duplicate-id": { "description": "全てのid属性値が一意であることを確認してください", "help": "id属性値は一意でなければなりません" @@ -326,7 +334,7 @@ "aria-required-children": { "pass": "必須のARIA子ロールが存在しています", "fail": "必須のARIAロールが提供されていません:{{~it.data:value}} {{=value}}{{~}}", - "incomplete": "Expecting ARIA {{=it.data && it.data.length > 1 ? 'children' : 'child'}} role to be added:{{~it.data:value}} {{=value}}{{~}}" + "incomplete": "ARIAの子ロールが追加されることが求められます:{{~it.data:value}} {{=value}}{{~}}" }, "aria-required-parent": { "pass": "必須のARIA親ロールが存在しています", @@ -361,6 +369,7 @@ "elmPartiallyObscuring": "他の要素と部分的に重なっているため、要素の背景色を判定できません", "outsideViewport": "ビューポートの外にあるため、要素の背景色を判定できません", "equalRatio": "要素のコントラスト比が背景と1:1です", + "shortTextContent": "実際のテキストコンテンツであるかを判断するには要素のコンテンツが短すぎます", "default": "コントラスト比を判定できません" } }, @@ -441,8 +450,8 @@ "fail": "ヘルプテキスト(titleまたはaria-describedby)がラベルテキストと同じです" }, "hidden-explicit-label": { - "pass": "Form element has a visible explicit <label>", - "fail": "Form element has explicit <label> that is hidden" + "pass": "フォーム要素に視認可能で明確な<label>があります", + "fail": "フォーム要素に非表示の明確な<label>があります" }, "implicit-label": { "pass": "フォーム要素に暗黙の(包含された)<label>が存在しています", @@ -494,12 +503,10 @@ }, "caption": { "pass": "マルチメディア要素にキャプショントラックが存在しています", - "fail": "マルチメディア要素にキャプショントラックが存在していません", "incomplete": "この要素のキャプショントラックが見つかりません" }, "description": { "pass": "マルチメディア要素に音声解説トラックが存在しています", - "fail": "マルチメディア要素に音声解説トラックが存在していません", "incomplete": "この要素の音声解説トラックが見つかりません" }, "frame-tested": { @@ -556,6 +563,18 @@ "pass": "要素のtitle属性が一意です", "fail": "要素のtitle属性が一意ではありません" }, + "duplicate-id-active": { + "pass": "ドキュメントに同じid属性を持つ有効な要素はありません", + "fail": "ドキュメントに同じid属性を持つ有効な要素があります: {{=it.data}}" + }, + "duplicate-id-aria": { + "pass": "ドキュメントに同じid属性を持つARIAまたはラベルに参照された要素はありません", + "fail": "ドキュメントに同じid属性を持つARIAに参照された複数の要素があります: {{=it.data}}" + }, + "duplicate-id": { + "pass": "ドキュメントに同じid属性を持つ要素は存在していません", + "fail": "ドキュメントに同じid属性を持つ要素が複数存在しています: {{=it.data}}" + }, "aria-label": { "pass": "aria-label属性が存在し、空ではありません", "fail": "aria-label属性が存在しない、または空です" @@ -572,10 +591,6 @@ "pass": "ドキュメントに空ではない<title>要素が存在しています", "fail": "ドキュメントに空ではない<title>要素が存在していません" }, - "duplicate-id": { - "pass": "ドキュメントに同じid属性を持つ要素は存在していません", - "fail": "ドキュメントに同じid属性を持つ要素が複数存在しています: {{=it.data}}" - }, "exists": { "pass": "要素は存在していません", "fail": "要素が存在しています" diff --git a/package.json b/package.json index 7e226813b3..19681ef83d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "axe-core", "description": "Accessibility engine for automated Web UI testing", - "version": "3.1.1", + "version": "3.1.2", "license": "MPL-2.0", "engines": { "node": ">=4" @@ -90,7 +90,7 @@ "grunt-contrib-concat": "^1.0.1", "grunt-contrib-connect": "^1.0.2", "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-uglify": "^3.3.0", + "grunt-contrib-uglify": "^4.0.0", "grunt-contrib-watch": "^1.1.0", "grunt-eslint": "^21.0.0", "grunt-markdownlint": "^2.0.0", diff --git a/sri-history.json b/sri-history.json index f50737073c..b67f8b0242 100644 --- a/sri-history.json +++ b/sri-history.json @@ -114,9 +114,13 @@ "3.1.0": { "axe.js": "sha256-qUMvWIpNXGal2tqBIDJsrXde0clrbTcATxlteQSyA4M=", "axe.min.js": "sha256-q1K/bHXBTINVndnMyDdntNSJHTAei0YhN/kNqeaGD5A=" - }, + }, "3.1.1": { "axe.js": "sha256-54k0QW1jMWPC2Lq/pTSX2xu8ky7QSd39uEqcy3Yk35o=", "axe.min.js": "sha256-Zs6maznrnIa0ko83hAWIKlhPPeBLmKmRmfphyuqBJbA=" + }, + "3.1.2": { + "axe.js": "sha256-goUY5vBRPoITOCdg7HK/vfHRs50+RdQRQQkNCU3lnMs=", + "axe.min.js": "sha256-wIvlzfT77n6fOnSL6/oLbzB873rY7QHTW/e0Z0mOoYs=" } -} +} \ No newline at end of file diff --git a/test/checks/aria/aria-allowed-role.js b/test/checks/aria/aria-allowed-role.js index 181266f3a6..d3a56ef325 100644 --- a/test/checks/aria/aria-allowed-role.js +++ b/test/checks/aria/aria-allowed-role.js @@ -91,7 +91,37 @@ describe('aria-allowed-role', function() { ); }); - it('returns false when MENU has type context', function() { + it('returns true when INPUT type is text with role combobox', function() { + var node = document.createElement('input'); + node.setAttribute('type', 'text'); + node.setAttribute('role', 'combobox'); + fixture.appendChild(node); + assert.isTrue( + checks['aria-allowed-role'].evaluate.call(checkContext, node) + ); + }); + + it('returns true when INPUT type is text with role spinbutton', function() { + var node = document.createElement('input'); + node.setAttribute('type', 'text'); + node.setAttribute('role', 'spinbutton'); + fixture.appendChild(node); + assert.isTrue( + checks['aria-allowed-role'].evaluate.call(checkContext, node) + ); + }); + + it('returns true when INPUT type is text with role searchbox', function() { + var node = document.createElement('input'); + node.setAttribute('type', 'text'); + node.setAttribute('role', 'searchbox'); + fixture.appendChild(node); + assert.isTrue( + checks['aria-allowed-role'].evaluate.call(checkContext, node) + ); + }); + + it('returns true when MENU has type context', function() { var node = document.createElement('menu'); node.setAttribute('type', 'context'); node.setAttribute('role', 'navigation'); @@ -135,10 +165,8 @@ describe('aria-allowed-role', function() { node.setAttribute('role', 'link'); node.href = ''; fixture.appendChild(node); - assert.isFalse( - checks['aria-allowed-role'].evaluate.call(checkContext, node) - ); - assert.deepEqual(checkContext._data, ['link']); + var actual = checks['aria-allowed-role'].evaluate.call(checkContext, node); + assert.isTrue(actual); }); it('returns true <img> with a non-empty alt', function() { @@ -159,17 +187,6 @@ describe('aria-allowed-role', function() { assert.isFalse( checks['aria-allowed-role'].evaluate.call(checkContext, node) ); - // assert.deepEqual(checkContext._data, ['presentation']); - }); - - it('should not allow a <link> with a href to have any invalid role', function() { - var node = document.createElement('link'); - node.setAttribute('role', 'invalid-role'); - node.href = '\\example.com'; - fixture.appendChild(node); - assert.isTrue( - checks['aria-allowed-role'].evaluate.call(checkContext, node) - ); }); it('should allow <select> without a multiple and size attribute to have a menu role', function() { diff --git a/test/checks/forms/autocomplete-appropriate.js b/test/checks/forms/autocomplete-appropriate.js index aae35f69be..30be5c6bc1 100644 --- a/test/checks/forms/autocomplete-appropriate.js +++ b/test/checks/forms/autocomplete-appropriate.js @@ -54,6 +54,18 @@ describe('autocomplete-appropriate', function() { assert.isTrue(evaluate.apply(checkContext, params)); }); + it('returns true if the input type is tel and the term is off', function() { + var options = {}; + var params = autocompleteCheckParams('off', 'tel', options); + assert.isTrue(evaluate.apply(checkContext, params)); + }); + + it('returns true if the input type is url and the term is on', function() { + var options = {}; + var params = autocompleteCheckParams('on', 'url', options); + assert.isTrue(evaluate.apply(checkContext, params)); + }); + it('returns false if the input type is text and the term maps to an empty array', function() { var options = { foo: [] }; var params = autocompleteCheckParams('foo', 'text', options); diff --git a/test/commons/aria/get-element-unallowed-roles.js b/test/commons/aria/get-element-unallowed-roles.js index 318bd455e2..b01344243d 100644 --- a/test/commons/aria/get-element-unallowed-roles.js +++ b/test/commons/aria/get-element-unallowed-roles.js @@ -52,4 +52,22 @@ describe('aria.getElementUnallowedRoles', function() { var actual = axe.commons.aria.getElementUnallowedRoles(node); assert.isEmpty(actual); }); + + it('returns false when role is implicit and allowImplicit is true (default)', function() { + var node = document.createElement('input'); + var role = 'textbox'; + node.setAttribute('role', role); + var actual = axe.commons.aria.getElementUnallowedRoles(node, true); + assert.isEmpty(actual); + }); + + it('returns false with implicit role of row for TR when allowImplicit is set to false via options', function() { + var node = document.createElement('table'); + node.setAttribute('role', 'grid'); + var row = document.createElement('tr'); + row.setAttribute('role', 'row'); + var actual = axe.commons.aria.getElementUnallowedRoles(row, false); + assert.isNotEmpty(actual); + assert.include(actual, 'row'); + }); }); diff --git a/test/integration/rules/aria-allowed-role/aria-allowed-role.html b/test/integration/rules/aria-allowed-role/aria-allowed-role.html index 6c3915d433..b27daccdfb 100644 --- a/test/integration/rules/aria-allowed-role/aria-allowed-role.html +++ b/test/integration/rules/aria-allowed-role/aria-allowed-role.html @@ -7,7 +7,7 @@ <section id="pass-section-role-doc-bib" role="doc-bibliography"></section> <ul><li id='pass-li-role-doc-biblioentry' role="doc-biblioentry"></li></ul> <aside id='pass-aside-doc-example' role='doc-example'></aside> -<div id='pass-div-has-any-role' role='divAnyRoleEvenInvalid'></div> +<div id='pass-div-has-any-role' role='contentinfo'></div> <div id="pass-div-valid-role" role="link">ok</div> <ol id="pass-ol-valid-role" role="directory"></ol> <nav id="pass-nav-role-doc-index" role="doc-index"></nav> @@ -27,6 +27,12 @@ <h1 id="pass-h1-role-doc-subtitle" role="doc-subtitle"></h1> <header id='pass-header-valid-role' role="group"></header> <footer id='pass-footer-valid-role' role="group"></footer> <embed id='pass-embed-valid-role' role='img'> +<input type="text" role="textbox" id="pass-input-text-redundant-role"/> +<input type="text" role="textbox combobox" id="pass-input-multiple-roles"/> +<input type="text" role="searchbox invalidrole" id="pass-input-multiple-valid-and-invalid-roles"/> +<input aria-autocomplete="list" aria-label="some label" autocomplete="off" role="searchbox" type="text" aria-expanded="true" id='pass-input-text-role-searchbox'> +<input aria-autocomplete="list" aria-label="some label" autocomplete="off" role="combobox" type="text" aria-expanded="true" id='pass-input-text-role-combobox'> +<input aria-autocomplete="list" aria-label="some label" autocomplete="off" role="spinbutton" type="text" aria-expanded="true" id='pass-input-text-role-spinbutton'> <input type="image" role="link" id="pass-input-image-valid-role"> <input type="checkbox" role='menuitemcheckbox' id='pass-input-checkbox-valid-role' > <h1 id='pass-h1-valid-role' role='none'></h1> @@ -46,4 +52,6 @@ <h1 id='pass-h1-valid-role' role='none'></h1> <input type="image" id="fail-input-image-invalid-role" role='doc-afterword'> <button id="fail-button-role-cell" role='cell'></button> <aside id='fail-aside-doc-foreword' role='doc-foreword'></aside> -<aside id="fail-aside-role-tab" role='tab'></aside> \ No newline at end of file +<aside id="fail-aside-role-tab" role='tab'></aside> +<button id='fail-button-role-gridcell' role="gridcell" title="IconCheckmark" aria-label="IconCheckmark icon"></button> +<input id='fail-input-role-gridcell-multiple-role' role="gridcell combobox"> diff --git a/test/integration/rules/aria-allowed-role/aria-allowed-role.json b/test/integration/rules/aria-allowed-role/aria-allowed-role.json index a18890b583..a92c112926 100644 --- a/test/integration/rules/aria-allowed-role/aria-allowed-role.json +++ b/test/integration/rules/aria-allowed-role/aria-allowed-role.json @@ -11,7 +11,6 @@ ["#pass-section-role-doc-bib"], ["#pass-li-role-doc-biblioentry"], ["#pass-aside-doc-example"], - ["#pass-div-has-any-role"], ["#pass-div-valid-role"], ["#pass-ol-valid-role"], ["#pass-nav-role-doc-index"], @@ -31,6 +30,13 @@ ["#pass-header-valid-role"], ["#pass-footer-valid-role"], ["#pass-embed-valid-role"], + ["#pass-div-has-any-role"], + ["#pass-input-text-redundant-role"], + ["#pass-input-multiple-roles"], + ["#pass-input-multiple-valid-and-invalid-roles"], + ["#pass-input-text-role-searchbox"], + ["#pass-input-text-role-combobox"], + ["#pass-input-text-role-spinbutton"], ["#pass-input-image-valid-role"], ["#pass-input-checkbox-valid-role"], ["#pass-h1-valid-role"], @@ -52,6 +58,8 @@ ["#fail-input-image-invalid-role"], ["#fail-button-role-cell"], ["#fail-aside-doc-foreword"], - ["#fail-aside-role-tab"] + ["#fail-aside-role-tab"], + ["#fail-button-role-gridcell"], + ["#fail-input-role-gridcell-multiple-role"] ] } \ No newline at end of file diff --git a/test/integration/rules/autocomplete-valid/autocomplete-valid.html b/test/integration/rules/autocomplete-valid/autocomplete-valid.html index ef58c8a23b..3168a36246 100644 --- a/test/integration/rules/autocomplete-valid/autocomplete-valid.html +++ b/test/integration/rules/autocomplete-valid/autocomplete-valid.html @@ -115,3 +115,7 @@ <input autocomplete="SECTION-foo mobile tel-extension" id="pass61" /> <input autocomplete="fax email" id="pass62" /> <input autocomplete="pager impp" id="pass63" /> +<input autocomplete="off" id="pass64" name="input_1" placeholder="Numeric Input Field" value="42" type="tel"> +<input autocomplete="on" id="pass65" value="" type="url"> +<input autocomplete="off" id="pass66" value="42" type="datetime-local"> + diff --git a/test/integration/rules/autocomplete-valid/autocomplete-valid.json b/test/integration/rules/autocomplete-valid/autocomplete-valid.json index 99ef0ae81a..7f3867a27b 100644 --- a/test/integration/rules/autocomplete-valid/autocomplete-valid.json +++ b/test/integration/rules/autocomplete-valid/autocomplete-valid.json @@ -71,6 +71,9 @@ ["#pass60"], ["#pass61"], ["#pass62"], - ["#pass63"] + ["#pass63"], + ["#pass64"], + ["#pass65"], + ["#pass66"] ] } diff --git a/test/rule-matches/aria-allowed-role-matches.js b/test/rule-matches/aria-allowed-role-matches.js new file mode 100644 index 0000000000..cb8d383122 --- /dev/null +++ b/test/rule-matches/aria-allowed-role-matches.js @@ -0,0 +1,45 @@ +describe('aria-allowed-role-matches', function() { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var rule; + + beforeEach(function() { + rule = axe._audit.rules.find(function(rule) { + return rule.id === 'aria-allowed-role'; + }); + }); + + afterEach(function() { + fixture.innerHTML = ''; + }); + + it('is a function', function() { + assert.isFunction(rule.matches); + }); + + it('return false (no matches) for a <link> with a href to have any invalid role', function() { + var node = document.createElement('link'); + node.setAttribute('role', 'invalid-role'); + node.href = '\\example.com'; + fixture.appendChild(node); + assert.isFalse(rule.matches(node)); + }); + + it('return true for input with redundant role', function() { + var node = document.createElement('input'); + node.setAttribute('type', 'text'); + node.setAttribute('role', 'textbox'); + node.href = '\\example.com'; + fixture.appendChild(node); + assert.isTrue(rule.matches(node)); + }); + + it('return true for element with valid role', function() { + var node = document.createElement('ol'); + node.setAttribute('role', 'listbox'); + node.href = '\\example.com'; + fixture.appendChild(node); + assert.isTrue(rule.matches(node)); + }); +});