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": "ドキュメントにはナビゲーションを補助するために要素がなければなりません"
},
+ "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