From a5452c3b5196658a3f1c0cc111dd658e9c5d70ea Mon Sep 17 00:00:00 2001 From: Romain Deltour Date: Thu, 4 Jul 2019 11:32:18 +0200 Subject: [PATCH] feat: check expected values of schema.org a11y metadata Fixes #202 --- packages/ace-core/src/checker/checker-epub.js | 136 ++++++++++++++++-- packages/ace-core/src/l10n/locales/en.json | 6 + packages/ace-core/src/l10n/locales/fr.json | 6 + tests/__tests__/axe-rules.test.js | 9 +- tests/__tests__/epub-rules.test.js | 29 ++++ .../epubrules-metadata/EPUB/content_001.xhtml | 9 ++ tests/data/epubrules-metadata/EPUB/nav.xhtml | 12 ++ .../data/epubrules-metadata/EPUB/package.opf | 25 ++++ .../epubrules-metadata/META-INF/container.xml | 6 + tests/data/epubrules-metadata/mimetype | 1 + tests/utils.js | 9 ++ 11 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 tests/data/epubrules-metadata/EPUB/content_001.xhtml create mode 100644 tests/data/epubrules-metadata/EPUB/nav.xhtml create mode 100644 tests/data/epubrules-metadata/EPUB/package.opf create mode 100644 tests/data/epubrules-metadata/META-INF/container.xml create mode 100644 tests/data/epubrules-metadata/mimetype create mode 100644 tests/utils.js diff --git a/packages/ace-core/src/checker/checker-epub.js b/packages/ace-core/src/checker/checker-epub.js index eba04a89..53c3f807 100644 --- a/packages/ace-core/src/checker/checker-epub.js +++ b/packages/ace-core/src/checker/checker-epub.js @@ -9,6 +9,100 @@ const ASSERTED_BY = 'Ace'; const MODE = 'automatic'; const KB_BASE = 'http://kb.daisy.org/publishing/'; +const A11Y_META = { + 'schema:accessMode': { + required: true, + allowedValues: [ + 'auditory', + 'chartOnVisual', + 'chemOnVisual', + 'colorDependent', + 'diagramOnVisual', + 'mathOnVisual', + 'musicOnVisual', + 'tactile', + 'textOnVisual', + 'textual', + 'visual', + ] + }, + 'schema:accessModeSufficient': { + recommended: true, + allowedValues: [ + 'auditory', + 'tactile', + 'textual', + 'visual', + ] + }, + 'schema:accessibilityAPI': { + allowedValues: [ + 'ARIA' + ] + }, + 'schema:accessibilityControl': { + allowedValues: [ + 'fullKeyboardControl', + 'fullMouseControl', + 'fullSwitchControl', + 'fullTouchControl', + 'fullVideoControl', + 'fullVoiceControl', + ] + }, + 'schema:accessibilityFeature': { + required: true, + allowedValues: [ + 'alternativeText', + 'annotations', + 'audioDescription', + 'bookmarks', + 'braille', + 'captions', + 'ChemML', + 'describedMath', + 'displayTransformability', + 'highContrastAudio', + 'highContrastDisplay', + 'index', + 'largePrint', + 'latex', + 'longDescription', + 'MathML', + 'none', + 'printPageNumbers', + 'readingOrder', + 'rubyAnnotations', + 'signLanguage', + 'structuralNavigation', + 'synchronizedAudioText', + 'tableOfContents', + 'taggedPDF', + 'tactileGraphic', + 'tactileObject', + 'timingControl', + 'transcript', + 'ttsMarkup', + 'unlocked ', + ], + }, + 'schema:accessibilityHazard': { + allowedValues: [ + 'flashing', + 'noFlashingHazard', + 'motionSimulation', + 'noMotionSimulationHazard', + 'sound', + 'noSoundHazard', + 'unknown', + 'none', + ] + }, + 'schema:accessibilitySummary': { + required: true, + } +}; + function asString(arrayOrString) { if (Array.isArray(arrayOrString) && arrayOrString.length > 0) { return asString(arrayOrString[0]); @@ -43,7 +137,7 @@ function newViolation({ impact = 'serious', title, testDesc, resDesc, kbPath, kb function newMetadataAssertion(name, impact = 'serious') { return newViolation({ impact, - title: `metadata-${name.toLowerCase().replace(':', '-')}`, + title: `metadata-${name.toLowerCase().replace('schema:', '')}`, testDesc: localize("checkepub.metadataviolation.testdesc", { name, interpolation: { escapeValue: false } }), resDesc: localize("checkepub.metadataviolation.resdesc", { name, interpolation: { escapeValue: false } }), kbPath: 'docs/metadata/schema-org.html', @@ -53,18 +147,34 @@ function newMetadataAssertion(name, impact = 'serious') { } function checkMetadata(assertions, epub) { - // Required metadata - [ - 'schema:accessMode', - 'schema:accessibilityFeature', - 'schema:accessibilitySummary', - ].filter(meta => epub.metadata[meta] === undefined) - .forEach(meta => assertions.withAssertions(newMetadataAssertion(meta))); - // Recommended metadata - [ - 'schema:accessModeSufficient', - ].filter(meta => epub.metadata[meta] === undefined) - .forEach(meta => assertions.withAssertions(newMetadataAssertion(meta, 'moderate'))); + // Check metadata values + for (const name in A11Y_META) { + const meta = A11Y_META[name]; + var values = epub.metadata[name]; + if (values === undefined) { + // Report missing metadata if it is required or recommended + if (meta.required) assertions.withAssertions(newMetadataAssertion(name)); + if (meta.recommended) assertions.withAssertions(newMetadataAssertion(name, 'moderate')); + } else if (meta.allowedValues) { + // Check metadata values are allowed + // see https://www.w3.org/wiki/WebSchemas/Accessibility + if (!Array.isArray(values)) { + values = [values] + } + values.filter(value => !meta.allowedValues.includes(value)) + .forEach(value => { + assertions.withAssertions(newViolation({ + impact: 'moderate', + title: `metadata-${name.toLowerCase().replace('schema:', '')}-invalid`, + testDesc: localize("checkepub.metadatainvalid.testdesc", { value, name, interpolation: { escapeValue: false } }), + resDesc: localize("checkepub.metadatainvalid.resdesc", { name, interpolation: { escapeValue: false } }), + kbPath: 'docs/metadata/schema-org.html', + kbTitle: localize("checkepub.metadatainvalid.kbtitle"), + ruleDesc: localize("checkepub.metadatainvalid.ruledesc", { name, interpolation: { escapeValue: false } }) + })) + }); + } + } } function checkTitle(assertions, epub) { diff --git a/packages/ace-core/src/l10n/locales/en.json b/packages/ace-core/src/l10n/locales/en.json index 93b8aa0b..c9b45181 100644 --- a/packages/ace-core/src/l10n/locales/en.json +++ b/packages/ace-core/src/l10n/locales/en.json @@ -6,6 +6,12 @@ "ruledesc": "Publications must declare the '{{name}}' metadata", "kbtitle": "Schema.org Accessibility Metadata" }, + "metadatainvalid": { + "testdesc": "Value '{{value}}' is invalid for '{{name}}' metadata", + "resdesc": "Use one of the metadata values defined by schema.org", + "ruledesc": "'{{name}}' metadata must be set to one of the expected values", + "kbtitle": "Schema.org Accessibility Metadata" + }, "titleviolation": { "testdesc": "Ensures the EPUB has a title", "resdesc": "Add a 'dc:title' metadata property to the Package Document", diff --git a/packages/ace-core/src/l10n/locales/fr.json b/packages/ace-core/src/l10n/locales/fr.json index 3d68bee8..35e2e0cb 100644 --- a/packages/ace-core/src/l10n/locales/fr.json +++ b/packages/ace-core/src/l10n/locales/fr.json @@ -6,6 +6,12 @@ "ruledesc": "Toute publication doit déclarer la métadonnée '{{name}}'", "kbtitle": "Métadonnées d'accessibilité Schema.org" }, + "metadatainvalid": { + "testdesc": "La valeur '{{value}}' est incorrecte pour la métadonnée '{{name}}'", + "resdesc": "Utiliser l’une des valeurs définie par schema.org", + "ruledesc": "La métadonnée '{{name}}' doit avoir une valeur parmi les valeurs prédéfinies", + "kbtitle": "Métadonnées d'accessibilité Schema.org" + }, "titleviolation": { "testdesc": "Vérifie que le EPUB a un titre", "resdesc": "Ajouter la métadonnée 'dc:title' au Document de Package", diff --git a/tests/__tests__/axe-rules.test.js b/tests/__tests__/axe-rules.test.js index ff2d650c..d0fea049 100644 --- a/tests/__tests__/axe-rules.test.js +++ b/tests/__tests__/axe-rules.test.js @@ -5,6 +5,7 @@ const path = require('path'); const tmp = require('tmp'); const runAce = require('../runAceJS'); +import { findAssertionsForDoc } from "../utils"; tmp.setGracefulCleanup(); @@ -38,14 +39,6 @@ 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'); diff --git a/tests/__tests__/epub-rules.test.js b/tests/__tests__/epub-rules.test.js index a78139b4..2a9718b7 100644 --- a/tests/__tests__/epub-rules.test.js +++ b/tests/__tests__/epub-rules.test.js @@ -5,6 +5,7 @@ const path = require('path'); const tmp = require('tmp'); const runAce = require('../runAceJS'); +import { findAssertionsForDoc } from "../utils"; tmp.setGracefulCleanup(); @@ -50,3 +51,31 @@ describe('page list and breaks', () => { expect(report['earl:result']['earl:outcome']).toEqual('pass'); }); }); + +describe('accessibility metadata', () => { + + test('missing metadata is reported', async () => { + const report = await ace('../data/epubrules-metadata'); + expect(report['earl:result']['earl:outcome']).toEqual('fail'); + const assertions = findAssertionsForDoc(report, 'EPUB/package.opf'); + expect(assertions).toEqual(expect.arrayContaining([ + expect.objectContaining({ + 'earl:test': expect.objectContaining({ + 'dct:title': 'metadata-accessibilitysummary', + }), + }), + ])); + }); + test('incorrect metadata value is reported', async () => { + const report = await ace('../data/epubrules-metadata'); + expect(report['earl:result']['earl:outcome']).toEqual('fail'); + const assertions = findAssertionsForDoc(report, 'EPUB/package.opf'); + expect(assertions).toEqual(expect.arrayContaining([ + expect.objectContaining({ + 'earl:test': expect.objectContaining({ + 'dct:title': 'metadata-accessmode-invalid', + }), + }), + ])); + }); +}); diff --git a/tests/data/epubrules-metadata/EPUB/content_001.xhtml b/tests/data/epubrules-metadata/EPUB/content_001.xhtml new file mode 100644 index 00000000..c55a9e8b --- /dev/null +++ b/tests/data/epubrules-metadata/EPUB/content_001.xhtml @@ -0,0 +1,9 @@ + + +Minimal EPUB + + +

