diff --git a/.gitignore b/.gitignore index 0681de529b..9168d18e45 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ typings/axe-core/axe-core-tests.js # doc doc/rule-descriptions.*.md +.history \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index e11bcfa238..337f47f05a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -242,7 +242,10 @@ module.exports = function(grunt) { tasks: ['build', 'notify'] }, tests: { - files: ['test/**/*.js', 'test/integration/**/!(index).{html,json}'], + files: [ + 'test/**/*.js', + 'test/integration/**/!(index).{xhtml,html,json}' + ], tasks: ['clean:tests', 'testconfig', 'fixture'] } }, diff --git a/bower.json b/bower.json index ac86ac3ded..f5cd787461 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.1.4", + "version": "4.1.4-canary.3", "contributors": [ { "name": "David Sturley", diff --git a/build/configure.js b/build/configure.js index ed69c17d9a..2cc081afb1 100644 --- a/build/configure.js +++ b/build/configure.js @@ -10,10 +10,8 @@ var entities = new (require('html-entities').AllHtmlEntities)(); var packageJSON = require('../package.json'); var dotRegex = /\{\{.+?\}\}/g; -var axeVersion = packageJSON.version.substring( - 0, - packageJSON.version.lastIndexOf('.') -); +var _v = packageJSON.version.replace(/-\w+\.\w+$/, ''); +var axeVersion = _v.substring(0, _v.lastIndexOf('.')); var descriptionTableHeader = '| Rule ID | Description | Impact | Tags | Issue Type |\n| :------- | :------- | :------- | :------- | :------- |\n'; diff --git a/build/tasks/testconfig.js b/build/tasks/testconfig.js index 9834cd1a0c..08d24a6f0d 100644 --- a/build/tasks/testconfig.js +++ b/build/tasks/testconfig.js @@ -14,7 +14,13 @@ module.exports = function(grunt) { this.files.forEach(function(f) { f.src.forEach(function(filepath) { var config = grunt.file.readJSON(filepath); - config.content = grunt.file.read(filepath.replace(/json$/, 'html')); + try { + config.content = grunt.file.read(filepath.replace(/json$/, 'html')); + } catch (e) { + config.content = grunt.file.read( + filepath.replace(/json$/, 'xhtml') + ); + } result.tests[config.rule] = result.tests[config.rule] || []; result.tests[config.rule].push(config); }); diff --git a/build/tasks/update-help.js b/build/tasks/update-help.js index d418867c7f..79d9fc3790 100644 --- a/build/tasks/update-help.js +++ b/build/tasks/update-help.js @@ -9,7 +9,7 @@ module.exports = function(grunt) { var options = this.options({ version: '1.0.0' }); - var v = options.version.split('.'); + var v = options.version.replace(/-\w+\.\w+$/, '').split('.'); v.pop(); var baseUrl = 'https://dequeuniversity.com/rules/axe/' + v.join('.') + '/'; diff --git a/build/test/config.js b/build/test/config.js index 54bf24ba85..eac1e4db9c 100644 --- a/build/test/config.js +++ b/build/test/config.js @@ -46,6 +46,7 @@ exports = module.exports = function(grunt, options) { log: true, urls: mapToUrl( [ + 'test/integration/full/**/*__.xhtml', 'test/integration/full/**/*.html', '!test/integration/full/**/frames/**/*.html' ], diff --git a/build/test/get-test-urls.js b/build/test/get-test-urls.js index d267895db8..6fb3649c84 100644 --- a/build/test/get-test-urls.js +++ b/build/test/get-test-urls.js @@ -27,6 +27,15 @@ const getTestUrls = async (host = `localhost`, port = `9876`) => { */ ...( await globby([ + // 'test/integration/full/landmark-one-main/**/*.html', + // '!test/integration/full/landmark-one-main/**/frames/**/*.html', + // 'test/integration/full/landmark-no-duplicate-main/**/*.html', + // '!test/integration/full/landmark-no-duplicate-main/**/frames/**/*.html' + + // 'test/integration/full/epub-type-has-matching-role/**/*__.xhtml', + // 'test/integration/full/pagebreak-label/**/*__.xhtml', + + 'test/integration/full/**/*__.xhtml', 'test/integration/full/**/*.html', '!test/integration/full/**/frames/**/*.html' ]) diff --git a/doc/examples/qunit/Gruntfile.js b/doc/examples/qunit/Gruntfile.js index b08c53082d..3f71195903 100644 --- a/doc/examples/qunit/Gruntfile.js +++ b/doc/examples/qunit/Gruntfile.js @@ -5,7 +5,7 @@ module.exports = function(grunt) { grunt.initConfig({ qunit: { - all: ['test/**/*.html'] + all: ['test/**/*.html', 'test/**/*__.xhtml'] } }); }; diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 4f143435d6..00e2f8209c 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -71,6 +71,7 @@ | :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :------ | :-------------------------------- | :--------- | | [autocomplete-valid](https://dequeuniversity.com/rules/axe/4.1/autocomplete-valid?application=RuleDescription) | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135 | failure | | [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/4.1/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | cat.structure, wcag21aa, wcag1412 | failure | +| [pagebreak-label](https://dequeuniversity.com/rules/axe/4.1/pagebreak-label?application=RuleDescription) | Ensure page markers have an accessible label | Serious | cat.epub | failure | ## Best Practices Rules @@ -83,6 +84,7 @@ Rules that do not necessarily conform to WCAG success criterion but are industry | [aria-dialog-name](https://dequeuniversity.com/rules/axe/4.1/aria-dialog-name?application=RuleDescription) | Ensures every ARIA dialog and alertdialog node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | [aria-treeitem-name](https://dequeuniversity.com/rules/axe/4.1/aria-treeitem-name?application=RuleDescription) | Ensures every ARIA treeitem node has an accessible name | Serious | cat.aria, best-practice | failure, needs review | | [empty-heading](https://dequeuniversity.com/rules/axe/4.1/empty-heading?application=RuleDescription) | Ensures headings have discernible text | Minor | cat.name-role-value, best-practice | failure, needs review | +| [epub-type-has-matching-role](https://dequeuniversity.com/rules/axe/4.1/epub-type-has-matching-role?application=RuleDescription) | Ensure the element has an ARIA role matching its epub:type | Minor | best-practice, cat.aria | failure | | [frame-tested](https://dequeuniversity.com/rules/axe/4.1/frame-tested?application=RuleDescription) | Ensures <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, review-item, best-practice | failure, needs review | | [frame-title-unique](https://dequeuniversity.com/rules/axe/4.1/frame-title-unique?application=RuleDescription) | Ensures <iframe> and <frame> elements contain a unique title attribute | Serious | cat.text-alternatives, best-practice | failure | | [heading-order](https://dequeuniversity.com/rules/axe/4.1/heading-order?application=RuleDescription) | Ensures the order of headings is semantically correct | Moderate | cat.semantics, best-practice | failure | @@ -96,7 +98,7 @@ Rules that do not necessarily conform to WCAG success criterion but are industry | [landmark-no-duplicate-banner](https://dequeuniversity.com/rules/axe/4.1/landmark-no-duplicate-banner?application=RuleDescription) | Ensures the document has at most one banner landmark | Moderate | cat.semantics, best-practice | failure | | [landmark-no-duplicate-contentinfo](https://dequeuniversity.com/rules/axe/4.1/landmark-no-duplicate-contentinfo?application=RuleDescription) | Ensures the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | failure | | [landmark-no-duplicate-main](https://dequeuniversity.com/rules/axe/4.1/landmark-no-duplicate-main?application=RuleDescription) | Ensures the document has at most one main landmark | Moderate | cat.semantics, best-practice | failure | -| [landmark-one-main](https://dequeuniversity.com/rules/axe/4.1/landmark-one-main?application=RuleDescription) | Ensures the document has a main landmark | Moderate | cat.semantics, best-practice | failure | +| [landmark-one-main](https://dequeuniversity.com/rules/axe/4.1/landmark-one-main?application=RuleDescription) | Ensures the document has a unique main landmark | Moderate | cat.semantics, best-practice | failure | | [landmark-unique](https://dequeuniversity.com/rules/axe/4.1/landmark-unique?application=RuleDescription) | Landmarks must have a unique role or role/label/title (i.e. accessible name) combination | Moderate | cat.semantics, best-practice | failure | | [meta-viewport-large](https://dequeuniversity.com/rules/axe/4.1/meta-viewport-large?application=RuleDescription) | Ensures <meta name="viewport"> can scale a significant amount | Minor | cat.sensory-and-visual-cues, best-practice | failure | | [meta-viewport](https://dequeuniversity.com/rules/axe/4.1/meta-viewport?application=RuleDescription) | Ensures <meta name="viewport"> does not disable text scaling and zooming | Critical | cat.sensory-and-visual-cues, best-practice, ACT | failure | diff --git a/lib/checks/aria/aria-required-children-evaluate.js b/lib/checks/aria/aria-required-children-evaluate.js index 7b225d1a26..063fd80887 100644 --- a/lib/checks/aria/aria-required-children-evaluate.js +++ b/lib/checks/aria/aria-required-children-evaluate.js @@ -10,16 +10,29 @@ import { hasContentVirtual, idrefs } from '../../commons/dom'; * Get all owned roles of an element */ function getOwnedRoles(virtualNode) { + // DAISY-AXE + const parentRole = getRole(virtualNode, { dpub: true }); + const ownedRoles = []; const ownedElements = getOwnedVirtual(virtualNode); for (let i = 0; i < ownedElements.length; i++) { let ownedElement = ownedElements[i]; - let role = getRole(ownedElement); + + // DAISY-AXE + let role = getRole(ownedElement, { dpub: true }); + // let role = getRole(ownedElement); // if owned node has no role or is presentational we keep // parsing the descendant tree. this means intermediate roles // between a required parent and child will fail the check - if (['presentation', 'none', null].includes(role)) { + + // DAISY-AXE + if ( + ['presentation', 'none', null].includes(role) || + (['list'].includes(role) && + ['doc-bibliography', 'doc-endnotes'].includes(parentRole)) + ) { + // if (['presentation', 'none', null].includes(role)) { ownedElements.push(...ownedElement.children); } else if (role) { ownedRoles.push(role); diff --git a/lib/checks/aria/matching-aria-role-evaluate.js b/lib/checks/aria/matching-aria-role-evaluate.js new file mode 100644 index 0000000000..65963155f1 --- /dev/null +++ b/lib/checks/aria/matching-aria-role-evaluate.js @@ -0,0 +1,185 @@ +import { tokenList } from '../../core/utils'; +import { getRole } from '../../commons/aria'; +import matchesSelector from '../../core/utils/element-matches'; + +function matchingAriaRoleEvaluate(node) { + // https://idpf.github.io/epub-guides/epub-aria-authoring/#sec-mappings + // https://www.w3.org/TR/dpub-aam-1.0/#mapping_role_table + // https://w3c.github.io/publ-cg/guides/aria-mapping.html#mapping-table + const mappings = new Map([ + ['abstract', 'doc-abstract'], + ['acknowledgments', 'doc-acknowledgments'], + ['afterword', 'doc-afterword'], + // ['answer', '??'], + // ['answers', '??'], + ['appendix', 'doc-appendix'], + // ['assessment', '??'], + // ['assessments', '??'], + // ['backmatter', '??'], + // ['balloon', '??'], + // ['backlink', 'doc-backlink'], // ?? + ['biblioentry', 'doc-biblioentry'], + ['bibliography', 'doc-bibliography'], + ['biblioref', 'doc-biblioref'], + // ['bodymatter', '??'], + // ['bridgehead', '??'], + // ['case-study', '??'], + ['chapter', 'doc-chapter'], + ['colophon', 'doc-colophon'], + // ['concluding-sentence', '??'], + ['conclusion', 'doc-conclusion'], + // ['contributors', '??'], + // ['copyright-page', '??'], + // ['cover', '??'], + // ['cover-image', 'doc-cover'], // ?? + // ['covertitle', '??'], + ['credit', 'doc-credit'], + ['credits', 'doc-credits'], + ['dedication', 'doc-dedication'], + // ['division', '??'], + ['endnote', 'doc-endnote'], + ['endnotes', 'doc-endnotes'], + ['epigraph', 'doc-epigraph'], + ['epilogue', 'doc-epilogue'], + ['errata', 'doc-errata'], + // ['example', 'doc-example'], + // ['feedback', '??'], + ['figure', 'figure'], // ARIA + // ['fill-in-the-blank-problem', '??'], + ['footnote', 'doc-footnote'], + // ['footnotes', '??'], + ['foreword', 'doc-foreword'], + // ['frontmatter', '??'], + // ['fulltitle', '??'], + // ['general-problem', '??'], + ['glossary', 'doc-glossary'], + ['glossdef', 'definition'], // ARIA + ['glossref', 'doc-glossref'], + ['glossterm', 'term'], // ARIA + // ['halftitle', '??'], + // ['halftitlepage', '??'], + // ['imprimatur', '??'], + // ['imprint', '??'], + ['help', 'doc-tip'], // ?? + ['index', 'doc-index'], + // ['index-editor-note', '??'], + // ['index-entry', '??'], + // ['index-entry-list', '??'], + // ['index-group', '??'], + // ['index-headnotes', '??'], + // ['index-legend', '??'], + // ['index-locator', '??'], + // ['index-locator-list', '??'], + // ['index-locator-range', '??'], + // ['index-term', '??'], + // ['index-term-categories', '??'], + // ['index-term-category', '??'], + // ['index-xref-preferred', '??'], + // ['index-xref-related', '??'], + ['introduction', 'doc-introduction'], + // ['keyword', '??'], + // ['keywords', '??'], + // ['label', '??'], + // ['landmarks', 'directory'], // ARIA (SKIPPED! NavDoc) + // ['learning-objective', '??'], + // ['learning-objectives', '??'], + // ['learning-outcome', '??'], + // ['learning-outcomes', '??'], + // ['learning-resource', '??'], + // ['learning-resources', '??'], + // ['learning-standard', '??'], + // ['learning-standards', '??'], + ['list', 'list'], // ARIA + ['list-item', 'listitem'], // ARIA + // ['loa', '??'], + // ['loi', '??'], + // ['lot', '??'], + // ['lov', '??'], + // ['match-problem', '??'], + // ['multiple-choice-problem', '??'], + ['noteref', 'doc-noteref'], + ['notice', 'doc-notice'], + // ['ordinal', '??'], + // ['other-credits', '??'], + ['page-list', 'doc-pagelist'], + ['pagebreak', 'doc-pagebreak'], + // ['panel', '??'], + // ['panel-group', '??'], + ['part', 'doc-part'], + // ['practice', '??'], + // ['practices', '??'], + // ['preamble', '??'], + ['preface', 'doc-preface'], + ['prologue', 'doc-prologue'], + ['pullquote', 'doc-pullquote'], + ['qna', 'doc-qna'], + // ['question', '??'], + ['referrer', 'doc-backlink'], + // ['revision-history', '??'], + // ['seriespage', '??'], + // ['sound-area', '??'], + // ['subchapter', '??'], + ['subtitle', 'doc-subtitle'], + ['table', 'table'], + ['table-cell', 'cell'], + ['table-row', 'row'], + // ['text-area', '??'], + ['tip', 'doc-tip'], + // ['title', '??'], + // ['titlepage', '??'], + ['toc', 'doc-toc'] + // ['toc-brief', '??'], + // ['topic-sentence', '??'], + // ['true-false-problem', '??'], + // ['volume', '??'], + ]); + + const hasXmlEpubType = node.hasAttributeNS( + 'http://www.idpf.org/2007/ops', + 'type' + ); + if ( + hasXmlEpubType || + node.hasAttribute('epub:type') // for unit tests that are not XML-aware due to fixture.innerHTML + ) { + // abort if descendant of landmarks nav (nav with epub:type=landmarks) + if ( + (hasXmlEpubType && matchesSelector(node, 'nav[*|type~="landmarks"] *')) || + matchesSelector(node, 'nav[epub\\:type~="landmarks"] *') + ) { + // console.log('BREAKPOINT'); + // throw new Error('BREAKPOINT'); + return true; + } + + // iterate for each epub:type value + var types = tokenList( + hasXmlEpubType + ? node.getAttributeNS('http://www.idpf.org/2007/ops', 'type') + : node.getAttribute('epub:type') + ); + for (const type of types) { + // If there is a 1-1 mapping, check that the role is set (best practice) + if (mappings.has(type)) { + // Note: using axe’s `getRole` util returns the effective role of the element + // (either explicitly set with the role attribute or implicit) + // So this works for types mapping to core ARIA roles (eg. glossref/glossterm). + const mappedRole = mappings.get(type); + const role = getRole(node, { dpub: true }); + // if (mappedRole !== role) { + // console.log('BREAKPOINT: ', type, mappedRole, role); + // // throw new Error('BREAKPOINT'); + // } + return mappedRole === role; + } else { + // e.g. cover, landmarks + // console.log('BREAKPOINT: ', type); + // throw new Error('BREAKPOINT'); + } + } + } + + return true; +} + +export default matchingAriaRoleEvaluate; diff --git a/lib/checks/aria/matching-aria-role.json b/lib/checks/aria/matching-aria-role.json new file mode 100644 index 0000000000..b8996c7e10 --- /dev/null +++ b/lib/checks/aria/matching-aria-role.json @@ -0,0 +1,11 @@ +{ + "id": "matching-aria-role", + "evaluate": "matching-aria-role-evaluate", + "metadata": { + "impact": "minor", + "messages": { + "pass": "Element has an ARIA role matching its epub:type", + "fail": "Element has no ARIA role matching its epub:type" + } + } +} diff --git a/lib/checks/landmarks/landmark-is-unique-after.js b/lib/checks/landmarks/landmark-is-unique-after.js index 5ccceac3aa..0cc943fca3 100644 --- a/lib/checks/landmarks/landmark-is-unique-after.js +++ b/lib/checks/landmarks/landmark-is-unique-after.js @@ -1,11 +1,20 @@ function landmarkIsUniqueAfter(results) { + // console.log("landmarkIsUniqueAfter results: ", JSON.stringify(results, null, 4)); + var uniqueLandmarks = []; // filter out landmark elements that share the same role and accessible text // so every non-unique landmark isn't reported as a failure (just the first) - return results.filter(currentResult => { + var filtered = results.filter(currentResult => { + if (!currentResult.data) { + // console.log('landmarkIsUniqueAfterlandmarkIsUniqueAfter NO DATA???!!!'); + return false; + } + var findMatch = someResult => { return ( + // currentResult.data.isLandmark && + // someResult.data.isLandmark && currentResult.data.role === someResult.data.role && currentResult.data.accessibleText === someResult.data.accessibleText ); @@ -22,6 +31,9 @@ function landmarkIsUniqueAfter(results) { currentResult.relatedNodes = []; return true; }); + + // console.log("landmarkIsUniqueAfter filtered: ", JSON.stringify(filtered, null, 4)); + return filtered; } export default landmarkIsUniqueAfter; diff --git a/lib/checks/landmarks/landmark-is-unique-evaluate.js b/lib/checks/landmarks/landmark-is-unique-evaluate.js index 83aa04c77e..8f543a180e 100644 --- a/lib/checks/landmarks/landmark-is-unique-evaluate.js +++ b/lib/checks/landmarks/landmark-is-unique-evaluate.js @@ -1,11 +1,38 @@ -import { getRole } from '../../commons/aria'; +import { getRole } from '../../commons/aria'; // getRoleType import { accessibleTextVirtual } from '../../commons/text'; +// import { getAriaRolesByType } from '../../commons/standards'; function landmarkIsUniqueEvaluate(node, options, virtualNode) { - var role = getRole(node); + var role = getRole(node, { dpub: true }); // fallback: true + if (!role) { + // this.data({ role: '', accessibleText: '', isLandmark: null }); + // console.log('landmarkIsUniqueEvaluate landmarkIsUniqueEvaluate landmarkIsUniqueEvaluate NO ROLE???!!!'); + return false; + } + + // var landmarks = getAriaRolesByType('landmark'); + // var roleType = getRoleType(role); + // var isLandmark = + // roleType === 'landmark' || + // landmarks.includes(roleType) || + // landmarks.includes(role); + + // if (!isLandmark) { + // // this.data({ role: '', accessibleText: '', isLandmark: null }); + // return false; + // } + // throw new Error('BREAK'); + var accessibleText = accessibleTextVirtual(virtualNode); + + // console.log('\n\n ))))) ', virtualNode.props ? virtualNode.props.nodeName : '!virtualNode.props', role, roleType, JSON.stringify(landmarks), isLandmark, " [[" + accessibleText + "]]") + accessibleText = accessibleText ? accessibleText.toLowerCase() : null; - this.data({ role: role, accessibleText: accessibleText }); + this.data({ + role: role, + accessibleText: accessibleText + // isLandmark: isLandmark + }); this.relatedNodes([node]); return true; diff --git a/lib/checks/lists/listitem-evaluate.js b/lib/checks/lists/listitem-evaluate.js index aebcbd8cc9..776a0c1631 100644 --- a/lib/checks/lists/listitem-evaluate.js +++ b/lib/checks/lists/listitem-evaluate.js @@ -1,5 +1,8 @@ import { getComposedParent } from '../../commons/dom'; -import { isValidRole } from '../../commons/aria'; + +// DAISY-AXE +import { getSuperClassRole, isValidRole } from '../../commons/aria'; +// import { isValidRole } from '../../commons/aria'; function listitemEvaluate(node) { const parent = getComposedParent(node); @@ -16,6 +19,12 @@ function listitemEvaluate(node) { } if (parentRole && isValidRole(parentRole)) { + // DAISY-AXE + const sup = getSuperClassRole(parentRole); + if (sup && sup.includes('list')) { + return true; + } + this.data({ messageKey: 'roleNotValid' }); diff --git a/lib/checks/lists/only-listitems-evaluate.js b/lib/checks/lists/only-listitems-evaluate.js index d1ab863428..a06ac30c23 100644 --- a/lib/checks/lists/only-listitems-evaluate.js +++ b/lib/checks/lists/only-listitems-evaluate.js @@ -1,5 +1,8 @@ import { isVisible } from '../../commons/dom'; -import { getRole } from '../../commons/aria'; + +// DAISY-AXE +import { getRole, getSuperClassRole } from '../../commons/aria'; +// import { getRole } from '../../commons/aria'; function onlyListitemsEvaluate(node, options, virtualNode) { let hasNonEmptyTextNode = false; @@ -24,7 +27,12 @@ function onlyListitemsEvaluate(node, options, virtualNode) { isEmpty = false; const isLi = actualNode.nodeName.toUpperCase() === 'LI'; const role = getRole(vNode); - const isListItemRole = role === 'listitem'; + + // DAISY-AXE + const sup = getSuperClassRole(role); + const isListItemRole = + role === 'listitem' || (sup && sup.includes('listitem')); + // const isListItemRole = role === 'listitem'; if (!isLi && !isListItemRole) { badNodes.push(actualNode); diff --git a/lib/commons/aria/get-element-unallowed-roles.js b/lib/commons/aria/get-element-unallowed-roles.js index d76b6767bf..ba3e58419d 100644 --- a/lib/commons/aria/get-element-unallowed-roles.js +++ b/lib/commons/aria/get-element-unallowed-roles.js @@ -8,10 +8,10 @@ import { tokenList, isHtmlElement, matchesSelector } from '../../core/utils'; // HTML elements (img, link, etc.) const dpubRoles = [ 'doc-backlink', - 'doc-biblioentry', + // 'doc-biblioentry', 'doc-biblioref', 'doc-cover', - 'doc-endnote', + // 'doc-endnote', 'doc-glossref', 'doc-noteref' ]; @@ -37,13 +37,14 @@ function getRoleSegments(node) { roles = roles.concat(nodeRoles); } - if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) { - const epubRoles = tokenList( - node.getAttributeNS('http://www.idpf.org/2007/ops', 'type').toLowerCase() - ).map(role => `doc-${role}`); + // DAISY-AXE (EPUB epub:type should be ignored) + // if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) { + // const epubRoles = tokenList( + // node.getAttributeNS('http://www.idpf.org/2007/ops', 'type').toLowerCase() + // ).map(role => `doc-${role}`); - roles = roles.concat(epubRoles); - } + // roles = roles.concat(epubRoles); + // } // filter invalid roles roles = roles.filter(role => isValidRole(role)); diff --git a/lib/commons/aria/get-super-class-role.js b/lib/commons/aria/get-super-class-role.js new file mode 100644 index 0000000000..cc753cb64d --- /dev/null +++ b/lib/commons/aria/get-super-class-role.js @@ -0,0 +1,21 @@ +import standards from '../../standards'; + +/** + * Get the "superclassRole" of role + * @method getSuperClassRole + * @memberof axe.commons.aria + * @instance + * @param {String} role The role to check + * @return {Mixed} String if a matching role and its superclassRole are found, otherwise `null` + */ +function getSuperClassRole(role) { + const roleDef = standards.ariaRoles[role]; + + if (!roleDef) { + return null; + } + + return roleDef.superclassRole; +} + +export default getSuperClassRole; diff --git a/lib/commons/aria/index.js b/lib/commons/aria/index.js index 14de4ebc0c..d249a29111 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -11,6 +11,7 @@ export { default as getElementUnallowedRoles } from './get-element-unallowed-rol export { default as getExplicitRole } from './get-explicit-role'; export { default as getOwnedVirtual } from './get-owned-virtual'; export { default as getRoleType } from './get-role-type'; +export { default as getSuperClassRole } from './get-super-class-role'; export { default as getRole } from './get-role'; export { default as getRolesByType } from './get-roles-by-type'; export { default as getRolesWithNameFromContents } from './get-roles-with-name-from-contents'; diff --git a/lib/commons/aria/lookup-table.js b/lib/commons/aria/lookup-table.js index a198feee71..6da5bcd5aa 100644 --- a/lib/commons/aria/lookup-table.js +++ b/lib/commons/aria/lookup-table.js @@ -489,7 +489,7 @@ lookupTable.role = { owned: null, nameFrom: ['author'], context: null, - implicit: ['dd', 'dfn'], + implicit: ['dd'], // DAISY-AXE: remove 'dfn' which has implicit 'term' role, see https://www.w3.org/TR/html-aria/#docconformance unsupported: false }, dialog: { @@ -590,7 +590,7 @@ lookupTable.role = { ] }, 'doc-biblioentry': { - type: 'listitem', + type: 'structure', attributes: { allowed: [ 'aria-expanded', @@ -611,9 +611,10 @@ lookupTable.role = { attributes: { allowed: ['aria-expanded', 'aria-errormessage'] }, - owned: { - one: ['doc-biblioentry'] - }, + owned: null, + // owned: { + // one: ['doc-biblioentry'] + // }, nameFrom: ['author'], context: null, unsupported: false, @@ -714,7 +715,7 @@ lookupTable.role = { allowedElements: ['section'] }, 'doc-endnote': { - type: 'listitem', + type: 'structure', attributes: { allowed: [ 'aria-expanded', @@ -735,9 +736,10 @@ lookupTable.role = { attributes: { allowed: ['aria-expanded', 'aria-errormessage'] }, - owned: { - one: ['doc-endnote'] - }, + owned: null, + // owned: { + // one: ['doc-endnote'] + // }, namefrom: ['author'], context: null, unsupported: false, @@ -776,7 +778,7 @@ lookupTable.role = { allowedElements: ['section'] }, 'doc-example': { - type: 'section', + type: 'structure', attributes: { allowed: ['aria-expanded', 'aria-errormessage'] }, @@ -813,7 +815,6 @@ lookupTable.role = { attributes: { allowed: ['aria-expanded', 'aria-errormessage'] }, - owned: ['term', 'definition'], namefrom: ['author'], context: null, unsupported: false, @@ -895,6 +896,7 @@ lookupTable.role = { }, owned: null, namefrom: ['author'], + nameFromContent: true, context: null, unsupported: false, allowedElements: ['hr'] @@ -944,7 +946,7 @@ lookupTable.role = { allowedElements: ['section'] }, 'doc-pullquote': { - type: 'none', + type: 'section', attributes: { allowed: ['aria-expanded'] }, @@ -972,6 +974,7 @@ lookupTable.role = { }, owned: null, namefrom: ['author'], + nameFromContent: true, context: null, unsupported: false, allowedElements: { @@ -1163,7 +1166,7 @@ lookupTable.role = { }, nameFrom: ['author'], context: null, - implicit: ['ol', 'ul', 'dl'], + implicit: ['ol', 'ul'], // DAISY-AXE: remove 'dl' which has no implicit role, see https://www.w3.org/TR/html-aria/#docconformance unsupported: false }, listbox: { @@ -1202,7 +1205,7 @@ lookupTable.role = { owned: null, nameFrom: ['author', 'contents'], context: ['list'], - implicit: ['li', 'dt'], + implicit: ['li'], // DAISY-AXE: remove 'dt' which has implicit 'term' role, see https://www.w3.org/TR/html-aria/#docconformance unsupported: false }, log: { @@ -1944,7 +1947,7 @@ lookupTable.role = { owned: null, nameFrom: ['author', 'contents'], context: null, - implicit: ['dt'], + implicit: ['dt', 'dfn'], // DAISY-AXE: add 'dfn' which has implicit 'term' role, see https://www.w3.org/TR/html-aria/#docconformance unsupported: false }, textbox: { diff --git a/lib/core/base/audit.js b/lib/core/base/audit.js index c790dc0b60..ff7dcba23f 100644 --- a/lib/core/base/audit.js +++ b/lib/core/base/audit.js @@ -710,11 +710,12 @@ function getDefferedRule(rule, context, options) { * For all the rules, create the helpUrl and add it to the data for that rule */ function getHelpUrl({ brand, application, lang }, ruleId, version) { + var _v = version ? version : axe.version.replace(/-\w+\.\w+$/, ''); return ( constants.helpUrlBase + brand + '/' + - (version || axe.version.substring(0, axe.version.lastIndexOf('.'))) + + (version || _v.substring(0, _v.lastIndexOf('.'))) + '/' + ruleId + '?application=' + diff --git a/lib/core/base/metadata-function-map.js b/lib/core/base/metadata-function-map.js index c197b0d776..69762cbc08 100644 --- a/lib/core/base/metadata-function-map.js +++ b/lib/core/base/metadata-function-map.js @@ -164,6 +164,10 @@ import svgNamespaceMatches from '../../rules/svg-namespace-matches'; import windowIsTopMatches from '../../rules/window-is-top-matches'; import xmlLangMismatchMatches from '../../rules/xml-lang-mismatch-matches'; +import epubTypeHasMatchingRoleMatches from '../../rules/epub-type-has-matching-role-matches'; +import pagebreakLabelMatches from '../../rules/pagebreak-label-matches'; +import matchingAriaRoleEvaluate from '../../checks/aria/matching-aria-role-evaluate'; + const metadataFunctionMap = { // aria 'abstractrole-evaluate': abstractroleEvaluate, @@ -331,7 +335,11 @@ const metadataFunctionMap = { 'skip-link-matches': skipLinkMatches, 'svg-namespace-matches': svgNamespaceMatches, 'window-is-top-matches': windowIsTopMatches, - 'xml-lang-mismatch-matches': xmlLangMismatchMatches + 'xml-lang-mismatch-matches': xmlLangMismatchMatches, + + 'epub-type-has-matching-role-matches': epubTypeHasMatchingRoleMatches, + 'pagebreak-label-matches': pagebreakLabelMatches, + 'matching-aria-role-evaluate': matchingAriaRoleEvaluate }; export default metadataFunctionMap; diff --git a/lib/core/public/configure.js b/lib/core/public/configure.js index be3eb58c28..03a2209c9f 100644 --- a/lib/core/public/configure.js +++ b/lib/core/public/configure.js @@ -1,7 +1,13 @@ import { hasReporter } from './reporter'; import { configureStandards } from '../../standards'; +// import matchesSelector from '../../core/utils/element-matches'; +// import { tokenList } from '../../core/utils'; +// import { getRole } from '../../commons/aria'; + function configure(spec) { + // throw new Error("DAISY ACE BREAKPOINT AXE CONFIGURE"); + var audit; audit = axe._audit; diff --git a/lib/rules/epub-type-has-matching-role-matches.js b/lib/rules/epub-type-has-matching-role-matches.js new file mode 100644 index 0000000000..f4ceca0ba0 --- /dev/null +++ b/lib/rules/epub-type-has-matching-role-matches.js @@ -0,0 +1,19 @@ +function epubTypeHasMatchingRoleMatches(node) { + // selector: '[*|type]', + return ( + node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type') || + node.hasAttribute('epub:type') // for unit tests that are not XML-aware due to fixture.innerHTML + ); + + // console.log('node.nodeName: ', node.nodeName); + // const attrs = Array.from(getNodeAttributes(node)); + // console.log(attrs.length); + // attrs.forEach((attr) => { + // console.log('\n====='); + // console.log(JSON.stringify(attr)); + // console.log('attr.nodeName: ', attr.nodeName); + // console.log('attr.namespaceURI: ', attr.namespaceURI); + // }); +} + +export default epubTypeHasMatchingRoleMatches; diff --git a/lib/rules/epub-type-has-matching-role.json b/lib/rules/epub-type-has-matching-role.json new file mode 100644 index 0000000000..f49753c50b --- /dev/null +++ b/lib/rules/epub-type-has-matching-role.json @@ -0,0 +1,12 @@ +{ + "id": "epub-type-has-matching-role", + "matches": "epub-type-has-matching-role-matches", + "tags": ["best-practice", "cat.aria"], + "metadata": { + "description": "Ensure the element has an ARIA role matching its epub:type", + "help": "ARIA role should be used in addition to epub:type" + }, + "all": [], + "any": ["matching-aria-role"], + "none": [] +} diff --git a/lib/rules/landmark-one-main.json b/lib/rules/landmark-one-main.json index 533cae06e9..a1b2f29eab 100644 --- a/lib/rules/landmark-one-main.json +++ b/lib/rules/landmark-one-main.json @@ -3,10 +3,11 @@ "selector": "html", "tags": ["cat.semantics", "best-practice"], "metadata": { - "description": "Ensures the document has a main landmark", - "help": "Document must have one main landmark" + "description": "Ensures the document has a unique main landmark", + "help": "Document must have one unique main landmark" }, - "all": ["page-has-main"], + "// DAISY-AXE all": ["page-has-main", "page-no-duplicate-main"], + "all": ["page-no-duplicate-main"], "any": [], "none": [] } diff --git a/lib/rules/landmark-unique-matches.js b/lib/rules/landmark-unique-matches.js index 94e3564fb7..bfa6ccbf96 100644 --- a/lib/rules/landmark-unique-matches.js +++ b/lib/rules/landmark-unique-matches.js @@ -1,5 +1,5 @@ import { findUpVirtual, isVisible } from '../commons/dom'; -import { getRole } from '../commons/aria'; +import { getRole, getRoleType } from '../commons/aria'; import { getAriaRolesByType } from '../commons/standards'; import { accessibleTextVirtual } from '../commons/text'; @@ -26,22 +26,34 @@ function landmarkUniqueMatches(node, virtualNode) { function isLandmarkVirtual(virtualNode) { var { actualNode } = virtualNode; var landmarkRoles = getAriaRolesByType('landmark'); - var role = getRole(actualNode); + + var role = getRole(actualNode, { dpub: true }); if (!role) { return false; } + // console.log('\n\n isLandmarkVirtual 1>> ', actualNode.nodeName, role); var nodeName = actualNode.nodeName.toUpperCase(); if (nodeName === 'HEADER' || nodeName === 'FOOTER') { - return isHeaderFooterLandmark(virtualNode); + var v = isHeaderFooterLandmark(virtualNode); + // console.log('\n\n isLandmarkVirtual 2>> ', v); + return v; } if (nodeName === 'SECTION' || nodeName === 'FORM') { var accessibleText = accessibleTextVirtual(virtualNode); + // console.log('\n\n isLandmarkVirtual 3>> ', !!accessibleText); return !!accessibleText; } - return landmarkRoles.indexOf(role) >= 0 || role === 'region'; + var roleType = getRoleType(role); + // console.log('\n\n isLandmarkVirtual 4>> ', roleType); + return ( + role === 'region' || + roleType === 'landmark' || + landmarkRoles.includes(roleType) || + landmarkRoles.indexOf(role) >= 0 + ); } return isLandmarkVirtual(virtualNode) && isVisible(node, true); diff --git a/lib/rules/pagebreak-label-matches.js b/lib/rules/pagebreak-label-matches.js new file mode 100644 index 0000000000..b4ec36e195 --- /dev/null +++ b/lib/rules/pagebreak-label-matches.js @@ -0,0 +1,19 @@ +function pagebreakLabelMatches(node) { + // selector: '[*|type~="pagebreak"], [role~="doc-pagebreak"]', + return ( + (node.hasAttribute('role') && + node + .getAttribute('role') + .match(/\S+/g) + .includes('doc-pagebreak')) || + (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type') && + node + .getAttributeNS('http://www.idpf.org/2007/ops', 'type') + .match(/\S+/g) + .includes('pagebreak')) + ); + + return false; +} + +export default pagebreakLabelMatches; diff --git a/lib/rules/pagebreak-label.json b/lib/rules/pagebreak-label.json new file mode 100644 index 0000000000..1ff408ec22 --- /dev/null +++ b/lib/rules/pagebreak-label.json @@ -0,0 +1,12 @@ +{ + "id": "pagebreak-label", + "matches": "pagebreak-label-matches", + "tags": ["cat.epub"], + "metadata": { + "description": "Ensure page markers have an accessible label", + "help": "Page markers should have an accessible label" + }, + "all": [], + "any": ["aria-label", "non-empty-title"], + "none": [] +} diff --git a/lib/standards/dpub-roles.js b/lib/standards/dpub-roles.js index 4d5ff06828..0cb7070a44 100644 --- a/lib/standards/dpub-roles.js +++ b/lib/standards/dpub-roles.js @@ -27,7 +27,7 @@ const dpubRoles = { superclassRole: ['link'] }, 'doc-biblioentry': { - type: 'listitem', + type: 'structure', requiredContext: ['doc-bibliography'], allowedAttrs: [ 'aria-expanded', @@ -35,11 +35,11 @@ const dpubRoles = { 'aria-posinset', 'aria-setsize' ], - superclassRole: ['listitem'] + superclassRole: ['none'] }, 'doc-bibliography': { type: 'landmark', - requiredOwned: ['doc-biblioentry'], + // requiredOwned: ['doc-biblioentry'], allowedAttrs: ['aria-expanded'], superclassRole: ['landmark'] }, @@ -85,7 +85,7 @@ const dpubRoles = { superclassRole: ['section'] }, 'doc-endnote': { - type: 'listitem', + type: 'structure', requiredContext: ['doc-endnotes'], allowedAttrs: [ 'aria-expanded', @@ -93,11 +93,11 @@ const dpubRoles = { 'aria-posinset', 'aria-setsize' ], - superclassRole: ['listitem'] + superclassRole: ['none'] }, 'doc-endnotes': { type: 'landmark', - requiredOwned: ['doc-endnote'], + // requiredOwned: ['doc-endnote'], allowedAttrs: ['aria-expanded'], superclassRole: ['landmark'] }, @@ -117,9 +117,9 @@ const dpubRoles = { superclassRole: ['landmark'] }, 'doc-example': { - type: 'section', + type: 'structure', allowedAttrs: ['aria-expanded'], - superclassRole: ['section'] + superclassRole: ['figure'] }, 'doc-footnote': { type: 'section', @@ -133,7 +133,6 @@ const dpubRoles = { }, 'doc-glossary': { type: 'landmark', - requiredOwned: ['definition', 'term'], allowedAttrs: ['aria-expanded'], superclassRole: ['landmark'] }, @@ -190,7 +189,7 @@ const dpubRoles = { superclassRole: ['landmark'] }, 'doc-pullquote': { - type: 'none', + type: 'section', superclassRole: ['none'] }, 'doc-qna': { diff --git a/locales/da.json b/locales/da.json index 7a448b218a..93bf26c535 100644 --- a/locales/da.json +++ b/locales/da.json @@ -1,6 +1,13 @@ { "lang": "da", "rules": { + "epub-type-has-matching-role": { + "desc": "Sikrer at elementet har en ARIA rolle, som matcher 'epub:type'", + "help": "ARIA rolle skal være til stede og matche den angivne 'epub:type'" + }, + "pagebreak-label": { + "desc": "Sikrer at sidemarkører har en tilgængelig etiket ('label')" + }, "accesskeys": { "description": "", "help": "Værdien for attributten 'accesskey' skal være unik" @@ -323,6 +330,10 @@ } }, "checks": { + "matching-aria-role": { + "fail": "Elementet har ingen ARIA rolle, som matcher 'epub:type'", + "pass": "Elementet har en ARIA rolle, som matcher 'epub:type'" + }, "abstractrole": { "pass": "Abstrakte roller er ikke brugt", "fail": "Abstrakte roller bør ikke bruges" diff --git a/locales/de.json b/locales/de.json index 1fc1101019..1b694ba730 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1,6 +1,13 @@ { "lang": "de", "rules": { + "epub-type-has-matching-role": { + "desc": "Ensure the element has an ARIA role matching its epub:type", + "help": "ARIA role should be used in addition to epub:type" + }, + "pagebreak-label": { + "desc": "Ensure page markers have an accessible label" + }, "accesskeys": { "description": "", "help": "Der Wert des accesskey-Attributes muss einzigartig sein." @@ -251,6 +258,10 @@ } }, "checks": { + "matching-aria-role": { + "pass": "Element has an ARIA role matching its epub:type", + "fail": "Element has no ARIA role matching its epub:type" + }, "abstractrole": { "pass": "", "fail": "Abstrakte ARIA-Rollen dürfen nicht direkt verwendet werden." diff --git a/locales/es.json b/locales/es.json index a9ccac15c5..e4a6e27775 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,6 +1,13 @@ { "lang": "es", "rules": { + "epub-type-has-matching-role": { + "desc": "Asegurarse de que el elemento tiene un rol ARIA que corresponda a su epub:type", + "help": "Debería usarse ARIA role, además de epub:type" + }, + "pagebreak-label": { + "desc": "Garantizar que los marcadores de página tienen una etiqueta accesible" + }, "accesskeys": { "description": "Garantiza que cada valor para el atributo accesskey es único", "help": "El valor del atributo accesskey debe ser único" @@ -319,6 +326,10 @@ } }, "checks": { + "matching-aria-role": { + "fail": "El elemento no tiene un rol ARIA que corresponda a su epub:type", + "pass": "El elemento tiene un rol ARIA que corresponde a su epub:type" + }, "abstractrole": { "pass": "No se usan 'abstract roles'", "fail": "Los 'abstract roles' no se pueden usar directamente" diff --git a/locales/eu.json b/locales/eu.json index 6f56607816..e71a882a63 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,6 +1,13 @@ { "lang": "eu", "rules": { + "epub-type-has-matching-role": { + "desc": "Ensure the element has an ARIA role matching its epub:type", + "help": "ARIA role should be used in addition to epub:type" + }, + "pagebreak-label": { + "desc": "Ensure page markers have an accessible label" + }, "accesskeys": { "description": "Accesskey atributurako balio bakoitza bakarra dela bermatzen du", "help": "Accesskey atributuaren balioak bakarra izan behar du" @@ -319,6 +326,10 @@ } }, "checks": { + "matching-aria-role": { + "pass": "Element has an ARIA role matching its epub:type", + "fail": "Element has no ARIA role matching its epub:type" + }, "abstractrole": { "pass": "Ez dira 'abstract rolak' erabiltzen", "fail": "'abstract rolak 'ezin dira zuzenean erabili" diff --git a/locales/fr.json b/locales/fr.json index 1073752acd..63d116a036 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,6 +1,13 @@ { "lang": "fr", "rules": { + "epub-type-has-matching-role": { + "desc": "Vérifie qu’un élément a un rôle ARIA correspondant à son epub:type", + "help": "Un rôle ARIA devrait être spécifié en plus de l’epub:type" + }, + "pagebreak-label": { + "desc": "Vérifie que les sauts de page ont un label accessible" + }, "accesskeys": { "description": "Vérifier que chaque valeur de l’attribut accesskey est unique", "help": "La valeur de l’attribut accesskey doit être unique" @@ -327,6 +334,10 @@ } }, "checks": { + "matching-aria-role": { + "fail": "L’élément n’a pas de rôle ARIA correspondant à son epub:type", + "pass": "L’élément a un rôle ARIA correspondant à son epub:type" + }, "abstractrole": { "pass": "Les rôles abstraits ne sont pas utilisés", "fail": "Les rôles abstraits ne peuvent pas être utilisés directement" diff --git a/locales/ja.json b/locales/ja.json index b70492b99a..c41d43c445 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -1,6 +1,13 @@ { "lang": "ja", "rules": { + "epub-type-has-matching-role": { + "desc": "Ensure the element has an ARIA role matching its epub:type", + "help": "ARIA role should be used in addition to epub:type" + }, + "pagebreak-label": { + "desc": "Ensure page markers have an accessible label" + }, "accesskeys": { "description": "すべてのaccesskey属性値が一意であることを確認します", "help": "accesskey属性値は一意でなければなりません" @@ -339,6 +346,10 @@ } }, "checks": { + "matching-aria-role": { + "pass": "Element has an ARIA role matching its epub:type", + "fail": "Element has no ARIA role matching its epub:type" + }, "abstractrole": { "pass": "抽象ロールは使用されていません", "fail": { diff --git a/locales/ko.json b/locales/ko.json index ee904e5619..08c16a2fd2 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -1,6 +1,13 @@ { "lang": "ko", "rules": { + "epub-type-has-matching-role": { + "desc": "Ensure the element has an ARIA role matching its epub:type", + "help": "ARIA role should be used in addition to epub:type" + }, + "pagebreak-label": { + "desc": "Ensure page markers have an accessible label" + }, "accesskeys": { "description": "모든 accesskey 속성 값이 고유한지 확인합니다.", "help": "accesskey 속성 값은 고유해야 합니다." @@ -319,6 +326,10 @@ } }, "checks": { + "matching-aria-role": { + "pass": "Element has an ARIA role matching its epub:type", + "fail": "Element has no ARIA role matching its epub:type" + }, "abstractrole": { "pass": "추상 역할은 직접 사용하지 않습니다.", "fail": "추상 역할은 직접 사용할 수 없습니다." diff --git a/locales/nl.json b/locales/nl.json index 407ef62618..fb02c8054d 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,6 +1,10 @@ { "lang": "nl", "checks": { + "matching-aria-role": { + "pass": "Element has an ARIA role matching its epub:type", + "fail": "Element has no ARIA role matching its epub:type" + }, "abstractrole": { "pass": "Er zijn geen abstracte rollen (role) gebruikt", "fail": "Gebruik geen abstracte rollen (role)" @@ -19,6 +23,13 @@ } }, "rules": { + "epub-type-has-matching-role": { + "desc": "Ensure the element has an ARIA role matching its epub:type", + "help": "ARIA role should be used in addition to epub:type" + }, + "pagebreak-label": { + "desc": "Ensure page markers have an accessible label" + }, "aria-required-attr": { "description": "Zorg dat elementen met ARIA rollen (role) de vereiste ARIA attributen hebben", "help": "Voorzien de vereiste ARIA attributen" diff --git a/locales/pt_BR.json b/locales/pt_BR.json index 940536274c..3d965ab0c4 100644 --- a/locales/pt_BR.json +++ b/locales/pt_BR.json @@ -1,6 +1,13 @@ { "lang": "pt_BR", "rules": { + "epub-type-has-matching-role": { + "desc": "Certifique-se de que o elemento tem um ARIA 'role' correspondente ao seu epub:type", + "help": "Um ARIA 'role' deve ser usado em conjunto com o epub:type" + }, + "pagebreak-label": { + "desc": "Certifique-se de que os marcadores de páginas tenham um rótulo acessível" + }, "accesskeys": { "description": "Certifique-se de que cada valor do atributo 'acesskey' é único", "help": "O valor do atributo 'accesskey' deve ser único" @@ -339,6 +346,10 @@ } }, "checks": { + "matching-aria-role": { + "fail": "O elemento não tem um ARIA 'role' correspondente ao seu epub:type", + "pass": "O elemento tem um ARIA 'role' correspondente ao seu epub:type" + }, "abstractrole": { "pass": "As funções abstratas não são utilizadas", "fail": { diff --git a/package-lock.json b/package-lock.json index 82e3376140..f1cf2f830b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "axe-core", - "version": "4.1.4", + "name": "@daisy/axe-core-for-ace", + "version": "4.1.4-canary.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2041,9 +2041,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001096", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001096.tgz", - "integrity": "sha512-PFTw9UyVfbkcMEFs82q8XVlRayj7HKvnhu5BLcmjGpv+SNyiWasCcWXPGJuO0rK0dhLRDJmtZcJ+LHUfypbw1w==", + "version": "1.0.30001207", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001207.tgz", + "integrity": "sha512-UPQZdmAsyp2qfCTiMU/zqGSWOYaY9F9LL61V8f+8MrubsaDGpaHD9HRV/EWZGULZn0Hxu48SKzI5DgFwTvHuYw==", "dev": true }, "caseless": { diff --git a/package.json b/package.json index e78cf79464..e20cd90cd5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "axe-core", + "name": "@daisy/axe-core-for-ace", "description": "Accessibility engine for automated Web UI testing", - "version": "4.1.4", + "version": "4.1.4-canary.3", "license": "MPL-2.0", "engines": { "node": ">=4" @@ -34,10 +34,10 @@ "url": "http://deque.com/" } ], - "homepage": "https://www.deque.com/axe/", + "homepage": "https://github.com/daisy/axe-core/pull/4", "repository": { "type": "git", - "url": "https://github.com/dequelabs/axe-core.git" + "url": "https://github.com/daisy/axe-core.git" }, "keywords": [ "Accessibility", @@ -50,6 +50,15 @@ ], "main": "axe.js", "typings": "axe.d.ts", + "files": [ + "LICENSE", + "README.md", + "CHANGELOG.md", + "locales/**/*", + "axe.js", + "axe.min.js", + "axe.d.ts" + ], "standard-version": { "scripts": { "postbump": "npm ci && npm run sri-update" @@ -64,6 +73,7 @@ "eslint": "eslint --color --format stylish '{lib,test,build,doc}/**/*.js' 'Gruntfile.js'", "test:headless": "node ./build/test/headless", "test": "tsc && grunt test", + "test-fast": "tsc && grunt test-fast", "test:examples": "node ./doc/examples/test-examples", "test:locales": "mocha test/test-locales.js", "test:rule-help-version": "mocha test/test-rule-help-version.js", @@ -150,5 +160,8 @@ "prettier --write", "git add" ] + }, + "publishConfig": { + "access": "public" } } diff --git a/test/checks/landmarks/landmark-is-unique-after.js b/test/checks/landmarks/landmark-is-unique-after.js index c626ba5e3c..fef990103a 100644 --- a/test/checks/landmarks/landmark-is-unique-after.js +++ b/test/checks/landmarks/landmark-is-unique-after.js @@ -29,54 +29,65 @@ describe('landmark-is-unique-after', function() { it('should update duplicate landmarks with failed result', function() { var result = checks['landmark-is-unique'].after([ createResultWithSameRelatedNodes(true, { - role: 'some role', + role: 'doc-afterword', accessibleText: 'some accessibleText' + // isLandmark: true // truthy }), createResultWithSameRelatedNodes(true, { - role: 'some role', + role: 'doc-afterword', accessibleText: 'some accessibleText' + // isLandmark: 111 // truthy }), createResultWithSameRelatedNodes(true, { - role: 'different role', + role: 'alertdialog', accessibleText: 'some accessibleText' + // isLandmark: false // falsy }), createResultWithSameRelatedNodes(true, { - role: 'some role', + role: 'doc-afterword', accessibleText: 'different accessibleText' + // isLandmark: 'true' // truthy }) ]); + // console.log("result: ", JSON.stringify(result, null, 4)); var expectedResult = [ createResultWithProvidedRelatedNodes( false, { - role: 'some role', + role: 'doc-afterword', accessibleText: 'some accessibleText' + // isLandmark: true // truthy }, [ createResult(true, { - role: 'some role', + role: 'doc-afterword', accessibleText: 'some accessibleText' + // isLandmark: 111 // truthy }) ] ), createResultWithProvidedRelatedNodes( true, { - role: 'different role', + role: 'alertdialog', accessibleText: 'some accessibleText' + // isLandmark: false // falsy }, [] ), createResultWithProvidedRelatedNodes( true, { - role: 'some role', + role: 'doc-afterword', accessibleText: 'different accessibleText' + // isLandmark: 'true' // truthy }, [] ) ]; + // console.log("expectedResult: ", JSON.stringify(expectedResult, null, 4)); + assert.deepEqual(result, expectedResult); }); }); diff --git a/test/checks/landmarks/landmark-is-unique.js b/test/checks/landmarks/landmark-is-unique.js index 049615deee..5de5b32a61 100644 --- a/test/checks/landmarks/landmark-is-unique.js +++ b/test/checks/landmarks/landmark-is-unique.js @@ -20,6 +20,7 @@ describe('landmark-is-unique', function() { var node = fixture.querySelector('div'); var expectedData = { accessibleText: null, + // isLandmark: true, role: 'main' }; axe._tree = axe.utils.getFlattenedTree(fixture); @@ -38,6 +39,7 @@ describe('landmark-is-unique', function() { var node = fixture.querySelector('div'); var expectedData = { accessibleText: 'test text', + // isLandmark: true, role: 'main' }; axe._tree = axe.utils.getFlattenedTree(fixture); diff --git a/test/commons/aria/get-role.js b/test/commons/aria/get-role.js index 6a82302dc6..e76a1bfb90 100644 --- a/test/commons/aria/get-role.js +++ b/test/commons/aria/get-role.js @@ -315,11 +315,29 @@ describe('aria.getRole', function() { assert.equal(aria.getRole(node, { dpub: true }), 'doc-chapter'); }); - it('does not returns DPUB roles with `dpub: false`', function() { + it('returns DPUB roles with `dpub: true` whilst ignoring implicit roles', function() { + var node = document.createElement('li'); + node.setAttribute('role', 'doc-chapter'); + flatTreeSetup(node); + assert.equal(aria.getRole(node, { dpub: true }), 'doc-chapter'); + }); + + it('returns non-DPUB implicit roles with `dpub: false/undefined`', function() { + var node = document.createElement('li'); + node.setAttribute('role', 'doc-chapter'); + var parentNode = document.createElement('div'); + parentNode.appendChild(node); + flatTreeSetup(parentNode); + assert.equal(aria.getRole(node, { dpub: false }), 'listitem'); + assert.equal(aria.getRole(node, { dpub: undefined }), 'listitem'); + }); + + it('does not returns DPUB roles with `dpub: false/undefined`', function() { var node = document.createElement('section'); node.setAttribute('role', 'doc-chapter'); flatTreeSetup(node); assert.isNull(aria.getRole(node, { dpub: false })); + assert.isNull(aria.getRole(node, { dpub: undefined })); }); }); @@ -379,6 +397,54 @@ describe('aria.getRole', function() { 'doc-chapter' ); }); + + it('respect the `dpub: false/undefined` option, whilst skipping the implicit roles due to non-abstract explicit role', function() { + var node = document.createElement('li'); + node.setAttribute('role', 'doc-chapter region'); + var parentNode = document.createElement('div'); + parentNode.appendChild(node); + flatTreeSetup(parentNode); + assert.equal( + aria.getRole(node, { fallback: true, dpub: false }), + 'region' + ); + assert.equal( + aria.getRole(node, { fallback: true, dpub: undefined }), + 'region' + ); + }); + + it('respect the `dpub: false/undefined` option, whilst ignoring the implicit roles and abstract explicit role', function() { + var node = document.createElement('li'); + node.setAttribute('role', 'doc-chapter section'); + var parentNode = document.createElement('div'); + parentNode.appendChild(node); + flatTreeSetup(parentNode); + assert.isNull( + aria.getRole(node, { noImplicit: true, fallback: true, dpub: false }) + ); + assert.isNull( + aria.getRole(node, { + noImplicit: true, + fallback: true, + dpub: undefined + }) + ); + }); + + it('respect the `dpub: false/undefined` option', function() { + var node = document.createElement('div'); + node.setAttribute('role', 'doc-chapter region'); + flatTreeSetup(node); + assert.equal( + aria.getRole(node, { fallback: true, dpub: false }), + 'region' + ); + assert.equal( + aria.getRole(node, { fallback: true, dpub: undefined }), + 'region' + ); + }); }); describe('noPresentational is honored', function() { diff --git a/test/commons/standards/get-aria-roles-by-type.js b/test/commons/standards/get-aria-roles-by-type.js index a83ffe528e..4014f2fb6f 100644 --- a/test/commons/standards/get-aria-roles-by-type.js +++ b/test/commons/standards/get-aria-roles-by-type.js @@ -12,6 +12,7 @@ describe('standards.getAriaRolesByType', function() { it('should return a list of role names by type', function() { // Source: https://www.w3.org/TR/wai-aria-1.1/#document_structure_roles var structureRoles = getAriaRolesByType('structure'); + // console.log(JSON.stringify(structureRoles, null, 4)); assert.deepEqual(structureRoles, [ 'article', 'blockquote', @@ -49,7 +50,10 @@ describe('standards.getAriaRolesByType', function() { 'term', 'time', 'toolbar', - 'tooltip' + 'tooltip', + 'doc-biblioentry', + 'doc-endnote', + 'doc-example' ]); }); diff --git a/test/core/base/audit.js b/test/core/base/audit.js index 7d51a70e23..c0888ae278 100644 --- a/test/core/base/audit.js +++ b/test/core/base/audit.js @@ -4,7 +4,8 @@ describe('Audit', function() { var Audit = axe._thisWillBeDeletedDoNotUse.base.Audit; var Rule = axe._thisWillBeDeletedDoNotUse.base.Rule; - var ver = axe.version.substring(0, axe.version.lastIndexOf('.')); + var _v = axe.version.replace(/-\w+\.\w+$/, ''); + var ver = _v.substring(0, _v.lastIndexOf('.')); var a, getFlattenedTree; var isNotCalled = function(err) { throw err || new Error('Reject should not be called'); diff --git a/test/core/public/configure.js b/test/core/public/configure.js index 8ab54650cb..557172bdd0 100644 --- a/test/core/public/configure.js +++ b/test/core/public/configure.js @@ -4,7 +4,8 @@ describe('axe.configure', function() { // var Check = axe._thisWillBeDeletedDoNotUse.base.Check; var fixture = document.getElementById('fixture'); var axeVersion = axe.version; - var ver = axe.version.substring(0, axe.version.lastIndexOf('.')); + var _v = axe.version.replace(/-\w+\.\w+$/, ''); + var ver = _v.substring(0, _v.lastIndexOf('.')); afterEach(function() { fixture.innerHTML = ''; diff --git a/test/core/public/get-rules.js b/test/core/public/get-rules.js index 775d62693d..ffcf2f7bed 100644 --- a/test/core/public/get-rules.js +++ b/test/core/public/get-rules.js @@ -1,6 +1,7 @@ describe('axe.getRules', function() { 'use strict'; - var ver = axe.version.substring(0, axe.version.lastIndexOf('.')); + var _v = axe.version.replace(/-\w+\.\w+$/, ''); + var ver = _v.substring(0, _v.lastIndexOf('.')); beforeEach(function() { axe._load({ diff --git a/test/core/public/run-rules.js b/test/core/public/run-rules.js index c039b99195..d34a61b88d 100644 --- a/test/core/public/run-rules.js +++ b/test/core/public/run-rules.js @@ -1,6 +1,7 @@ describe('runRules', function() { 'use strict'; - var ver = axe.version.substring(0, axe.version.lastIndexOf('.')); + var _v = axe.version.replace(/-\w+\.\w+$/, ''); + var ver = _v.substring(0, _v.lastIndexOf('.')); // These tests can sometimes be flaky in IE, allow for up to 3 retries if (axe.testUtils.isIE11) { diff --git a/test/core/utils/get-selector.js b/test/core/utils/get-selector.js index c98a52cdec..5e9daca0c5 100644 --- a/test/core/utils/get-selector.js +++ b/test/core/utils/get-selector.js @@ -38,7 +38,7 @@ function makeNonuniqueLongAttributes(fixture) { return node; } -describe('axe.utils.getSelector', function() { +describe('axe.utils.getSelector (core)', function() { 'use strict'; var fixture = document.getElementById('fixture'); diff --git a/test/integration/full/epub-type-has-matching-role/content__.xhtml b/test/integration/full/epub-type-has-matching-role/content__.xhtml new file mode 100644 index 0000000000..2ca87358c0 --- /dev/null +++ b/test/integration/full/epub-type-has-matching-role/content__.xhtml @@ -0,0 +1,65 @@ + + + + epub-type-has-matching-role test + + + + + + + + + +

