From df3c66620e6205ef758f953f94595b3c5148d99c Mon Sep 17 00:00:00 2001 From: Romain Deltour Date: Wed, 3 Jul 2019 02:52:34 +0200 Subject: [PATCH] feat: improve epub:type / DPUB ARIA role mapping - no longer check that a `cover` epub:type maps to a `doc-cover` role Fixes #201, Fixes #214 - disable mapping checks for descendants of a 'landmarks' nav Fixes #220 - add a mapping check for the `figure` type - support multiple `epub:type` values - support implicit ARIA roles --- .../ace-core/src/checker/checker-chromium.js | 1 + packages/ace-core/src/scripts/ace-axe.js | 28 +++++++---- .../src/scripts/axe-patch-aria-roles.js | 11 ++++ tests/__tests__/axe-rules.test.js | 47 ++++++++++++++++++ .../EPUB/content_001.xhtml | 10 ++++ .../EPUB/image_001.jpg | Bin 0 -> 18447 bytes .../EPUB/nav.xhtml | 12 +++++ .../EPUB/package.opf | 24 +++++++++ .../META-INF/container.xml | 6 +++ .../axerule-matching-dpub-role-cover/mimetype | 1 + .../EPUB/content_001.xhtml | 7 +++ .../axerule-matching-dpub-role/EPUB/nav.xhtml | 6 +++ .../EPUB/package.opf | 1 + 13 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 packages/ace-core/src/scripts/axe-patch-aria-roles.js create mode 100644 tests/data/axerule-matching-dpub-role-cover/EPUB/content_001.xhtml create mode 100644 tests/data/axerule-matching-dpub-role-cover/EPUB/image_001.jpg create mode 100644 tests/data/axerule-matching-dpub-role-cover/EPUB/nav.xhtml create mode 100644 tests/data/axerule-matching-dpub-role-cover/EPUB/package.opf create mode 100644 tests/data/axerule-matching-dpub-role-cover/META-INF/container.xml create mode 100644 tests/data/axerule-matching-dpub-role-cover/mimetype diff --git a/packages/ace-core/src/checker/checker-chromium.js b/packages/ace-core/src/checker/checker-chromium.js index c68a3961..99d91915 100644 --- a/packages/ace-core/src/checker/checker-chromium.js +++ b/packages/ace-core/src/checker/checker-chromium.js @@ -16,6 +16,7 @@ tmp.setGracefulCleanup(); const scripts = [ path.resolve(require.resolve('axe-core'), '../axe.min.js'), require.resolve('../scripts/vendor/outliner.min.js'), + require.resolve('../scripts/axe-patch-aria-roles.js'), require.resolve('../scripts/axe-patch-is-aria-role-allowed.js'), require.resolve('../scripts/axe-patch-only-list-items.js'), require.resolve('../scripts/ace-axe.js'), diff --git a/packages/ace-core/src/scripts/ace-axe.js b/packages/ace-core/src/scripts/ace-axe.js index 5d1da4db..771aa572 100644 --- a/packages/ace-core/src/scripts/ace-axe.js +++ b/packages/ace-core/src/scripts/ace-axe.js @@ -105,7 +105,6 @@ daisy.ace.run = function(done) { ['chapter', 'doc-chapter'], ['colophon', 'doc-colophon'], ['conclusion', 'doc-conclusion'], - ['cover', 'doc-cover'], ['credit', 'doc-credit'], ['credits', 'doc-credits'], ['dedication', 'doc-dedication'], @@ -114,6 +113,7 @@ daisy.ace.run = function(done) { ['epigraph', 'doc-epigraph'], ['epilogue', 'doc-epilogue'], ['errata', 'doc-errata'], + ['figure', 'figure'], ['footnote', 'doc-footnote'], ['foreword', 'doc-foreword'], ['glossary', 'doc-glossary'], @@ -125,8 +125,8 @@ daisy.ace.run = function(done) { ['introduction', 'doc-introduction'], ['noteref', 'doc-noteref'], ['notice', 'doc-notice'], - ['pagebreak', 'doc-pagebreak'], ['page-list', 'doc-pagelist'], + ['pagebreak', 'doc-pagebreak'], ['part', 'doc-part'], ['preface', 'doc-preface'], ['prologue', 'doc-prologue'], @@ -137,16 +137,24 @@ daisy.ace.run = function(done) { ['tip', 'doc-tip'], ['toc', 'doc-toc'] ]); + if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) { - var type = node.getAttributeNS('http://www.idpf.org/2007/ops', 'type').trim(); - if (mappings.has(type)) { - if (!node.hasAttribute('role')) { - return false; - } else { - var role = node.getAttribute('role').trim(); - return role == mappings.get(type); - } + // abort if descendant of landmarks nav (nav with epub:type=landmarks) + if (axe.utils.matchesSelector(node, 'nav[*|type~="landmarks"] *')) { + return true; } + + // iterate for each epub:type value + var types = axe.utils.tokenList(node.getAttributeNS('http://www.idpf.org/2007/ops', '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). + return mappings.get(type) == axe.commons.aria.getRole(node,{dpub: true}); + } + } } return true; }, diff --git a/packages/ace-core/src/scripts/axe-patch-aria-roles.js b/packages/ace-core/src/scripts/axe-patch-aria-roles.js new file mode 100644 index 00000000..30e8733b --- /dev/null +++ b/packages/ace-core/src/scripts/axe-patch-aria-roles.js @@ -0,0 +1,11 @@ +'use strict'; + +/* +This patch is needed to ensure that axe's ARIA lookup table is consistent +with the mappings defined in the ARIA in HTML spec. +*/ +(function axePatch(window) { + const axe = window.axe; + axe.commons.aria.lookupTable.role.listitem.implicit = ['li']; + axe.commons.aria.lookupTable.role.figure.implicit = ['figure']; +}(window)); diff --git a/tests/__tests__/axe-rules.test.js b/tests/__tests__/axe-rules.test.js index 6047b989..ff2d650c 100644 --- a/tests/__tests__/axe-rules.test.js +++ b/tests/__tests__/axe-rules.test.js @@ -114,6 +114,13 @@ test('Checks that `epub:type` have matching ARIA roles', async() => { } }), }), + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'epub-type-has-matching-role' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#fail1'] }), + }), + }), expect.objectContaining({ 'earl:test': expect.objectContaining({ 'dct:title': 'epub-type-has-matching-role' }), 'earl:result': expect.objectContaining({ @@ -121,6 +128,13 @@ test('Checks that `epub:type` have matching ARIA roles', async() => { 'earl:pointer': expect.objectContaining({ css: ['#fail2'] }), }), }), + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'epub-type-has-matching-role' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#fail3'] }), + }), + }), ])); expect(assertions).not.toEqual(expect.arrayContaining([ expect.objectContaining({ @@ -149,4 +163,37 @@ test('Checks that `epub:type` have matching ARIA roles', async() => { }), }) ])); + expect(assertions).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'epub-type-has-matching-role' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#pass4'] }), + }), + }) + ])); + expect(assertions).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'epub-type-has-matching-role' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#pass5'] }), + }), + }) + ])); + const navAssertions = findAssertionsForDoc(report, 'nav.xhtml'); + expect(navAssertions).toBeDefined(); + expect(navAssertions).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'epub-type-has-matching-role' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#pass1'] }), + }), + }) + ])); }); +test('Checks that `epub:type` `cover` isn’t reported has missing a matching ARIA role', async() => { + const report = await ace('../data/axerule-matching-dpub-role-cover'); + expect(report['earl:result']['earl:outcome']).toEqual('pass'); +}); \ No newline at end of file diff --git a/tests/data/axerule-matching-dpub-role-cover/EPUB/content_001.xhtml b/tests/data/axerule-matching-dpub-role-cover/EPUB/content_001.xhtml new file mode 100644 index 00000000..0a01f578 --- /dev/null +++ b/tests/data/axerule-matching-dpub-role-cover/EPUB/content_001.xhtml @@ -0,0 +1,10 @@ + + +Minimal EPUB + + +
+A beautiful cover +
+ + diff --git a/tests/data/axerule-matching-dpub-role-cover/EPUB/image_001.jpg b/tests/data/axerule-matching-dpub-role-cover/EPUB/image_001.jpg new file mode 100644 index 0000000000000000000000000000000000000000..825a9404d9db8401ad2f5c7e07cf7916549dac9d GIT binary patch literal 18447 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 literal 0 HcmV?d00001 diff --git a/tests/data/axerule-matching-dpub-role-cover/EPUB/nav.xhtml b/tests/data/axerule-matching-dpub-role-cover/EPUB/nav.xhtml new file mode 100644 index 00000000..1d538c19 --- /dev/null +++ b/tests/data/axerule-matching-dpub-role-cover/EPUB/nav.xhtml @@ -0,0 +1,12 @@ + + +Minimal Nav + + + + + diff --git a/tests/data/axerule-matching-dpub-role-cover/EPUB/package.opf b/tests/data/axerule-matching-dpub-role-cover/EPUB/package.opf new file mode 100644 index 00000000..7ee2ba5e --- /dev/null +++ b/tests/data/axerule-matching-dpub-role-cover/EPUB/package.opf @@ -0,0 +1,24 @@ + + + + Minimal EPUB 3.0 + en + NOID + 2017-01-01T00:00:01Z + structuralNavigation + everything OK! + noFlashingHazard + noSoundHazard + noMotionSimulationHazard + textual + textual + + + + + + + + + + diff --git a/tests/data/axerule-matching-dpub-role-cover/META-INF/container.xml b/tests/data/axerule-matching-dpub-role-cover/META-INF/container.xml new file mode 100644 index 00000000..2cf00654 --- /dev/null +++ b/tests/data/axerule-matching-dpub-role-cover/META-INF/container.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/data/axerule-matching-dpub-role-cover/mimetype b/tests/data/axerule-matching-dpub-role-cover/mimetype new file mode 100644 index 00000000..57ef03f2 --- /dev/null +++ b/tests/data/axerule-matching-dpub-role-cover/mimetype @@ -0,0 +1 @@ +application/epub+zip \ No newline at end of file diff --git a/tests/data/axerule-matching-dpub-role/EPUB/content_001.xhtml b/tests/data/axerule-matching-dpub-role/EPUB/content_001.xhtml index 04d6d4ed..675a0e84 100644 --- a/tests/data/axerule-matching-dpub-role/EPUB/content_001.xhtml +++ b/tests/data/axerule-matching-dpub-role/EPUB/content_001.xhtml @@ -3,6 +3,7 @@ Minimal EPUB +

Loomings

Call me Ishmael.

@@ -15,11 +16,17 @@
notice
hello
+ +definition +
notice
notice
+ +
notice
+ diff --git a/tests/data/axerule-matching-dpub-role/EPUB/nav.xhtml b/tests/data/axerule-matching-dpub-role/EPUB/nav.xhtml index 506aabb3..c339fa29 100644 --- a/tests/data/axerule-matching-dpub-role/EPUB/nav.xhtml +++ b/tests/data/axerule-matching-dpub-role/EPUB/nav.xhtml @@ -8,5 +8,11 @@
  • content 001
  • + diff --git a/tests/data/axerule-matching-dpub-role/EPUB/package.opf b/tests/data/axerule-matching-dpub-role/EPUB/package.opf index 17cf544e..ae62abed 100644 --- a/tests/data/axerule-matching-dpub-role/EPUB/package.opf +++ b/tests/data/axerule-matching-dpub-role/EPUB/package.opf @@ -18,6 +18,7 @@ +