Loomings

+

Call me Ishmael.

+ + diff --git a/tests/data/epubrules-metadata/EPUB/nav.xhtml b/tests/data/epubrules-metadata/EPUB/nav.xhtml new file mode 100644 index 00000000..1d538c19 --- /dev/null +++ b/tests/data/epubrules-metadata/EPUB/nav.xhtml @@ -0,0 +1,12 @@ + + +Minimal Nav + + + + + diff --git a/tests/data/epubrules-metadata/EPUB/package.opf b/tests/data/epubrules-metadata/EPUB/package.opf new file mode 100644 index 00000000..0e136fac --- /dev/null +++ b/tests/data/epubrules-metadata/EPUB/package.opf @@ -0,0 +1,25 @@ + + + + Minimal EPUB 3.0 + en + NOID + 2017-01-01T00:00:01Z + structuralNavigation + + + noFlashingHazard + noSoundHazard + noMotionSimulationHazard + + text + textual + + + + + + + + + diff --git a/tests/data/epubrules-metadata/META-INF/container.xml b/tests/data/epubrules-metadata/META-INF/container.xml new file mode 100644 index 00000000..2cf00654 --- /dev/null +++ b/tests/data/epubrules-metadata/META-INF/container.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/data/epubrules-metadata/mimetype b/tests/data/epubrules-metadata/mimetype new file mode 100644 index 00000000..57ef03f2 --- /dev/null +++ b/tests/data/epubrules-metadata/mimetype @@ -0,0 +1 @@ +application/epub+zip \ No newline at end of file diff --git a/tests/utils.js b/tests/utils.js new file mode 100644 index 00000000..7b9fb734 --- /dev/null +++ b/tests/utils.js @@ -0,0 +1,9 @@ +'use strict'; + +export function findAssertionsForDoc(report, path) { + for (const assertion of report.assertions) { + if (assertion['earl:testSubject'].url === path) { + return assertion.assertions; + } + } +}