Loomings

+

Call me Ishmael.

+ +
+ A beautiful cover +
+ + + +
    +
  1. item 1
  2. +
+ +
notice
+ +
hello
+ +
definition
+definition +
+ + + +
notice
+ +
notice
+ +
notice
+ + + +
+ + + + + diff --git a/test/integration/full/epub-type-has-matching-role/epub-type-has-matching-role-nav.js b/test/integration/full/epub-type-has-matching-role/epub-type-has-matching-role-nav.js new file mode 100644 index 0000000000..ffd252977e --- /dev/null +++ b/test/integration/full/epub-type-has-matching-role/epub-type-has-matching-role-nav.js @@ -0,0 +1,47 @@ +describe('epub-type-has-matching-role-nav test pass', function() { + // Checks that `epub:type` have matching ARIA roles + // Ensure the element has an ARIA role matching its epub:type + // ARIA role should be used in addition to epub:type + + 'use strict'; + var results; + before(function(done) { + axe.testUtils.awaitNestedLoad(function() { + // axe.configure({}); // DAISY ACE BREAKPOINT AXE CONFIGURE + + axe.run( + { runOnly: { type: 'rule', values: ['epub-type-has-matching-role'] } }, + function(err, r) { + assert.isNull(err); + results = r; + done(); + } + ); + }); + }); + + describe('violations', function() { + it('should find 0', function() { + assert.lengthOf(results.violations, 0); + }); + }); + + describe('passes', function() { + it('should find 1', function() { + assert.lengthOf(results.passes, 1); + }); + + it('should find #id-toc', function() { + assert.deepEqual(results.passes[0].nodes[0].target, ['#id-toc']); + }); + }); + + it('should find 0 inapplicable', function() { + assert.lengthOf(results.inapplicable, 0); + // assert.lengthOf(results.inapplicable[0].nodes, 0); + }); + + it('should find 0 incomplete', function() { + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/full/epub-type-has-matching-role/epub-type-has-matching-role.js b/test/integration/full/epub-type-has-matching-role/epub-type-has-matching-role.js new file mode 100644 index 0000000000..98bd4aa0d5 --- /dev/null +++ b/test/integration/full/epub-type-has-matching-role/epub-type-has-matching-role.js @@ -0,0 +1,62 @@ +describe('epub-type-has-matching-role test fail', function() { + // Checks that `epub:type` have matching ARIA roles + // Ensure the element has an ARIA role matching its epub:type + // ARIA role should be used in addition to epub:type + + 'use strict'; + var results; + before(function(done) { + axe.testUtils.awaitNestedLoad(function() { + // axe.configure({}); // DAISY ACE BREAKPOINT AXE CONFIGURE + + axe.run( + { runOnly: { type: 'rule', values: ['epub-type-has-matching-role'] } }, + function(err, r) { + assert.isNull(err); + results = r; + done(); + } + ); + }); + }); + + describe('violations', function() { + it('should find 1', function() { + // console.log(JSON.stringify(results.violations, null, 4)); + assert.lengthOf(results.violations, 1); + }); + + it('should find #fail1 #fail2 #fail3', function() { + assert.deepEqual(results.violations[0].nodes[0].target, ['#fail1']); + assert.deepEqual(results.violations[0].nodes[1].target, ['#fail2']); + assert.deepEqual(results.violations[0].nodes[2].target, ['#fail3']); + }); + }); + + describe('passes', function() { + it('should find 1', function() { + // console.log(JSON.stringify(results.passes, null, 4)); + assert.lengthOf(results.passes, 1); + }); + + it('should find section #pass1-6', function() { + assert.deepEqual(results.passes[0].nodes[0].target, ['section']); + assert.deepEqual(results.passes[0].nodes[1].target, ['#pass2']); + assert.deepEqual(results.passes[0].nodes[2].target, ['#pass3']); + assert.deepEqual(results.passes[0].nodes[3].target, ['#pass4']); + assert.deepEqual(results.passes[0].nodes[4].target, ['#pass6']); + assert.deepEqual(results.passes[0].nodes[5].target, ['#pass5']); + assert.deepEqual(results.passes[0].nodes[6].target, ['#id-landmarks']); + assert.deepEqual(results.passes[0].nodes[7].target, ['#pass0']); + }); + }); + + it('should find 0 inapplicable', function() { + assert.lengthOf(results.inapplicable, 0); + // assert.lengthOf(results.inapplicable[0].nodes, 0); + }); + + it('should find 0 incomplete', function() { + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/full/epub-type-has-matching-role/nav__.xhtml b/test/integration/full/epub-type-has-matching-role/nav__.xhtml new file mode 100644 index 0000000000..db1c928406 --- /dev/null +++ b/test/integration/full/epub-type-has-matching-role/nav__.xhtml @@ -0,0 +1,39 @@ + + + epub-type-has-matching-role cover test + + + + + + + + + + + +
+ + + + + diff --git a/test/integration/full/landmark-no-duplicate-main/frames/level1-1.html b/test/integration/full/landmark-no-duplicate-main/frames/level1-1.html new file mode 100644 index 0000000000..a0c3273e51 --- /dev/null +++ b/test/integration/full/landmark-no-duplicate-main/frames/level1-1.html @@ -0,0 +1,10 @@ + + + + + + + +

No main landmarks

+ + diff --git a/test/integration/full/landmark-no-duplicate-main/landmark-no-duplicate-main-pass1.html b/test/integration/full/landmark-no-duplicate-main/landmark-no-duplicate-main-pass1.html new file mode 100644 index 0000000000..7659fe192c --- /dev/null +++ b/test/integration/full/landmark-no-duplicate-main/landmark-no-duplicate-main-pass1.html @@ -0,0 +1,29 @@ + + + + + + + + + + + +

No main landmarks

+ +
+ + + + + diff --git a/test/integration/full/landmark-no-duplicate-main/landmark-no-duplicate-main-pass1.js b/test/integration/full/landmark-no-duplicate-main/landmark-no-duplicate-main-pass1.js new file mode 100644 index 0000000000..cdbeb398b3 --- /dev/null +++ b/test/integration/full/landmark-no-duplicate-main/landmark-no-duplicate-main-pass1.js @@ -0,0 +1,37 @@ +describe('landmark-no-duplicate-main test pass 1', function() { + 'use strict'; + var results; + before(function(done) { + axe.testUtils.awaitNestedLoad(function() { + axe.run( + { runOnly: { type: 'rule', values: ['landmark-no-duplicate-main'] } }, + function(err, r) { + assert.isNull(err); + results = r; + done(); + } + ); + }); + }); + + describe('violations', function() { + it('should find 0', function() { + assert.lengthOf(results.violations, 0); + }); + }); + + describe('passes', function() { + it('should find 0', function() { + assert.lengthOf(results.passes, 0); + }); + }); + + it('should find 1 inapplicable', function() { + assert.lengthOf(results.inapplicable, 1); + assert.lengthOf(results.inapplicable[0].nodes, 0); + }); + + it('should find 0 incomplete', function() { + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/full/landmark-one-main/frames/level1-fail1.html b/test/integration/full/landmark-one-main/frames/level1-fail1.html new file mode 100644 index 0000000000..1ad1fa5aff --- /dev/null +++ b/test/integration/full/landmark-one-main/frames/level1-fail1.html @@ -0,0 +1,15 @@ + + + + + + + +
+

Main landmark 1 created with main tag

+
+
+

Main landmark 2 created with main role

+
+ + diff --git a/test/integration/full/landmark-one-main/landmark-one-main-fail.js b/test/integration/full/landmark-one-main/landmark-one-main-fail.js index 8bac6b994e..7c1eb84213 100644 --- a/test/integration/full/landmark-one-main/landmark-one-main-fail.js +++ b/test/integration/full/landmark-one-main/landmark-one-main-fail.js @@ -15,26 +15,47 @@ describe('landmark-one-main test failure', function() { }); describe('violations', function() { - it('should find 1', function() { - assert.lengthOf(results.violations[0].nodes, 2); + // DAISY-AXE disables page-has-main + it('should find 0', function() { + assert.lengthOf(results.violations, 0); }); - it('should find #frame1', function() { - assert.deepEqual(results.violations[0].nodes[0].target, ['#fail1']); - }); + // it('should find 2', function() { + // assert.lengthOf(results.violations[0].nodes, 2); + // }); + + // it('should find #fail1', function() { + // assert.deepEqual(results.violations[0].nodes[0].target, ['#fail1']); + // }); + // it('should find #frame1, #violation2', function() { + // assert.deepEqual(results.violations[0].nodes[1].target, [ + // '#frame1', + // '#violation2' + // ]); + // }); + }); + + describe('passes', function() { + // DAISY-AXE disables page-has-main + it('should find 1', function() { + assert.lengthOf(results.passes, 1); + }); + it('should find 2', function() { + assert.lengthOf(results.passes[0].nodes, 2); + }); + it('should find #fail1', function() { + assert.deepEqual(results.passes[0].nodes[0].target, ['#fail1']); + }); it('should find #frame1, #violation2', function() { - assert.deepEqual(results.violations[0].nodes[1].target, [ + assert.deepEqual(results.passes[0].nodes[1].target, [ '#frame1', '#violation2' ]); }); - }); - - describe('passes', function() { - it('should find 0', function() { - assert.lengthOf(results.passes, 0); - }); + // it('should find 0', function() { + // assert.lengthOf(results.passes, 0); + // }); }); it('should find 0 inapplicable', function() { diff --git a/test/integration/full/landmark-one-main/landmark-one-main-fail1.html b/test/integration/full/landmark-one-main/landmark-one-main-fail1.html new file mode 100644 index 0000000000..37d3a2999b --- /dev/null +++ b/test/integration/full/landmark-one-main/landmark-one-main-fail1.html @@ -0,0 +1,29 @@ + + + + + + + + + + + +

No main content here

+ +
+ + + + + diff --git a/test/integration/full/landmark-one-main/landmark-one-main-fail1.js b/test/integration/full/landmark-one-main/landmark-one-main-fail1.js new file mode 100644 index 0000000000..d31bc95b7b --- /dev/null +++ b/test/integration/full/landmark-one-main/landmark-one-main-fail1.js @@ -0,0 +1,47 @@ +describe('landmark-one-main test failure 1', function() { + 'use strict'; + var results; + before(function(done) { + axe.testUtils.awaitNestedLoad(function() { + axe.run( + { runOnly: { type: 'rule', values: ['landmark-one-main'] } }, + function(err, r) { + assert.isNull(err); + results = r; + done(); + } + ); + }); + }); + + describe('violations', function() { + it('should find 1', function() { + assert.lengthOf(results.violations[0].nodes, 1); + }); + + it('should find #frame1, #violation2', function() { + assert.deepEqual(results.violations[0].nodes[0].target, [ + '#frame1', + '#violation2' + ]); + }); + }); + + describe('passes', function() { + it('should find 1', function() { + assert.lengthOf(results.passes[0].nodes, 1); + }); + + it('should find #pass1', function() { + assert.deepEqual(results.passes[0].nodes[0].target, ['#pass1']); + }); + }); + + it('should find 0 inapplicable', function() { + assert.lengthOf(results.inapplicable, 0); + }); + + it('should find 0 incomplete', function() { + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/full/landmark-one-main/landmark-one-main-fail2.html b/test/integration/full/landmark-one-main/landmark-one-main-fail2.html new file mode 100644 index 0000000000..d76ea3fd5e --- /dev/null +++ b/test/integration/full/landmark-one-main/landmark-one-main-fail2.html @@ -0,0 +1,34 @@ + + + + + + + + + + + +
+

Main landmark 2 created with main role

+
+
+

Main landmark 2 created with main tag

+
+ +
+ + + + + diff --git a/test/integration/full/landmark-one-main/landmark-one-main-fail2.js b/test/integration/full/landmark-one-main/landmark-one-main-fail2.js new file mode 100644 index 0000000000..8b56a4f975 --- /dev/null +++ b/test/integration/full/landmark-one-main/landmark-one-main-fail2.js @@ -0,0 +1,47 @@ +describe('landmark-one-main test failure 2', function() { + 'use strict'; + var results; + before(function(done) { + axe.testUtils.awaitNestedLoad(function() { + axe.run( + { runOnly: { type: 'rule', values: ['landmark-one-main'] } }, + function(err, r) { + assert.isNull(err); + results = r; + done(); + } + ); + }); + }); + + describe('violations', function() { + it('should find 1', function() { + assert.lengthOf(results.violations[0].nodes, 1); + }); + + it('should find #fail1', function() { + assert.deepEqual(results.violations[0].nodes[0].target, ['#fail1']); + }); + }); + + describe('passes', function() { + it('should find 1', function() { + assert.lengthOf(results.passes[0].nodes, 1); + }); + + it('should find #frame1 #pass3', function() { + assert.deepEqual(results.passes[0].nodes[0].target, [ + '#frame1', + '#pass3' + ]); + }); + }); + + it('should find 0 inapplicable', function() { + assert.lengthOf(results.inapplicable, 0); + }); + + it('should find 0 incomplete', function() { + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/full/landmark-one-main/landmark-one-main-pass1.js b/test/integration/full/landmark-one-main/landmark-one-main-pass1.js index 3e5eab4640..5c20e0f648 100644 --- a/test/integration/full/landmark-one-main/landmark-one-main-pass1.js +++ b/test/integration/full/landmark-one-main/landmark-one-main-pass1.js @@ -1,4 +1,4 @@ -describe('landmark-one-main test pass', function() { +describe('landmark-one-main test pass 1', function() { 'use strict'; var results; before(function(done) { diff --git a/test/integration/full/landmark-one-main/landmark-one-main-pass2.js b/test/integration/full/landmark-one-main/landmark-one-main-pass2.js index e18612ebb1..964aaa7efc 100644 --- a/test/integration/full/landmark-one-main/landmark-one-main-pass2.js +++ b/test/integration/full/landmark-one-main/landmark-one-main-pass2.js @@ -1,4 +1,4 @@ -describe('landmark-one-main test pass', function() { +describe('landmark-one-main test pass 2', function() { 'use strict'; var results; before(function(done) { diff --git a/test/integration/full/landmark-one-main/landmark-one-main-pass3.js b/test/integration/full/landmark-one-main/landmark-one-main-pass3.js index e18612ebb1..5105a5ba5c 100644 --- a/test/integration/full/landmark-one-main/landmark-one-main-pass3.js +++ b/test/integration/full/landmark-one-main/landmark-one-main-pass3.js @@ -1,4 +1,4 @@ -describe('landmark-one-main test pass', function() { +describe('landmark-one-main test pass 3', function() { 'use strict'; var results; before(function(done) { diff --git a/test/integration/full/pagebreak-label/content__.xhtml b/test/integration/full/pagebreak-label/content__.xhtml new file mode 100644 index 0000000000..53a5d7fd8b --- /dev/null +++ b/test/integration/full/pagebreak-label/content__.xhtml @@ -0,0 +1,37 @@ + + + + pagebreak-label test + + + + + + + + + +

Loomings

+

Call me Ishmael.

+ + + + + + +
+ + + + + diff --git a/test/integration/full/pagebreak-label/pagebreak-label.js b/test/integration/full/pagebreak-label/pagebreak-label.js new file mode 100644 index 0000000000..ccb320857b --- /dev/null +++ b/test/integration/full/pagebreak-label/pagebreak-label.js @@ -0,0 +1,55 @@ +describe('pagebreak-label test fail', function() { + // Checks that `epub:type` have matching ARIA roles + // Ensure the element has an ARIA role matching its epub:type + // ARIA role should be used in addition to epub:type + + 'use strict'; + var results; + before(function(done) { + axe.testUtils.awaitNestedLoad(function() { + // axe.configure({}); // DAISY ACE BREAKPOINT AXE CONFIGURE + + axe.run( + { runOnly: { type: 'rule', values: ['pagebreak-label'] } }, + function(err, r) { + assert.isNull(err); + results = r; + done(); + } + ); + }); + }); + + describe('violations', function() { + it('should find 1', function() { + // console.log(JSON.stringify(results.violations, null, 4)); + assert.lengthOf(results.violations, 1); + }); + + it('should find #p3 #p4', function() { + assert.deepEqual(results.violations[0].nodes[0].target, ['#p3']); + assert.deepEqual(results.violations[0].nodes[1].target, ['#p4']); + }); + }); + + describe('passes', function() { + it('should find 1', function() { + // console.log(JSON.stringify(results.passes, null, 4)); + assert.lengthOf(results.passes, 1); + }); + + it('should find section #p1 #p2', function() { + assert.deepEqual(results.passes[0].nodes[0].target, ['#p1']); + assert.deepEqual(results.passes[0].nodes[1].target, ['#p2']); + }); + }); + + it('should find 0 inapplicable', function() { + assert.lengthOf(results.inapplicable, 0); + // assert.lengthOf(results.inapplicable[0].nodes, 0); + }); + + it('should find 0 incomplete', function() { + assert.lengthOf(results.incomplete, 0); + }); +}); 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 1342927aae..bf4fc6fac7 100644 --- a/test/integration/rules/aria-allowed-role/aria-allowed-role.html +++ b/test/integration/rules/aria-allowed-role/aria-allowed-role.html @@ -8,7 +8,17 @@
+

+ +

+
    +
  • +
+

+ +

+
ok
    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 feefed99c2..66784320cd 100644 --- a/test/integration/rules/aria-allowed-role/aria-allowed-role.json +++ b/test/integration/rules/aria-allowed-role/aria-allowed-role.json @@ -9,8 +9,12 @@ ["#pass-object-role-document"], ["#pass-section-role-doc-afterword"], ["#pass-section-role-doc-bib"], + ["#pass-li-role-doc-biblioentry2"], + ["#pass-li-role-doc-endnote2"], + ["#pass-li-role-doc-endnote"], ["#pass-li-role-doc-biblioentry"], ["#pass-aside-doc-example"], + ["#pass-figure-doc-example"], ["#pass-div-valid-role"], ["#pass-ol-valid-role"], ["#pass-nav-role-doc-index"], @@ -66,7 +70,9 @@ ["#pass-dpub-6"], ["#pass-dpub-7"], ["#hr-presentation"], - ["#hr-none"] + ["#hr-none"], + ["#fail-dpub-6"], + ["#fail-dpub-7"] ], "violations": [ ["#fail-dd-no-role"], @@ -86,9 +92,7 @@ ["#fail-dpub-2"], ["#fail-dpub-3"], ["#fail-dpub-4"], - ["#fail-dpub-5"], - ["#fail-dpub-6"], - ["#fail-dpub-7"] + ["#fail-dpub-5"] ], "incomplete": [["#incomplete1"], ["#incomplete2"]] } diff --git a/test/integration/rules/aria-required-children/aria-required-children.html b/test/integration/rules/aria-required-children/aria-required-children.html index 25cca78769..eeb8f7eb1e 100644 --- a/test/integration/rules/aria-required-children/aria-required-children.html +++ b/test/integration/rules/aria-required-children/aria-required-children.html @@ -25,6 +25,26 @@
    +
    +
      +
    • +
    +
    +
    +
      +
    • +
    +
    +
    +
      +
    • +
    +
    +
    +
      +
    • +
    +
    diff --git a/test/integration/rules/aria-required-children/aria-required-children.json b/test/integration/rules/aria-required-children/aria-required-children.json index 31500091ff..5b559a05bb 100644 --- a/test/integration/rules/aria-required-children/aria-required-children.json +++ b/test/integration/rules/aria-required-children/aria-required-children.json @@ -26,7 +26,11 @@ ["#pass13"], ["#pass14"], ["#pass15"], - ["#pass16"] + ["#pass16"], + ["#pass17"], + ["#pass18"], + ["#pass19"], + ["#pass20"] ], "incomplete": [ ["#incomplete1"], @@ -37,8 +41,6 @@ ["#incomplete6"], ["#incomplete7"], ["#incomplete8"], - ["#incomplete9"], - ["#incomplete10"], ["#incomplete11"] ] } diff --git a/test/integration/rules/epub-type-has-matching-role/epub-type-has-matching-role.json b/test/integration/rules/epub-type-has-matching-role/epub-type-has-matching-role.json new file mode 100644 index 0000000000..5a7e09deb6 --- /dev/null +++ b/test/integration/rules/epub-type-has-matching-role/epub-type-has-matching-role.json @@ -0,0 +1,15 @@ +{ + "description": "epub-type-has-matching-role test", + "rule": "epub-type-has-matching-role", + "violations": [["#fail1"], ["#fail2"], ["#fail3"]], + "passes": [ + ["section"], + ["#pass2"], + ["#pass3"], + ["#pass4"], + ["#pass6"], + ["#pass5"], + ["#id-landmarks"], + ["#pass0"] + ] +} diff --git a/test/integration/rules/epub-type-has-matching-role/epub-type-has-matching-role.xhtml b/test/integration/rules/epub-type-has-matching-role/epub-type-has-matching-role.xhtml new file mode 100644 index 0000000000..fa168ccb99 --- /dev/null +++ b/test/integration/rules/epub-type-has-matching-role/epub-type-has-matching-role.xhtml @@ -0,0 +1,50 @@ + + + + epub-type-has-matching-role test + + + + + + +

    Loomings

    +

    Call me Ishmael.

    + +
    + A beautiful cover +
    + + + +
      +
    1. item 1
    2. +
    + +
    notice
    + +
    hello
    + +
    definition
    + definition +
    + + + +
    notice
    + +
    notice
    + +
    notice
    + + + + + diff --git a/test/integration/rules/heading-order/frame.html b/test/integration/rules/heading-order/frame.html index a28abfdeb7..fc46b81dd5 100644 --- a/test/integration/rules/heading-order/frame.html +++ b/test/integration/rules/heading-order/frame.html @@ -2,7 +2,7 @@ - landmark-unique test + heading order test diff --git a/test/integration/rules/landmark-unique/landmark-unique-pass_.json b/test/integration/rules/landmark-unique/landmark-unique-pass_.json new file mode 100644 index 0000000000..7c3f6c8868 --- /dev/null +++ b/test/integration/rules/landmark-unique/landmark-unique-pass_.json @@ -0,0 +1,14 @@ +{ + "description": "landmark-unique-pass_ tests", + "rule": "landmark-unique", + "violations": [["#header-notes"], ["#pass-aside-footnote1"]], + "passes": [ + ["#pass-nav-toc"], + ["#pass-nav-nav"], + ["#pass-nav-pagelist"], + ["#pass-nav-pagelist-other"], + ["#pass-epi"], + ["#pass-biblio"], + ["#section-notes"] + ] +} diff --git a/test/integration/rules/landmark-unique/landmark-unique-pass_.xhtml b/test/integration/rules/landmark-unique/landmark-unique-pass_.xhtml new file mode 100644 index 0000000000..618e6a0708 --- /dev/null +++ b/test/integration/rules/landmark-unique/landmark-unique-pass_.xhtml @@ -0,0 +1,84 @@ + + + + landmark-unique pass test + + + + + + +

    Loomings

    +

    Call me Ishmael.

    + + + + + + + + + +

    tmp1

    + +

    tmp3

    + +
    n text
    +
    +

    n txt1

    +
    +
    +

    n txt2

    +
    + +
    text
    +
    +

    txt1

    +
    + +
    +

    txt3

    +
    + + + diff --git a/test/integration/rules/runner.js b/test/integration/rules/runner.js index bee2797b66..dd713e13b6 100644 --- a/test/integration/rules/runner.js +++ b/test/integration/rules/runner.js @@ -2,6 +2,8 @@ (function() { 'use strict'; + // axe.configure({}); // DAISY ACE BREAKPOINT AXE CONFIGURE + function flattenResult(results) { return { passes: results.passes[0], @@ -61,6 +63,11 @@ test[collection].forEach(function(selector) { it('should find ' + JSON.stringify(selector), function() { if (!nodes) { + console.log( + JSON.stringify(results, null, 4), + ' ---- ', + JSON.stringify(test, null, 4) + ); assert(false, 'there are no ' + collection); return; } @@ -79,10 +86,20 @@ }); if (matches.length === 0) { + console.log( + JSON.stringify(results, null, 4), + ' ---- ', + JSON.stringify(test, null, 4) + ); assert(false, 'Element not found'); } else if (matches.length === 1) { assert(true, 'Element found'); } else { + console.log( + JSON.stringify(results, null, 4), + ' ---- ', + JSON.stringify(test, null, 4) + ); assert( false, 'Found ' + @@ -95,12 +112,29 @@ it('should not return other results', function() { if (typeof nodes !== 'undefined') { - var targets = nodes.map(function(node) { - return node.target; - }); + var targets = nodes + .filter(function(node) { + return node.impact !== null; + }) + .map(function(node) { + return node.target; + }); + if (targets && targets.length) { + console.log( + JSON.stringify(results, null, 4), + ' ---- ', + JSON.stringify(test, null, 4) + ); + } // check that all nodes are removed assert.equal(JSON.stringify(targets), '[]'); } else { + console.log( + JSON.stringify(results, null, 4), + ' ---- ', + JSON.stringify(test, null, 4) + ); + assert(false, 'there are no ' + collection); } }); diff --git a/test/runner.tmpl b/test/runner.tmpl index 6c71d29d20..d673adba45 100644 --- a/test/runner.tmpl +++ b/test/runner.tmpl @@ -24,7 +24,8 @@ diff --git a/test/test-rule-help-version.js b/test/test-rule-help-version.js index 018b455f5d..59fe93a424 100644 --- a/test/test-rule-help-version.js +++ b/test/test-rule-help-version.js @@ -3,7 +3,7 @@ var path = require('path'); var assert = require('assert'); var packageJSON = require(path.join(__dirname, '../package.json')); -var versions = packageJSON.version.split('.'); +var versions = packageJSON.version.replace(/-\w+\.\w+$/, '').split('.'); var version = versions[0] + '.' + versions[1]; it( diff --git a/test/version.js b/test/version.js index d9f2c3a116..583ae39d5f 100644 --- a/test/version.js +++ b/test/version.js @@ -1,2 +1,2 @@ // This is temporary as the tests still need to use this file -axe.version = '4.1.4'; +axe.version = '4.1.4-canary.3';