From 85ffb9635ba78e94c6fb1da182bbee5264c1f110 Mon Sep 17 00:00:00 2001 From: Romain Deltour Date: Sat, 23 Dec 2017 23:36:12 +0100 Subject: [PATCH] feat(rule): check that epub:type have matching ARIA roles Fixes #70 --- packages/ace-core/src/scripts/ace-axe.js | 85 +++++++++++++ tests/__tests__/axe-rules.test.js | 112 +++++++++++++++--- tests/__tests__/regression.test.js | 1 - .../EPUB/content_001.xhtml | 25 ++++ .../axerule-matching-dpub-role/EPUB/nav.xhtml | 12 ++ .../EPUB/package.opf | 23 ++++ .../META-INF/container.xml | 6 + .../data/axerule-matching-dpub-role/mimetype | 1 + .../epubrules-pagelist/EPUB/content_001.xhtml | 2 +- 9 files changed, 246 insertions(+), 21 deletions(-) create mode 100644 tests/data/axerule-matching-dpub-role/EPUB/content_001.xhtml create mode 100644 tests/data/axerule-matching-dpub-role/EPUB/nav.xhtml create mode 100644 tests/data/axerule-matching-dpub-role/EPUB/package.opf create mode 100644 tests/data/axerule-matching-dpub-role/META-INF/container.xml create mode 100644 tests/data/axerule-matching-dpub-role/mimetype diff --git a/packages/ace-core/src/scripts/ace-axe.js b/packages/ace-core/src/scripts/ace-axe.js index c1bbf5e8..75054b3c 100644 --- a/packages/ace-core/src/scripts/ace-axe.js +++ b/packages/ace-core/src/scripts/ace-axe.js @@ -84,6 +84,82 @@ daisy.ace.run = function(done) { }; window.axe.configure({ + checks: [ + { + id: "matching-aria-role", + evaluate: function evaluate(node, options) { + var mappings = new Map([ + ['abstract', 'doc-abstract'], + ['acknowledgments', 'doc-acknowledgments'], + ['afterword', 'doc-afterword'], + ['appendix', 'doc-appendix'], + ['backlink', 'doc-backlink'], + ['biblioentry', 'doc-biblioentry'], + ['bibliography', 'doc-bibliography'], + ['biblioref', 'doc-biblioref'], + ['chapter', 'doc-chapter'], + ['colophon', 'doc-colophon'], + ['conclusion', 'doc-conclusion'], + ['cover', 'doc-cover'], + ['credit', 'doc-credit'], + ['credits', 'doc-credits'], + ['dedication', 'doc-dedication'], + ['endnote', 'doc-endnote'], + ['endnotes', 'doc-endnotes'], + ['epigraph', 'doc-epigraph'], + ['epilogue', 'doc-epilogue'], + ['errata', 'doc-errata'], + ['footnote', 'doc-footnote'], + ['foreword', 'doc-foreword'], + ['glossary', 'doc-glossary'], + ['glossdef', 'definition'], + ['glossref', 'doc-glossref'], + ['glossterm', 'term'], + ['help', 'doc-tip'], + ['index', 'doc-index'], + ['introduction', 'doc-introduction'], + ['noteref', 'doc-noteref'], + ['notice', 'doc-notice'], + ['pagebreak', 'doc-pagebreak'], + ['page-list', 'doc-pagelist'], + ['part', 'doc-part'], + ['preface', 'doc-preface'], + ['prologue', 'doc-prologue'], + ['pullquote', 'doc-pullquote'], + ['qna', 'doc-qna'], + ['referrer', 'doc-backlink'], + ['subtitle', 'doc-subtitle'], + ['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); + } + } + } + return true; + }, + metadata: { + impact: 'minor', + messages: { + pass: function anonymous(it) { + var out = 'Element has an ARIA role matching its epub:type'; + return out; + }, + fail: function anonymous(it) { + var out = 'Element has no ARIA role matching its epub:type'; + return out; + } + } + } + } + ], rules: [ { id: 'pagebreak-label', @@ -93,6 +169,15 @@ daisy.ace.run = function(done) { description: "Ensure page markers have an accessible label", }, tags: ['cat.epub'] + }, + { + id: 'epub-type-has-matching-role', + selector: '[*|type]', + any: ['matching-aria-role'], + metadata: { + description: "Ensure the element has an ARIA role matching its epub:type", + }, + tags: ['best-practice'] } ] }); diff --git a/tests/__tests__/axe-rules.test.js b/tests/__tests__/axe-rules.test.js index 222faeb6..d8a48116 100644 --- a/tests/__tests__/axe-rules.test.js +++ b/tests/__tests__/axe-rules.test.js @@ -36,6 +36,14 @@ function ace(epub, options = {}) { .catch(err => console.log(err)); } +function findAssertionsForDoc(report, path) { + for (const assertion of report.assertions) { + if(assertion['earl:testSubject'].url === path) { + return assertion.assertions; + } + } +} + test('`bypass` rule is disabled', async () => { const report = await ace('../data/axerule-bypass'); expect(report['earl:result']['earl:outcome']).toEqual('pass'); @@ -49,24 +57,90 @@ test('DPUB ARIA roles are allowed', async () => { test('Ensure page breaks have labels', async () => { const report = await ace('../data/axerule-pagebreak-label'); expect(report['earl:result']['earl:outcome']).toEqual('fail'); - expect(report.assertions).toEqual(expect.arrayContaining([ + const assertions = findAssertionsForDoc(report, 'content_001.xhtml'); + expect(assertions).toBeDefined(); + expect(assertions).toEqual(expect.arrayContaining([ + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'pagebreak-label' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#p3'] }), + }), + }), + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'pagebreak-label' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#p4'] }), + }), + }), + ])); + expect(assertions).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'pagebreak-label' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#p1'] }), + }), + }) + ])); + expect(assertions).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ + 'earl:test': expect.objectContaining({ 'dct:title': 'pagebreak-label' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#p2'] }), + }), + }) + ])); +}); + +test('Checks that `epub:type` have matching ARIA roles', async() => { + const report = await ace('../data/axerule-matching-dpub-role'); + expect(report['earl:result']['earl:outcome']).toEqual('fail'); + const assertions = findAssertionsForDoc(report, 'content_001.xhtml'); + expect(assertions).toBeDefined(); + expect(assertions).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: ['#fail1'] }), + }), + }), expect.objectContaining({ - 'earl:testSubject': expect.objectContaining({ url: 'content_001.xhtml' }), - assertions: [ - expect.objectContaining({ - 'earl:test': expect.objectContaining({ 'dct:title': 'pagebreak-label' }), - 'earl:result': expect.objectContaining({ - 'earl:outcome': 'fail', - 'earl:pointer': expect.objectContaining({ css: ['#p3'] }), - }), - }), - expect.objectContaining({ - 'earl:test': expect.objectContaining({ 'dct:title': 'pagebreak-label' }), - 'earl:result': expect.objectContaining({ - 'earl:outcome': 'fail', - 'earl:pointer': expect.objectContaining({ css: ['#p4'] }), - }), - }), - ], - })])); + 'earl:test': expect.objectContaining({ 'dct:title': 'epub-type-has-matching-role' }), + 'earl:result': expect.objectContaining({ + 'earl:outcome': 'fail', + 'earl:pointer': expect.objectContaining({ css: ['#fail2'] }), + }), + }), + ])); + 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: ['#pass1'] }), + }), + }) + ])); + 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: ['#pass2'] }), + }), + }) + ])); + 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: ['#pass3'] }), + }), + }) + ])); }); diff --git a/tests/__tests__/regression.test.js b/tests/__tests__/regression.test.js index 110f9d1d..5e2dbc61 100644 --- a/tests/__tests__/regression.test.js +++ b/tests/__tests__/regression.test.js @@ -53,7 +53,6 @@ test('issue #57: Failed to execute \'matches\' on \'Element\': \'m:annotation-xm test('issue #85: failed to detect page markers from `epub:type`', async () => { const report = await ace('../data/issue-85'); - expect(report['earl:result']['earl:outcome']).toEqual('pass'); expect(report.properties.hasPageBreaks).toBe(true); }); diff --git a/tests/data/axerule-matching-dpub-role/EPUB/content_001.xhtml b/tests/data/axerule-matching-dpub-role/EPUB/content_001.xhtml new file mode 100644 index 00000000..04d6d4ed --- /dev/null +++ b/tests/data/axerule-matching-dpub-role/EPUB/content_001.xhtml @@ -0,0 +1,25 @@ + + +Minimal EPUB + + +

Loomings

+

Call me Ishmael.

+ + + +
    +
  1. item 1
  2. +
+ +
notice
+ +
hello
+ + + +
notice
+ +
notice
+ + diff --git a/tests/data/axerule-matching-dpub-role/EPUB/nav.xhtml b/tests/data/axerule-matching-dpub-role/EPUB/nav.xhtml new file mode 100644 index 00000000..506aabb3 --- /dev/null +++ b/tests/data/axerule-matching-dpub-role/EPUB/nav.xhtml @@ -0,0 +1,12 @@ + + +Minimal Nav + + + + + diff --git a/tests/data/axerule-matching-dpub-role/EPUB/package.opf b/tests/data/axerule-matching-dpub-role/EPUB/package.opf new file mode 100644 index 00000000..17cf544e --- /dev/null +++ b/tests/data/axerule-matching-dpub-role/EPUB/package.opf @@ -0,0 +1,23 @@ + + + + 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/META-INF/container.xml b/tests/data/axerule-matching-dpub-role/META-INF/container.xml new file mode 100644 index 00000000..2cf00654 --- /dev/null +++ b/tests/data/axerule-matching-dpub-role/META-INF/container.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/data/axerule-matching-dpub-role/mimetype b/tests/data/axerule-matching-dpub-role/mimetype new file mode 100644 index 00000000..57ef03f2 --- /dev/null +++ b/tests/data/axerule-matching-dpub-role/mimetype @@ -0,0 +1 @@ +application/epub+zip \ No newline at end of file diff --git a/tests/data/epubrules-pagelist/EPUB/content_001.xhtml b/tests/data/epubrules-pagelist/EPUB/content_001.xhtml index f9aaf815..db05b999 100644 --- a/tests/data/epubrules-pagelist/EPUB/content_001.xhtml +++ b/tests/data/epubrules-pagelist/EPUB/content_001.xhtml @@ -5,6 +5,6 @@

Loomings

Call me Ishmael.

- +