diff --git a/build/create-glossary-usages.js b/build/create-glossary-usages.js new file mode 100644 index 0000000000..d47bb31837 --- /dev/null +++ b/build/create-glossary-usages.js @@ -0,0 +1,89 @@ +/** + * Create glossary usages + * -> for each glossary item (find references in each rule) + * -> this is saved in `_data` which is later used in `pages/glossary` + */ +const globby = require('globby') +const regexps = require('../utils/reg-exps') +const createFile = require('../utils/create-file') +const getAllMatchesForRegex = require('../utils/get-all-matches-for-regex') +const getMarkdownData = require('../utils/get-markdown-data') + +const init = async () => { + /** + * Get all rules `markdown` data + */ + const rulesData = globby + .sync([`./_rules/*.md`]) + .map(rulePath => getMarkdownData(rulePath)) + + /** + * Eg: + * { + * `non-empty`: [ + * { name: `aria valid ...`, slug: `rules/XXXXX` }, + * .... + * ] + * .... + * } + */ + const glossaryUsages = {} + + rulesData.forEach(ruleData => { + const { frontmatter, body } = ruleData + const { + id: ruleId, + name: ruleName, + accessibility_requirements: ruleAccessibilityRequirements, + } = frontmatter + + const glossaryMatches = getAllMatchesForRegex( + regexps.glossaryReferenceInRules, + body, + false + ) + + glossaryMatches.forEach(glossaryItem => { + const hasGlossaryKey = regexps.glossaryKey.test(glossaryItem.block) + if (!hasGlossaryKey) { + return + } + + const key = glossaryItem.block.match(regexps.glossaryKey)[1] + if (!key) { + return + } + + const usage = { + name: ruleName, + slug: `rules/${ruleId}`, + } + if (!glossaryUsages[key]) { + glossaryUsages[key] = [usage] + return + } + + const exists = glossaryUsages[key].some(u => u.slug === usage.slug) + if (exists) { + return + } + + glossaryUsages[key] = glossaryUsages[key].concat(usage) + }) + }) + + /** + * Create `_data/glossary-usages.json` + */ + await createFile( + `./_data/glossary-usages.json`, + JSON.stringify(glossaryUsages, undefined, 2) + ) +} + +/** + * Invoke + */ +init() + .then(() => console.info(`Completed: task: create:glossary\n`)) + .catch(e => console.error(e)) diff --git a/build/create-implementation-metrics.js b/build/create-implementation-metrics.js new file mode 100644 index 0000000000..eba6cf360a --- /dev/null +++ b/build/create-implementation-metrics.js @@ -0,0 +1,81 @@ +const globby = require('globby') +const readFile = require('../utils/read-file') +const createFile = require('../utils/create-file') + +/** + * Init + */ +const init = async () => { + /** + * Get all implementation reports + */ + const reports = globby + .sync([`./_data/implementations/*.json`]) + .map(reportPath => { + const fileContent = readFile(reportPath) + return JSON.parse(fileContent) + }) + + const implementers = [] + const implementationsGroupedByRuleId = {} + + reports.forEach(report => { + const { tool, organisation, data } = report + + /** + * Create data that can be used in `src/templates/coverage.js` + */ + implementers.push(report) + + /** + * Iterate each implementation & group by rule id + */ + data.forEach(({ ruleId, implementation }) => { + if (!implementation) { + return + } + + /** + * Note: + * only build `metrics` for implementations that are `complete` + */ + const { complete = false } = implementation + if (complete) { + return + } + + if (!implementationsGroupedByRuleId[ruleId]) { + implementationsGroupedByRuleId[ruleId] = [] + } + + implementationsGroupedByRuleId[ruleId].push({ + organisation, + tool, + implementation, + }) + }) + }) + + /** + * Create `implementations.json` + */ + await createFile( + `_data/implementers.json`, + JSON.stringify(implementers, null, 2) + ) + + /** + * Create metrics in `_data` for usage in `site` + */ + await createFile( + `_data/implementation-metrics.json`, + JSON.stringify(implementationsGroupedByRuleId, null, 2) + ) +} + +init() + .then(() => console.info(`\nImplementation metrics generated.\n`)) + .catch(e => { + console.error(e) + process.exit(1) + }) diff --git a/build/create-testcases.js b/build/create-testcases.js new file mode 100644 index 0000000000..e060d67003 --- /dev/null +++ b/build/create-testcases.js @@ -0,0 +1,149 @@ +const globby = require('globby') +const makeDir = require('make-dir') +const objectHash = require('object-hash') +const codeBlocks = require('gfm-code-blocks') +const { + www: { url, baseDir }, +} = require('./../package.json') +const getMarkdownData = require('./../utils/get-markdown-data') +const createFile = require('../utils/create-file') +const regexps = require('../utils/reg-exps') +const getAllMatchesForRegex = require('../utils/get-all-matches-for-regex') +const copyTestcasesAssets = require('./testcases/copy-testcases-assets') +const createTestcasesJson = require('./testcases/create-testcases-json') +const createTestcasesOfRuleOfEmReportTool = require('./testcases/create-testcases-of-rule-of-em-report-tool') + +/** + * Create test case files & other meta-data from test case in each rule. + * + * -> create test cases files into `./public/testcases/` + * -> copy `./test-assets/*` into `./public` + * -> create `testcases.json` into `./public` + */ +const init = async () => { + /** + * Create `public` directory + */ + await makeDir(`public`) + + /** + * Get all rules `markdown` data + */ + const rulesData = globby + .sync([`./_rules/*.md`]) + .map(rulePath => getMarkdownData(rulePath)) + + let allRulesTestcases = [] + + /** + * iterate all rule pages + * -> get code snippets + * -> and their relevant titles + */ + rulesData.forEach(ruleData => { + const { frontmatter, body } = ruleData + const { + id: ruleId, + name: ruleName, + accessibility_requirements: ruleAccessibilityRequirements, + } = frontmatter + const codeTitles = getAllMatchesForRegex(regexps.testcaseTitle, body) + + /** + * get code blocks in markdown body + */ + const codeSnippets = codeBlocks(body) + + if (codeTitles.length !== codeSnippets.length) { + throw new Error( + `Number of matching titles for code snippets is wrong. Check markdown '${ruleName}' for irregularities.` + ) + } + + /** + * iterate each code snippet + * -> create a testcase file + * -> and add meta of testcase to `testcases.json` + */ + const ruleTestcases = codeSnippets.reduce((out, codeBlock, index) => { + const title = codeTitles[index] + if (!title) { + throw new Error('No title found for code snippet.') + } + + const { code, block } = codeBlock + let { type = 'html' } = codeBlock + + if (regexps.testcaseCodeSnippetTypeIsSvg.test(block.substring(0, 15))) { + type = 'svg' + } + + const codeId = objectHash({ + block, + type, + ruleId, + }) + + const titleCurated = title.value.split(' ').map(t => t.toLowerCase()) + + const testcaseFileName = `${ruleId}/${codeId}.${type}` + const testcasePath = `testcases/${testcaseFileName}` + /** + * Create testcase file + */ + createFile(`${baseDir}/${testcasePath}`, code) + + /** + * Create meta data for testcase(s) + */ + const testcase = { + testcaseId: codeId, + url: `${url}/${testcasePath}`, + relativePath: testcasePath, + expected: titleCurated[0], + ruleId, + ruleName, + rulePage: `${url}/rules/${ruleId}`, + ruleAccessibilityRequirements, + } + + out.push(testcase) + return out + }, []) + + // add rule testcases to all testcases + allRulesTestcases = allRulesTestcases.concat(ruleTestcases) + + /** + * Create test cases of rule for use with `em report tool` + */ + createTestcasesOfRuleOfEmReportTool({ + ruleId, + ruleName, + ruleTestcases, + ruleAccessibilityRequirements, + }) + }) + + /** + * Copy `test-assets` that are used by `testcases` + */ + await copyTestcasesAssets() + + /** + * Generate `testcases.json` + */ + await createTestcasesJson(allRulesTestcases) + + console.info(`\nGenerated Test Cases.\n`) +} + +/** + * Invoke + */ +init() + .then(() => console.log('Completed: task: create:testcases')) + .catch(e => { + console.error(e) + process.write(1) + }) diff --git a/build/get-implementation.js b/build/get-implementation.js new file mode 100644 index 0000000000..4aae3970a1 --- /dev/null +++ b/build/get-implementation.js @@ -0,0 +1,74 @@ +const assert = require('assert') +const program = require('commander') +const { version } = require('../package.json') +const createFile = require('../utils/create-file') +const getFramedReport = require('./implementations/get-framed-report') +const getImplementationForReport = require('./implementations/get-implementation-for-report') + +/** + * Init + * @param {Object} program program + */ +const init = async program => { + const { org, tool, path } = program + + /** + * assert `args` + */ + assert(org, '`Organisation` is required') + assert(tool, '`tool` is required') + assert(path, '`path` is required') + + console.info(`\nGet implementation of ${tool} by ${org}\n`) + + /** + * fetch `report` & `frame` as required + */ + const framedReport = await getFramedReport(path) + + /** + * Get `implementation` + */ + const data = await getImplementationForReport(framedReport) + + /** + * create report + */ + const report = { + organisation: org, + tool, + data, + } + + /** + * Save `implementation` to `_data/implementations` + */ + const filename = tool + .split(' ') + .join('-') + .toLowerCase() + await createFile( + `_data/implementations/${filename}.json`, + JSON.stringify(report, null, 2) + ) +} + +/** + * Parse `args` + */ +program + .version(version) + .option('-o, --org ', 'Organisation, which created the EARL report') + .option('-t, --tool ', 'Tool used by EARL report') + .option('-p, --path ', 'Path to EARL report') + .parse(process.argv) + +/** + * Init + */ +init(program) + .then(() => console.info(`\nImplementations data generated.\n`)) + .catch(e => { + console.error(e) + process.exit(1) + }) diff --git a/build/get-implementations.js b/build/get-implementations.js deleted file mode 100644 index cabe1fb11b..0000000000 --- a/build/get-implementations.js +++ /dev/null @@ -1,70 +0,0 @@ -const path = require('path') -const axios = require('axios') -const jsonld = require('jsonld') -const pkg = require('./../package.json') -const jsonLdFrameConfig = require('./implementation-json-ld-frame') -const createFile = require('./create-file') -const outputFile = path.join(__dirname, '..', '_data', 'implementations.json') - -/** - * Transform data to JSONLD frame - * @param {Object} data data - * @param {String} reportUrl url - */ -const getFramedResult = async (data, url) => { - return new Promise((resolve, reject) => { - jsonld.frame(data, jsonLdFrameConfig, (err, result) => { - if (err) { - reject(err) - } - result['reportUrl'] = url - resolve(result) - }) - }) -} - -/** - * Given report data - * - * @param {String} url resource endpoint for the report - */ -const getReportData = url => { - return axios.get(url).then(async response => { - const { data } = response - return await getFramedResult(data, url) - }) -} - -/** - * Tabulate implementation data - * @param {Array} data - */ -const tabulateImplementationData = data => { - return data.map(implementation => { - const graph = implementation['@graph'] - if (!graph || !graph.length) { - return - } - const assertedBy = graph[0]['assertedBy'] - return { - vendorName: assertedBy['vendor']['foaf:name'], - vendorTool: assertedBy['vendorTool'], - vendorToolVersion: assertedBy['@id'].split('/').reverse()[0], - reportUrl: implementation.reportUrl, - } - }) -} - -;(async () => { - const implementations = pkg.config.implementations - if (!implementations || Object.keys(implementations).length <= 0) { - throw new Error('No implementations are specified in config.') - } - - const reports = Object.values(implementations) - const promises = reports.map(getReportData) - const result = await Promise.all(promises) - const tabulatedData = tabulateImplementationData(result) - createFile(outputFile, JSON.stringify(tabulatedData, undefined, 2)) - console.info('\nDONE!!! Generated Implementations Data.\n') -})() diff --git a/build/get-wcag-sc-metadata.js b/build/get-wcag-data.js similarity index 73% rename from build/get-wcag-sc-metadata.js rename to build/get-wcag-data.js index 3f4cd7aed7..51f6c95b10 100644 --- a/build/get-wcag-sc-metadata.js +++ b/build/get-wcag-data.js @@ -5,7 +5,7 @@ */ const path = require('path') const axios = require('axios') -const createFile = require('./create-file') +const createFile = require('../utils/create-file') const pkg = require('./../package.json') const outputFileScMetaData = path.join(__dirname, '..', '_data', 'sc-urls.json') const outputFileScEmReportAuditResult = path.join( @@ -15,6 +15,10 @@ const outputFileScEmReportAuditResult = path.join( 'sc-em-report-audit-result.json' ) +/** + * Determine if a given success criteria is 2.0 + * @param {Object} sc success criterion + */ const isScWcag20 = sc => { const is20 = !( sc.versions && @@ -24,6 +28,10 @@ const isScWcag20 = sc => { return is20 } +/** + * Get enhanced meta data of success criterion + * @param {Object} sc success criteria + */ const getMetaData = sc => { const urlPrefix = `https://www.w3.org/TR/WCAG` const is20 = isScWcag20(sc) @@ -34,12 +42,12 @@ const getMetaData = sc => { is20 ? 'http://www.w3.org/WAI/WCAG20/quickref/#qr-' : 'https://www.w3.org/WAI/WCAG21/quickref/#' - }${path}` + }${path}` const understandingUrl = `${ is20 ? 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/' : 'https://www.w3.org/WAI/WCAG21/Understanding/' - }/${path}.html` + }/${path}.html` /** * Construct `test` - used by `wcag em report tool` */ @@ -59,10 +67,14 @@ const getMetaData = sc => { } } -const getScMetaData = async url => { - const { data } = await axios.get(url) +/** + * Get all WCAG SC reference data + * @param {String} url URL + */ +const getWaiWcagReferenceData = async url => { + const { data: { principles } } = await axios.get(url) + const scMetaData = {} - const { principles } = data principles.forEach(p => p.guidelines.forEach(g => g.successcriteria.forEach(sc => { @@ -73,8 +85,12 @@ const getScMetaData = async url => { return scMetaData } -;(async () => { +/** + * Init + */ +const init = async () => { const wcagReferenceUrl = pkg.config.references.wcag21 + if (!wcagReferenceUrl) { throw new Error('No reference URL for WCAG21 is specified in config.') } @@ -82,11 +98,8 @@ const getScMetaData = async url => { /** * Create a list of success criteria meta data */ - const scMetaData = await getScMetaData(wcagReferenceUrl) - await createFile( - outputFileScMetaData, - JSON.stringify(scMetaData, undefined, 2) - ) + const scMetaData = await getWaiWcagReferenceData(wcagReferenceUrl) + await createFile(outputFileScMetaData, JSON.stringify(scMetaData, undefined, 2)) /** * Create wcag em report tool friendly audit result array @@ -106,10 +119,13 @@ const getScMetaData = async url => { hasPart: [], } }) - await createFile( - outputFileScEmReportAuditResult, - JSON.stringify(scEmReportAuditResult, undefined, 2) - ) - console.info('\nDONE!!! Generated WCAG Success Criterion Data.\n') -})() + await createFile(outputFileScEmReportAuditResult, JSON.stringify(scEmReportAuditResult, undefined, 2)) +} + +/** + * Invoke + */ +init() + .then(() => console.info('Completed: task: get:wcag:data.\n')) + .catch(e => console.error(e)) diff --git a/build/implementations/get-assertion-source.js b/build/implementations/get-assertion-source.js new file mode 100644 index 0000000000..7010f56368 --- /dev/null +++ b/build/implementations/get-assertion-source.js @@ -0,0 +1,23 @@ +const flat = require('flat') +const isUrl = require('is-url') +const { + www: { url: siteUrl }, +} = require('./../../package.json') + +/** + * Given an assertion object + * - get `source`, which resembles closest to the url of the testcase + * + * Achieved by + * - flatten the given object + * - and verity if values are of type `url` and has `siteUrl` + * @param {Object} assertion assertion + */ +const getAssertionSource = assertion => { + const flattenedAssertion = flat(assertion) + return Object.values(flattenedAssertion).find(value => { + return isUrl(value) && value.includes(siteUrl) + }) +} + +module.exports = getAssertionSource diff --git a/build/implementations/get-assertions.js b/build/implementations/get-assertions.js new file mode 100644 index 0000000000..678678f957 --- /dev/null +++ b/build/implementations/get-assertions.js @@ -0,0 +1,18 @@ +/** + * Get assertions from framed reports + * + * @param {Object|Array} framedReports implementation reports + */ +const getAssertions = framedReports => { + if(!framedReports) { + throw new Error('argument report is expected') + } + const reports = Array.isArray(framedReports) ? framedReports : [framedReports] + + return reports.reduce((out, report) => { + out.push(...report[`@graph`]) + return out + }, []) +} + +module.exports = getAssertions diff --git a/build/implementations/get-best-matching-rules.js b/build/implementations/get-best-matching-rules.js new file mode 100644 index 0000000000..5e2e14299b --- /dev/null +++ b/build/implementations/get-best-matching-rules.js @@ -0,0 +1,20 @@ +/** + * Given a set of assertion results, get best fit, based on outcome mapping + * + * @param {Array} ruleAsserts mapped assertions + */ +const getBestMatchingRules = ruleAsserts => { + const mappedRules = ruleAsserts.filter(({ mapping }) => mapping !== false) + if (!mappedRules) { + return + } + + const completeRules = mappedRules.filter(({ complete }) => complete === true) + if (!completeRules.length) { + return mappedRules + } + + return completeRules +} + +module.exports = getBestMatchingRules diff --git a/build/implementations/get-framed-report.js b/build/implementations/get-framed-report.js new file mode 100644 index 0000000000..051b3c1236 --- /dev/null +++ b/build/implementations/get-framed-report.js @@ -0,0 +1,34 @@ +const globby = require('globby') +const axios = require('axios') +const jsonld = require('jsonld') +const isUrl = require('is-url') +const frameConfig = require('./json-ld-frame-config') +const readFile = require('../../utils/read-file') + +const getFramedReport = async path => { + + if(!path) { + throw new Error('Path should be provided from which report has to be obtained.'); + } + + if (isUrl(path)) { + const { data } = await axios.get(path) + return await jsonld.frame(data, frameConfig) + } + + const reportFiles = globby.sync([path]); + const reports = reportFiles.map(reportPath => { + const fileContent = readFile(reportPath) + return JSON.parse(fileContent) + }) + + const result = [] + for (let report of reports) { + const framedReport = await jsonld.frame(report, frameConfig) + result.push(framedReport) + } + + return result +} + +module.exports = getFramedReport diff --git a/build/implementations/get-implementation-for-report.js b/build/implementations/get-implementation-for-report.js new file mode 100644 index 0000000000..dfdbd25515 --- /dev/null +++ b/build/implementations/get-implementation-for-report.js @@ -0,0 +1,27 @@ +const getAssertions = require('./get-assertions') +const getTestcasesGroupedByRule = require('./get-testcases-grouped-by-rule') +const getRuleMapping = require('./get-rule-mapping') + +/** + * Get implementation metric from submitted report + * + * @param {Object|Array} reports implementation reports + */ +const getImplementationForReport = async reports => { + const assertions = getAssertions(reports) + const testcasesGroupedByRuleId = await getTestcasesGroupedByRule() + + return Object.keys(testcasesGroupedByRuleId) + .map(ruleId => { + const ruleTestcases = testcasesGroupedByRuleId[ruleId] + const implementation = getRuleMapping(ruleTestcases, assertions) + return { + ruleId, + ruleName: ruleTestcases[0].ruleName, + implementation, + } + }) + .filter(result => result.implementation && result.implementation.length) +} + +module.exports = getImplementationForReport diff --git a/build/implementations/get-rule-mapping-state.js b/build/implementations/get-rule-mapping-state.js new file mode 100644 index 0000000000..61a84d57ef --- /dev/null +++ b/build/implementations/get-rule-mapping-state.js @@ -0,0 +1,51 @@ +const assert = require('assert') + +const outcomeMapping = { + failed: ['failed', 'cantTell'], + fail: ['failed', 'cantTell'], + passed: ['passed', 'cantTell', 'inapplicable'], + pass: ['passed', 'cantTell', 'inapplicable'], + inapplicable: ['passed', 'cantTell', 'inapplicable', 'untested'], +} + +/** + * Get rule state based on given assertions + * + * @param {Array} assertions + */ +const getRuleMappingState = assertions => { + const mapping = assertions.some( + ({ actual, expected }) => + expected === 'failed' && ['failed', 'cantTell'].includes(actual) + ) + + if (!mapping) { + return { mapping: false } + } + + const complete = assertions.every(({ expected, actual }) => { + return expected !== 'failed' || actual === 'failed' + }) + + const incorrect = assertions + .filter(data => { + const { expected, actual } = data + assert(outcomeMapping[expected], `Unknown result type ${expected}`) + + return !outcomeMapping[expected].includes(actual) + }) + .map(({ url }) => url) + + // const fullAuto = undefined + + return { + id: assertions[0].title, + complete, + incorrect, + assertions: assertions.map(({ expected, actual, url }) => { + return { expected, actual, url } + }), + } +} + +module.exports = getRuleMappingState diff --git a/build/implementations/get-rule-mapping.js b/build/implementations/get-rule-mapping.js new file mode 100644 index 0000000000..159ce5e3f6 --- /dev/null +++ b/build/implementations/get-rule-mapping.js @@ -0,0 +1,58 @@ +const flat = require('flat') +const getTestcaseRelativeUrl = require('./get-testcase-relative-url') +const getRuleMappingState = require('./get-rule-mapping-state') +const getBestMatchingRules = require('./get-best-matching-rules') +const getTestcaseAssertions = require('./get-testcase-assertions') +const getTestCaseMapping = require('./get-testcase-mapping') + +function getRuleMapping(testcases, assertions) { + const ruleData = {} + + testcases.forEach(tc => { + const relativeUrl = getTestcaseRelativeUrl(tc.url) + const tcAssertions = getTestcaseAssertions(assertions, relativeUrl) + + // Create a mapping for each assertion + tcAssertions.forEach(assertion => { + const testMapping = getTestCaseMapping(assertion, tc) + if (!testMapping || testMapping.actual === 'untested') { + return + } + if (!ruleData[testMapping.title]) { + ruleData[testMapping.title] = [] + } + ruleData[testMapping.title].push(testMapping) + }) + }) + + for (tc of testcases) { + const relativeUrl = getTestcaseRelativeUrl(tc.url) + + // Push untested results for every test case without an assertion + Object.values(ruleData).forEach(testMappings => { + if ( + !Object.values(testMappings).some( + ({ url }) => url && url.includes(relativeUrl) + ) + ) { + testMappings.push({ + title: testMappings[0].title, + expected: tc.expected, + actual: 'untested', + url: tc.url, + }) + } + }) + } + + const ruleAsserts = Object.values(ruleData).map(data => { + const mappingState = getRuleMappingState(data) + return { + ...mappingState, + } + }) + + return getBestMatchingRules(ruleAsserts) +} + +module.exports = getRuleMapping diff --git a/build/implementations/get-testcase-assertions.js b/build/implementations/get-testcase-assertions.js new file mode 100644 index 0000000000..64931efb97 --- /dev/null +++ b/build/implementations/get-testcase-assertions.js @@ -0,0 +1,17 @@ +const getAssertionSource = require('./get-assertion-source') + +/** + * Get all assertions for given testcase + * + * @param {Array} assertions assertions + * @param {String} relativeUrl relative url of testcase + */ +const getTestcaseAssertions = (assertions, relativeUrl) => { + const testcaseAssertions = assertions.filter(assertion => { + const source = getAssertionSource(assertion) + return source.includes(relativeUrl) + }) + return testcaseAssertions +} + +module.exports = getTestcaseAssertions diff --git a/build/implementations/get-testcase-mapping.js b/build/implementations/get-testcase-mapping.js new file mode 100644 index 0000000000..c1561961b5 --- /dev/null +++ b/build/implementations/get-testcase-mapping.js @@ -0,0 +1,38 @@ +const getAssertionSource = require('./get-assertion-source') + +/** + * Utility fn to get title from assertion + * + * @param {Object} assertion assertion + * @returns {String} + */ +const getTitleFromAssertion = assertion => { + const { test, EMTest } = assertion + if (!test) { + return EMTest || null + } + if (typeof test === 'string') { + return test + } + if (test.title) { + return test.title + } + return test['@id'] || '' +} + +/** + * Get mapping for a given testcase + * + * @param {Object} assertion assertion + * @param {Object} tc testcase + */ +const getTestcaseMapping = (assertion, { expected }) => { + return { + title: getTitleFromAssertion(assertion), + url: getAssertionSource(assertion), + expected: expected, + actual: assertion.result.outcome.replace('earl:', ''), + } +} + +module.exports = getTestcaseMapping diff --git a/build/implementations/get-testcase-relative-url.js b/build/implementations/get-testcase-relative-url.js new file mode 100644 index 0000000000..83b09b0cce --- /dev/null +++ b/build/implementations/get-testcase-relative-url.js @@ -0,0 +1,11 @@ +/** + * Get relative (suffix) from url + * + * @param {String} url url + */ +const getTestcaseRelativeUrl = url => { + const index = url.indexOf(`/testcases/`) + return url.substring(index) +} + +module.exports = getTestcaseRelativeUrl diff --git a/build/implementations/get-testcases-grouped-by-rule.js b/build/implementations/get-testcases-grouped-by-rule.js new file mode 100644 index 0000000000..2d7878be70 --- /dev/null +++ b/build/implementations/get-testcases-grouped-by-rule.js @@ -0,0 +1,20 @@ +const testcasesData = require('./../../public/testcases.json') + +/** + * Get testcases of rules + * - group them by `ruleId` + */ +const getTestcasesGroupedByRule = () => { + const { testcases } = testcasesData + + return testcases.reduce((out, testcase) => { + const { ruleId } = testcase + if (!out[ruleId]) { + out[ruleId] = [] + } + out[ruleId].push(testcase) + return out + }, {}) +} + +module.exports = getTestcasesGroupedByRule diff --git a/build/implementation-json-ld-frame.js b/build/implementations/json-ld-frame-config.js similarity index 97% rename from build/implementation-json-ld-frame.js rename to build/implementations/json-ld-frame-config.js index 458819e666..b20d852ca7 100644 --- a/build/implementation-json-ld-frame.js +++ b/build/implementations/json-ld-frame-config.js @@ -85,6 +85,10 @@ module.exports = { '@id': 'dct:source', }, + title: { + '@id': 'dct:title', + }, + assertions: { '@reverse': 'earl:subject', }, diff --git a/gatsby/copy-testcases-assets.js b/build/testcases/copy-testcases-assets.js similarity index 83% rename from gatsby/copy-testcases-assets.js rename to build/testcases/copy-testcases-assets.js index c1417d97cf..874b9b3d4d 100644 --- a/gatsby/copy-testcases-assets.js +++ b/build/testcases/copy-testcases-assets.js @@ -6,7 +6,7 @@ const { ncp } = require('ncp') const copyTestcasesAssets = () => { return new Promise((resolve, reject) => { ncp('./test-assets', './public/test-assets', err => { - console.info(`\nDONE!!! Copied Test Assets Directory.\n`) + console.info(`\nCopied Test Assets Directory.\n`) if (err) { reject(err) } diff --git a/gatsby/create-testcases-json.js b/build/testcases/create-testcases-json.js similarity index 83% rename from gatsby/create-testcases-json.js rename to build/testcases/create-testcases-json.js index 9bb797bd61..3cf1f35958 100644 --- a/gatsby/create-testcases-json.js +++ b/build/testcases/create-testcases-json.js @@ -2,8 +2,8 @@ const { www: { url, baseDir }, author, description, -} = require('./../package.json') -const createFile = require('../build/create-file') +} = require('./../../package.json') +const createFile = require('../../utils/create-file') /** * Create `testcases.json` diff --git a/gatsby/create-testcases-of-rule-of-em-report-tool.js b/build/testcases/create-testcases-of-rule-of-em-report-tool.js similarity index 78% rename from gatsby/create-testcases-of-rule-of-em-report-tool.js rename to build/testcases/create-testcases-of-rule-of-em-report-tool.js index 8ed9935c7a..dca5f213d7 100644 --- a/gatsby/create-testcases-of-rule-of-em-report-tool.js +++ b/build/testcases/create-testcases-of-rule-of-em-report-tool.js @@ -1,12 +1,12 @@ const { www: { url, baseDir }, -} = require('./../package.json') -const scUrlsMetaData = require('./../_data/sc-urls.json') -const scEmReportAuditResult = require('./../_data/sc-em-report-audit-result.json') -const graphContext = require('./../_data/wcag-em-report-tool-mappings/@graph-context.json') -const graphAdditionalMeta = require('./../_data/wcag-em-report-tool-mappings/@graph-additional-meta.json') -const graphEvaluatorMeta = require('./../_data/wcag-em-report-tool-mappings/@graph-evaluator-meta.json') -const createFile = require('./../build/create-file') +} = require('./../../package.json') +const scUrlsMetaData = require('./../../_data/sc-urls.json') +const scEmReportAuditResult = require('./../../_data/sc-em-report-audit-result.json') +const graphContext = require('./wcag-em-report-tool-mappings/@graph-context.json') +const graphAdditionalMeta = require('./wcag-em-report-tool-mappings/@graph-additional-meta.json') +const graphEvaluatorMeta = require('./wcag-em-report-tool-mappings/@graph-evaluator-meta.json') +const createFile = require('../../utils/create-file') /** * Create testcases json file that can be used by diff --git a/_data/wcag-em-report-tool-mappings/@graph-additional-meta.json b/build/testcases/wcag-em-report-tool-mappings/@graph-additional-meta.json similarity index 100% rename from _data/wcag-em-report-tool-mappings/@graph-additional-meta.json rename to build/testcases/wcag-em-report-tool-mappings/@graph-additional-meta.json diff --git a/_data/wcag-em-report-tool-mappings/@graph-context.json b/build/testcases/wcag-em-report-tool-mappings/@graph-context.json similarity index 100% rename from _data/wcag-em-report-tool-mappings/@graph-context.json rename to build/testcases/wcag-em-report-tool-mappings/@graph-context.json diff --git a/_data/wcag-em-report-tool-mappings/@graph-evaluator-meta.json b/build/testcases/wcag-em-report-tool-mappings/@graph-evaluator-meta.json similarity index 100% rename from _data/wcag-em-report-tool-mappings/@graph-evaluator-meta.json rename to build/testcases/wcag-em-report-tool-mappings/@graph-evaluator-meta.json diff --git a/gatsby-browser.js b/gatsby-browser.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gatsby/create-glossary-uages-in-rules.js b/gatsby/create-glossary-uages-in-rules.js deleted file mode 100644 index 522cc1a09b..0000000000 --- a/gatsby/create-glossary-uages-in-rules.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Create glossary usages - * -> for each glossary item (find references in each rule) - * -> this is saved in `_data` which is later used in `pages/glossary` - */ -const queries = require('./queries') -const regexps = require('./reg-exps') -const createFile = require('./../build/create-file') -const getAllMatchesForRegex = require('./get-all-matches-for-regex') - -const createGlossaryUsagesInRules = options => { - const { graphql } = options - - return graphql(queries.getAllRules).then(({ errors, data }) => { - if (errors) { - Promise.reject(errors) - } - - /** - * Eg: - * { - * `non-empty`: [ - * { name: `aria valid ...`, slug: `rules/XXXXX` }, - * .... - * ] - * .... - * } - */ - const glossaryUsages = {} - - const getAllRules = data.allMarkdownRemark.edges - - getAllRules.forEach(async markdownPage => { - const { node } = markdownPage - const { rawMarkdownBody, frontmatter, fields } = node - const { name } = frontmatter - const { slug } = fields - - const glossaryMatches = getAllMatchesForRegex( - regexps.glossaryReferenceInRules, - rawMarkdownBody, - false - ) - - glossaryMatches.forEach(glossaryItem => { - const hasGlossaryKey = regexps.glossaryKey.test(glossaryItem.block) - if (!hasGlossaryKey) { - return - } - - const key = glossaryItem.block.match(regexps.glossaryKey)[1] - if (!key) { - return - } - - const usage = { - name, - slug, - } - if (!glossaryUsages[key]) { - glossaryUsages[key] = [usage] - return - } - - const exists = glossaryUsages[key].some(u => u.slug === usage.slug) - if (exists) { - return - } - - glossaryUsages[key] = glossaryUsages[key].concat(usage) - }) - }) - - /** - * Create `_data/glossay-usages.json` - */ - createFile( - `./_data/glossary-usages.json`, - JSON.stringify(glossaryUsages, undefined, 2) - ) - - console.info(`\nDONE!!! Generated Glossary Usages Data.\n`) - }) -} - -module.exports = createGlossaryUsagesInRules diff --git a/gatsby/create-page-add-md-context.js b/gatsby/create-page-add-md-context.js index b955f1e281..e31d85e436 100644 --- a/gatsby/create-page-add-md-context.js +++ b/gatsby/create-page-add-md-context.js @@ -3,8 +3,8 @@ * -> get all data necessary from `on-create-node` callback * -> extend `context` object on `markdown` pages */ -const path = require('path') -const getComponent = require('./get-component') + +const getTemplate = require('./get-template') const createPageAddMdContext = options => { const { graphql, actions } = options @@ -57,7 +57,7 @@ const createPageAddMdContext = options => { createPage({ path: slug, - component: path.resolve(getComponent(markdownType, slug)), + component: getTemplate(markdownType, slug), context: { slug, fileName, diff --git a/gatsby/create-page-implementer-report.js b/gatsby/create-page-implementer-report.js new file mode 100644 index 0000000000..59ba87a003 --- /dev/null +++ b/gatsby/create-page-implementer-report.js @@ -0,0 +1,34 @@ +const implementers = require('../_data/implementers.json') +const getTemplate = require('./get-template') + +const createPageImplementerReport = options => { + const { actions } = options + const { createPage } = actions + + // Your component that should be rendered for every item in JSON. + + // Create pages for each JSON entry. + implementers.forEach(implementer => { + const { tool, organisation } = implementer + + const filename = tool + .split(' ') + .join('-') + .toLowerCase() + + const slug = `implementation/${filename}` + + createPage({ + path: slug, + component: getTemplate('implementer'), + context: { + slug, + filename, + title: `Implementation Report of ${tool} (${organisation})`, + data: JSON.stringify(implementer), + }, + }) + }) +} + +module.exports = createPageImplementerReport diff --git a/gatsby/create-pages.js b/gatsby/create-pages.js index a0cf3fae8b..77a7ae3a07 100644 --- a/gatsby/create-pages.js +++ b/gatsby/create-pages.js @@ -1,6 +1,5 @@ const createPageAddMdContext = require('./create-page-add-md-context') -const createTestcasesOfAllRules = require('./create-testcases-of-all-rules') -const createGlossaryUsagesInRules = require('./create-glossary-uages-in-rules') +const createPageImplementerReport = require('./create-page-implementer-report') const createPages = async options => { const promises = [ @@ -12,19 +11,9 @@ const createPages = async options => { createPageAddMdContext(options), /** - * Create test case files & meta data - * -> create test cases files into `./public/testcases/` - * -> copy `./test-assets/*` into `./public` - * -> create `testcases.json` into `./public` + * Create implementation report pages */ - createTestcasesOfAllRules(options), - - /** - * Create glossary usages - * -> for each glossary item (find references in each rule) - * -> this is saved in `_data` which is later used in `pages/glossary` - */ - createGlossaryUsagesInRules(options), + createPageImplementerReport(options), ] await Promise.all(promises) diff --git a/gatsby/create-testcases-of-all-rules.js b/gatsby/create-testcases-of-all-rules.js deleted file mode 100644 index f904ab1709..0000000000 --- a/gatsby/create-testcases-of-all-rules.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Create test case files & meta data - * -> create test cases files into `./public/testcases/` - * -> copy `./test-assets/*` into `./public` - * -> create `testcases.json` into `./public` - */ -const fs = require('fs') -const objectHash = require('object-hash') -const codeBlocks = require('gfm-code-blocks') -const fastmatter = require('fastmatter') -const { - www: { url, baseDir }, -} = require('./../package.json') -const createFile = require('../build/create-file') -const getAllMatchesForRegex = require('./get-all-matches-for-regex') -const queries = require('./queries') -const regexps = require('./reg-exps') -const copyTestcasesAssets = require('./copy-testcases-assets') -const createTestcasesJson = require('./create-testcases-json') -const createTestcasesOfRuleOfEmReportTool = require('./create-testcases-of-rule-of-em-report-tool') - -const createTestcasesOfAllRules = options => { - const { graphql } = options - - return graphql(queries.getAllRules).then(async ({ errors, data }) => { - if (errors) { - Promise.reject(errors) - } - - let allRulesTestcases = [] - - /** - * iterate all rule pages - * -> get code snippets - * -> and their relevant titles - */ - const allRulePages = data.allMarkdownRemark.edges - - allRulePages.forEach(markdownPage => { - const { node } = markdownPage - const { rawMarkdownBody, frontmatter, fields } = node - const { fastmatterAttributes } = fields - const { - accessibility_requirements: ruleAccessibilityRequirements, - } = JSON.parse(fastmatterAttributes) - const { name: ruleName } = frontmatter - const { slug } = fields - const ruleId = slug.replace('rules/', '') - const codeTitles = getAllMatchesForRegex( - regexps.testcaseTitle, - rawMarkdownBody - ) - const codeSnippets = codeBlocks(rawMarkdownBody) - - if (codeTitles.length !== codeSnippets.length) { - throw new Error( - `Number of matching titles for code snippets is wrong. Check markdown '${ruleName}' for irregularities. Slug: '${slug}'` - ) - } - - /** - * iterate each code snippet - * -> create a testcase file - * -> and add meta of testcase to `testcases.json` - */ - const ruleTestcases = codeSnippets.reduce((out, codeBlock, index) => { - const title = codeTitles[index] - if (!title) { - throw new Error('No title found for code snippet.') - } - - const { code, block } = codeBlock - let { type = 'html' } = codeBlock - - if (regexps.testcaseCodeSnippetTypeIsSvg.test(block.substring(0, 15))) { - type = 'svg' - } - - const codeId = objectHash({ - block, - type, - ruleId, - }) - - const titleCurated = title.value.split(' ').map(t => t.toLowerCase()) - - const testcaseFileName = `${ruleId}/${codeId}.${type}` - const testcasePath = `testcases/${testcaseFileName}` - /** - * Create testcase file - */ - createFile(`${baseDir}/${testcasePath}`, code) - - /** - * Create meta data for testcase(s) - */ - const testcase = { - testcaseId: codeId, - url: `${url}/${testcasePath}`, - expected: titleCurated[0], - ruleId, - ruleName, - rulePage: `${url}/${slug}`, - ruleAccessibilityRequirements, - requirementsMapping: Object.keys(ruleAccessibilityRequirements || {}), - } - - out.push(testcase) - return out - }, []) - - // add rule testcases to all testcases - allRulesTestcases = allRulesTestcases.concat(ruleTestcases) - - /** - * Create test cases of rule for use with `em report tool` - */ - createTestcasesOfRuleOfEmReportTool({ - ruleId, - ruleName, - ruleTestcases, - ruleAccessibilityRequirements, - }) - }) - - /** - * Copy `test-assets` that are used by `testcases` - */ - await copyTestcasesAssets() - - /** - * Generate `testcases.json` - */ - await createTestcasesJson(allRulesTestcases) - - console.info(`\nDONE!!! Generated Test Cases.\n`) - }) -} - -module.exports = createTestcasesOfAllRules diff --git a/gatsby/get-component.js b/gatsby/get-component.js deleted file mode 100644 index 744ab85c50..0000000000 --- a/gatsby/get-component.js +++ /dev/null @@ -1,18 +0,0 @@ -function getComponent(type, slug) { - const map = { - glossary: './src/templates/glossary.js', - rules: './src/templates/rule.js', - } - - if (Object.keys(map).includes(type)) { - return map[type] - } - - if (type === 'implementations' && slug.includes('overview')) { - return './src/templates/coverage.js' - } - - return './src/templates/default.js' -} - -module.exports = getComponent diff --git a/gatsby/get-node-data.js b/gatsby/get-node-data.js index 47e15bcafb..dec706d0b2 100644 --- a/gatsby/get-node-data.js +++ b/gatsby/get-node-data.js @@ -1,7 +1,7 @@ const fs = require('fs') const fastmatter = require('fastmatter') const { createFilePath } = require('gatsby-source-filesystem') -const getGitLog = require('./get-git-log') +const getGitLog = require('./../utils/get-git-log') /** * Get node data, to enhance metadata of pages diff --git a/gatsby/get-template.js b/gatsby/get-template.js new file mode 100644 index 0000000000..db3c61a182 --- /dev/null +++ b/gatsby/get-template.js @@ -0,0 +1,21 @@ +const path = require('path') + +const getTemplate = (type, slug) => { + const map = { + glossary: './src/templates/glossary.js', + rules: './src/templates/rule.js', + implementer: './src/templates/implementer.js', + } + + if (Object.keys(map).includes(type)) { + return path.resolve(map[type]) + } + + if (type === 'implementations' && slug.includes('overview')) { + return path.resolve('./src/templates/implementations.js') + } + + return path.resolve('./src/templates/default.js') +} + +module.exports = getTemplate diff --git a/package-lock.json b/package-lock.json index 965c132fed..22288ae388 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1641,6 +1641,26 @@ "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", "dev": true }, + "act-rules-implementation-access-engine": { + "version": "git+https://git@github.com/act-rules/act-rules-implementation-access-engine.git#fc89e8142ff8d2ad37ee411cbf3801b44320df43", + "from": "git+https://git@github.com/act-rules/act-rules-implementation-access-engine.git" + }, + "act-rules-implementation-alfa": { + "version": "git+https://git@github.com/act-rules/act-rules-implementation-alfa.git#3944f96980ecf3d6d2987b63c00859968119548f", + "from": "git+https://git@github.com/act-rules/act-rules-implementation-alfa.git" + }, + "act-rules-implementation-axe-core": { + "version": "git+https://git@github.com/act-rules/act-rules-implementation-axe-core.git#3d61261e7998e2b753eac465cd4dfb316269f0ca", + "from": "git+https://git@github.com/act-rules/act-rules-implementation-axe-core.git" + }, + "act-rules-implementation-rgaa": { + "version": "git+https://git@github.com/act-rules/act-rules-implementation-rgaa.git#1d572a40f6879a75d36090c7275c42e4d0d79cee", + "from": "git+https://git@github.com/act-rules/act-rules-implementation-rgaa.git" + }, + "act-rules-implementation-trusted-tester": { + "version": "git+https://git@github.com/act-rules/act-rules-implementation-trusted-tester.git#db78eb5206f6bb95fd587628c6c77984576b22e0", + "from": "git+https://git@github.com/act-rules/act-rules-implementation-trusted-tester.git" + }, "address": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", @@ -10046,6 +10066,11 @@ "unc-path-regex": "^0.1.2" } }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", diff --git a/package.json b/package.json index 2f0b1e20ce..eeb4b76e14 100644 --- a/package.json +++ b/package.json @@ -82,8 +82,15 @@ } ], "dependencies": { + "act-rules-implementation-access-engine": "git+https://git@github.com/act-rules/act-rules-implementation-access-engine.git", + "act-rules-implementation-alfa": "git+https://git@github.com/act-rules/act-rules-implementation-alfa.git", + "act-rules-implementation-axe-core": "git+https://git@github.com/act-rules/act-rules-implementation-axe-core.git", + "act-rules-implementation-rgaa": "git+https://git@github.com/act-rules/act-rules-implementation-rgaa.git", + "act-rules-implementation-trusted-tester": "git+https://git@github.com/act-rules/act-rules-implementation-trusted-tester.git", "axios": "^0.18.1", + "commander": "^2.20.0", "fastmatter": "^2.1.1", + "flat": "^4.1.0", "gatsby": "^2.8.6", "gatsby-image": "^2.1.2", "gatsby-plugin-manifest": "^2.1.1", @@ -93,13 +100,14 @@ "gatsby-plugin-sass": "^2.0.10", "gatsby-plugin-sharp": "^2.1.3", "gatsby-remark-autolink-headers": "^2.0.15", + "gatsby-remark-copy-linked-files": "^2.0.12", "gatsby-remark-prismjs": "^3.2.11", "gatsby-source-filesystem": "^2.0.38", - "gatsby-remark-copy-linked-files": "^2.0.12", "gatsby-transformer-remark": "^2.3.12", "gatsby-transformer-sharp": "^2.1.21", "gfm-code-blocks": "^1.0.0", "globby": "^9.2.0", + "is-url": "^1.2.4", "jsonld": "^1.6.2", "make-dir": "^2.1.0", "marked": "^0.6.2", @@ -130,17 +138,27 @@ "license": "MIT", "scripts": { "clean": "rm -rf .cache public", - "prebuild": "npm run get-data", + "getImplementationAccessEngine": "node ./build/get-implementation --org 'Level Access' --tool 'Access Engine' --path './node_modules/act-rules-implementation-access-engine/report.json'", + "getImplementationAlfa": "node ./build/get-implementation --org 'Siteimprove' --tool 'Alfa' --path './node_modules/act-rules-implementation-alfa/report.json'", + "getImplementationAxe": "node ./build/get-implementation --org 'Deque Systems' --tool 'Axe' --path './node_modules/act-rules-implementation-axe-core/report.json'", + "getImplementationRgaa": "node ./build/get-implementation --org 'Access42' --tool 'RGAA 3.0' --path './node_modules/act-rules-implementation-rgaa/reports/*.json'", + "getImplementationTrustedTester": "node ./build/get-implementation --org 'Trusted Tester' --tool 'Trusted Tester' --path './node_modules/act-rules-implementation-trusted-tester/reports/*.json'", + "getImplementations": "npm run getImplementationAccessEngine && npm run getImplementationAlfa && npm run getImplementationAxe && npm run getImplementationRgaa && npm run getImplementationTrustedTester", + "createImplementationsMetrics": "node ./build/create-implementation-metrics", + "implementations": "npm run getImplementations && npm run createImplementationsMetrics", + "createTestcases": "node ./build/create-testcases", + "createGlossary": "node ./build/create-glossary-usages", + "getWcagData": "node ./build/get-wcag-data", + "getData": "npm run getWcagData && npm run createTestcases && npm run createGlossary && npm run implementations", + "predevelop": "npm run getData", + "develop": "gatsby develop", + "prebuild": "npm run getData", "build": "gatsby build", - "develop": "npm run get-data && gatsby develop", "format": "prettier --write *.{json,md,js,jsx} './{_data,_rules,build,gatsby,pages,src,test-assets}/**/*.{json,md,js,jsx}'", "start": "npm run clean && npm run develop", "serve": "gatsby serve", - "test": "npm run test:rules", - "test:rules": "jest", - "get-implementations": "node ./build/get-implementations", - "get-wcag-sc-metadata": "node ./build/get-wcag-sc-metadata", - "get-data": "npm run get-wcag-sc-metadata && npm run get-implementations" + "pretest": "npm run getData", + "test": "jest --coverage" }, "homepage": "https://github.com/act-rules/act-rules.github.io", "repository": { @@ -161,10 +179,6 @@ "references": { "wcag21": "https://raw.githubusercontent.com/w3c/wai-wcag-quickref/gh-pages/_data/wcag21.json" }, - "implementations": { - "alfa": "https://raw.githubusercontent.com/w3c/earl/master/earl-reports/alfa-report.json", - "axe": "https://raw.githubusercontent.com/w3c/earl/master/earl-reports/axe-report.json" - }, "rule-format-metadata": { "input-aspects": { "default": "https://www.w3.org/TR/act-rules-aspects/#intro", @@ -180,16 +194,11 @@ "verbose": true, "bail": true, "testPathIgnorePatterns": [ - "/test/utils/", + "/test/test-utils/", "/.cache/", "/.public/" ] }, - "husky": { - "hooks": { - "pre-commit": "npm test" - } - }, "lint-staged": { "*.{md,json,js,html}": [ "prettier --write", diff --git a/pages/implementations/testcases.md b/pages/implementations/testcases.md index 2639a6601d..905166632e 100644 --- a/pages/implementations/testcases.md +++ b/pages/implementations/testcases.md @@ -18,7 +18,7 @@ In the `testcases.json` file, test cases are included on the `testcases` array, - `ruleId`: Unique identifier for the rule. - `ruleName`: Title of the rule the test case is for - `rulePage`: Page containing a detailed rule description. -- `requirementsMapping`: Array of success criteria and other accessibility requirements not satisfied when the rule `failed` +- `ruleAccessibilityRequirements`: Array of success criteria and other accessibility requirements not satisfied when the rule `failed` ```json { @@ -33,7 +33,7 @@ In the `testcases.json` file, test cases are included on the `testcases` array, "ruleId": "5f99a7", "ruleName": "ARIA attribute is valid", "rulePage": "https://act-rules.github.io/rules/5f99a7", - "requirementsMapping": ["wcag20:4.1.2"] + "ruleAccessibilityRequirements": ["wcag20:4.1.2"] }, ... ] } ``` @@ -42,7 +42,7 @@ In the `testcases.json` file, test cases are included on the `testcases` array, To run the test cases, go through each URL run the necessary tests on that page. Tools and test procedures do not need to have a one-to-one mapping to ACT-R rules. While you could run all tests in an implementation on every test case, for efficiency we recommend doing one of the following: -1. Only run tests relevant for the `requirementsMapping` (e.g. for a test case that maps to success criterion 1.1.1, only run tests related to success criterion 1.1.1) +1. Only run tests relevant for the `ruleAccessibilityRequirements` (e.g. for a test case that maps to success criterion 1.1.1, only run tests related to success criterion 1.1.1) 2. Maintain a manual many-to-many mapping of rules in your implementation to ACT-R Rules diff --git a/pages/implementations/wcag-em-tool.md b/pages/implementations/wcag-em-tool.md index 1cdee7ee5d..87bd5c4b71 100644 --- a/pages/implementations/wcag-em-tool.md +++ b/pages/implementations/wcag-em-tool.md @@ -22,11 +22,11 @@ If you have a manual test methodology, where you fill results into some report t ## Step 3: Run The Testcases -1. Under each success criterion set to "Cannot tell" click "Show web pages to enter -individual results +1. Under each success criterion set to "Cannot tell" click "Show web pages to enter + individual results -2. For every page, answer "passed", "failed" "not present" or "cannot tell" (this has to be -done on each success criterion +2. For every page, answer "passed", "failed" "not present" or "cannot tell" (this has to be + done on each success criterion 3. Go to "report findings" and enter your name under "evaluator" diff --git a/src/components/layout/index.js b/src/components/layout/index.js index dacf0dce39..a5cb79c365 100644 --- a/src/components/layout/index.js +++ b/src/components/layout/index.js @@ -16,6 +16,18 @@ class Layout extends React.Component { } } + componentDidMount() { + const { hash } = window.location; + if (!hash) { + return + } + const el = document.getElementById(hash) + if (!el) { + return + } + el.scrollIntoView(); + } + handleHideShowMenu() { this.setState(prevState => ({ showMenu: !prevState.showMenu, diff --git a/src/components/layout/index.scss b/src/components/layout/index.scss index 207d3332c2..ce61513eb5 100644 --- a/src/components/layout/index.scss +++ b/src/components/layout/index.scss @@ -124,6 +124,12 @@ section.layout-container { margin-top: 3rem; } + &.page-implementers { + h1, h2, h3, h4, h5, h6 { + display: inline-block; + } + } + // Styles for rules page &.page-rules{ .rules-listing { @@ -171,6 +177,7 @@ section.layout-container { font-weight: bold; margin-top: 1em; font-size: 90%; + color: inherit; cursor: auto; &:hover { text-decoration: none; diff --git a/src/pages/rules.js b/src/pages/rules.js index d567184c99..0aa033e695 100644 --- a/src/pages/rules.js +++ b/src/pages/rules.js @@ -6,6 +6,7 @@ import showdown from 'showdown' import { getAccessibilityRequirements, getInputRulesForRule, + getImplementationsCount, } from './../utils/render-fragments' export default ({ data }) => { @@ -47,6 +48,10 @@ export default ({ data }) => { accessibility_requirements, 'text' )} + {/* input rules */} + {getInputRulesForRule(input_rules, allRules.edges, true)} + {/* implementation count */} + {getImplementationsCount(slug)} {/* rule description */}
{ }} /> - {/* input rules */} - {getInputRulesForRule(input_rules, allRules.edges, true)} ) })} diff --git a/src/styles/base.scss b/src/styles/base.scss index 43fe0151a2..9790f2b9b8 100644 --- a/src/styles/base.scss +++ b/src/styles/base.scss @@ -6,7 +6,7 @@ body { } h1, h2, h3, h4, h5, h6 { - @include header-style($fg-primary) + @include header-style($fg-primary); } ul{ diff --git a/src/templates/coverage.js b/src/templates/implementations.js similarity index 68% rename from src/templates/coverage.js rename to src/templates/implementations.js index ccca1e80fa..a3c6056bd8 100644 --- a/src/templates/coverage.js +++ b/src/templates/implementations.js @@ -1,8 +1,8 @@ import React from 'react' import Layout from '../components/layout' import { graphql } from 'gatsby' -import implementations from './../../_data/implementations' import SEO from '../components/seo' +import implementers from './../../_data/implementers' export default ({ data }) => { const { markdownRemark, site } = data @@ -21,29 +21,27 @@ export default ({ data }) => { - - - {implementations.map((row, index) => { + {implementers.map(row => { + const { organisation, tool } = row + const filename = tool + .split(' ') + .join('-') + .toLowerCase() + const reportUrl = `/implementation/${filename}` return ( - - - - - + + + {/* TODO: */} + {/* */} + ) diff --git a/src/templates/implementer.js b/src/templates/implementer.js new file mode 100644 index 0000000000..63d9fef592 --- /dev/null +++ b/src/templates/implementer.js @@ -0,0 +1,84 @@ +import React from 'react' +import Layout from '../components/layout' +import { graphql, Link } from 'gatsby' +import SEO from '../components/seo' + +export default ({ data }) => { + const { site, sitePage } = data + const { context } = sitePage + const { title: pageTitle, data: contextData } = context + + const updatedTitle = `${pageTitle} | ${site.siteMetadata.title}` + const report = JSON.parse(contextData) + const { data: ruleImplementations } = report + + return ( + + +
+

{pageTitle}

+ {ruleImplementations.map((ruleImplementation, index) => { + const { ruleId, ruleName, implementation } = ruleImplementation + const { assertions } = implementation[0] + const key = `${index}-${ruleId}` + return ( +
+ +

{ruleName}

+ + { +
# Tool NameVersion Created By Report
{index + 1}{row.vendorTool}{row.vendorToolVersion}{row.vendorName}
{tool}{row.vendorToolVersion}{organisation} - - View Report - + View Report
+ + + + + + + + + {assertions.map((assertion, index) => { + const { url, expected, actual } = assertion + const key = `${index}-${url}` + return ( + + + + + + ) + })} + +
Testcase UrlExpectedActual
+ + {url} + + {expected}{actual}
+ } +
+ ) + })} + + + ) +} + +export const query = graphql` + query($path: String!) { + sitePage(path: { eq: $path }) { + context { + data + title + } + } + site { + siteMetadata { + title + keywords + } + } + } +` diff --git a/src/templates/rule.js b/src/templates/rule.js index 241b7737e9..1ffccf754c 100644 --- a/src/templates/rule.js +++ b/src/templates/rule.js @@ -12,6 +12,8 @@ import { getAuthors, getInputRulesForRule, getInputAspects, + getImplementations, + getImplementationsLink, } from './../utils/render-fragments' import SEO from '../components/seo' import { contributors, repository, config } from './../../package.json' @@ -81,6 +83,9 @@ export default ({ data }) => { repository.url, `_rules/${relativePath}` )} +
+ {/* implementations */} + {getImplementations(slug)} {/* acknowledgements */}
@@ -98,8 +103,12 @@ export default ({ data }) => {
    + {/* glossary */} {getGlossaryUsedLink(slug, allGlossary)} + {/* changelog */} {getChangelogLink(ruleChangelog)} + {/* implementations */} + {getImplementationsLink(slug)}
  • Acknowledgements
  • diff --git a/src/utils/render-fragments.js b/src/utils/render-fragments.js index b044641538..19e47236fc 100644 --- a/src/utils/render-fragments.js +++ b/src/utils/render-fragments.js @@ -2,6 +2,63 @@ import React from 'react' import scUrls from './../../_data/sc-urls' import { Link } from 'gatsby' import glossaryUsages from './../../_data/glossary-usages.json' +import implementationMetrics from './../../_data/implementation-metrics.json' + +export const getImplementations = slug => { + const ruleId = slug.replace('rules/', '') + const metrics = implementationMetrics[ruleId] + if (!metrics) { + return null + } + return ( + <> + +

    Implementations

    +
    + + + + + + + + + + {metrics.map(metric => { + const { organisation, tool } = metric + const filename = tool + .split(' ') + .join('-') + .toLowerCase() + const reportUrl = `/implementation/${filename}#${ruleId}` + return ( + + + + + + ) + })} + +
    Tool NameCreated ByReport
    {tool}{organisation} + View Report +
    + + ) +} + +export const getImplementationsLink = slug => { + const ruleId = slug.replace('rules/', '') + const metrics = implementationMetrics[ruleId] + if (!metrics) { + return null + } + return ( +
  • + Implementations ({metrics.length}) +
  • + ) +} export const getChangelog = (changelog, url, file) => { if (!changelog.length) { @@ -385,28 +442,43 @@ export function getInputRulesForRule(
    Input Rules
      - {inputRules.map(inputRuleId => { - const atomicRule = allRules.find( - rule => rule.node.frontmatter.id === inputRuleId - ) - const aHref = stripBasePath - ? atomicRule.node.fields.slug.replace('rules/', '') - : atomicRule.node.fields.slug - const name = atomicRule.node.frontmatter.name - return ( -
    • - - {name} - -
    • - ) - })} + {inputRules.map(inputRuleId => { + const atomicRule = allRules.find( + rule => rule.node.frontmatter.id === inputRuleId + ) + const aHref = stripBasePath + ? atomicRule.node.fields.slug.replace('rules/', '') + : atomicRule.node.fields.slug + const name = atomicRule.node.frontmatter.name + return ( +
    • + + {name} + +
    • + ) + })}
) } +export function getImplementationsCount(slug) { + const ruleId = slug.replace('rules/', '') + const metrics = implementationMetrics[ruleId] + if (!metrics) { + return null + } + return ( +
+
+

Implementations: {metrics.length}

+
+
+ ) +} + export function getGlossaryUsageInRules(usages) { if (!usages) { return null diff --git a/test/build/implementations/get-assertions.test.js b/test/build/implementations/get-assertions.test.js new file mode 100644 index 0000000000..7c375637d2 --- /dev/null +++ b/test/build/implementations/get-assertions.test.js @@ -0,0 +1,16 @@ +const getFramedReport = require('../../../build/implementations/get-framed-report') +const getAssertions = require('../../../build/implementations/get-assertions') + +describe(`getAssertions`, () => { + it('throws when no argument is specified', () => + expect(() => getAssertions(null)).toThrow() + ) + + it('returns assertions from framed reports', async () => { + const framedReport = await getFramedReport( + `https://raw.githubusercontent.com/w3c/earl/master/earl-reports/alfa-report.json` + ) + const assertions = getAssertions(framedReport) + expect(assertions.length).toBeGreaterThan(0) + }) +}) diff --git a/test/build/implementations/get-framed-report.test.js b/test/build/implementations/get-framed-report.test.js new file mode 100644 index 0000000000..44f2116d12 --- /dev/null +++ b/test/build/implementations/get-framed-report.test.js @@ -0,0 +1,31 @@ +const getFramedReport = require('../../../build/implementations/get-framed-report') + +describe(`getFramedReport`, () => { + it('throws when no argument is specified', async () => + expect(getFramedReport(null)).rejects.toBeInstanceOf(Error) + ) + + it('returns framed report from given URL (1 report)', async () => { + const framedReport = await getFramedReport( + `https://raw.githubusercontent.com/w3c/earl/master/earl-reports/alfa-report.json` + ) + assertFramedReport(framedReport) + }) + + it('returns framed report from files GLOB (multiple reports)', async () => { + const framedReports = await getFramedReport( + `./node_modules/act-rules-implementation-rgaa/reports/*.json` + ) + expect(framedReports.length).toBeGreaterThan(1) + framedReports.forEach((report) => assertFramedReport(report)) + }) +}) + +const assertFramedReport = (report) => { + expect(report).toBeDefined() + const keys = Object.keys(report); + ['@context', '@graph'].forEach((key) => { + expect(keys.includes(key)).toBe(true) + }) + expect(report['@graph'].length).toBeGreaterThan(0) +} \ No newline at end of file diff --git a/test/build/implementations/get-implementation-for-report.test.js b/test/build/implementations/get-implementation-for-report.test.js new file mode 100644 index 0000000000..1bdf806923 --- /dev/null +++ b/test/build/implementations/get-implementation-for-report.test.js @@ -0,0 +1,17 @@ +const getFramedReport = require('../../../build/implementations/get-framed-report') +const getImplementationForReport = require('../../../build/implementations/get-implementation-for-report') + +describe(`getImplementationForReport`, () => { + + it(`gets implementation report`, async () => { + const framedReports = await getFramedReport( + `./node_modules/act-rules-implementation-rgaa/reports/*.json` + ) + const result = await getImplementationForReport(framedReports) + expect(result).toBeDefined() + expect(result.length).toBeGreaterThan(0) + result.forEach((data) => { + expect(Object.keys(data)).toEqual(['ruleId', 'ruleName', 'implementation']) + }) + }) +}) \ No newline at end of file diff --git a/test/build/implementations/get-testcases-grouped-by-rule.test.js b/test/build/implementations/get-testcases-grouped-by-rule.test.js new file mode 100644 index 0000000000..7288446a44 --- /dev/null +++ b/test/build/implementations/get-testcases-grouped-by-rule.test.js @@ -0,0 +1,12 @@ +const getRules = require('./../../test-utils/get-rules') +const getTestcasesGroupedByRule = require('../../../build/implementations/get-testcases-grouped-by-rule') + +describe('getTestcasesGroupedByRule', () => { + + it('should have testcases for every rule', () => { + const rules = getRules() + const groupedTestcases = getTestcasesGroupedByRule() + expect(rules.length).toEqual(Object.keys(groupedTestcases).length) + }) + +}) \ No newline at end of file diff --git a/test/rule-filename.test.js b/test/rule/filename.test.js similarity index 84% rename from test/rule-filename.test.js rename to test/rule/filename.test.js index 5913e552d3..a84499d533 100644 --- a/test/rule-filename.test.js +++ b/test/rule/filename.test.js @@ -1,4 +1,4 @@ -const describeRule = require('./utils/describe-rule') +const describeRule = require('../test-utils/describe-rule') describeRule('filename', ruleData => { const { filename, frontmatter } = ruleData diff --git a/test/rule-frontmatter.test.js b/test/rule/frontmatter.test.js similarity index 92% rename from test/rule-frontmatter.test.js rename to test/rule/frontmatter.test.js index 3af248b4e5..b7469f1387 100644 --- a/test/rule-frontmatter.test.js +++ b/test/rule/frontmatter.test.js @@ -1,6 +1,6 @@ -const describeRule = require('./utils/describe-rule') -const { contributors } = require('./../package.json') +const describeRule = require('../test-utils/describe-rule') +const { contributors } = require('./../../package.json') const contributorsNames = contributors.map(contributor => contributor.name.toLowerCase()) describeRule('frontmatter', (ruleData) => { diff --git a/test/rule-headings.test.js b/test/rule/headings.test.js similarity index 73% rename from test/rule-headings.test.js rename to test/rule/headings.test.js index a434177b1e..e1c2501799 100644 --- a/test/rule-headings.test.js +++ b/test/rule/headings.test.js @@ -1,13 +1,11 @@ -const describeRule = require('./utils/describe-rule') -const getAllHeadingsFromMarkdownBody = require('./utils/get-all-headings-from-markdown-body') -const getHeadingOfDepth = require('./utils/get-heading-of-depth') +const describeRule = require('../test-utils/describe-rule') +const getAllHeadingsFromMarkdownBody = require('../test-utils/get-all-headings-from-markdown-body') +const getHeadingOfDepth = require('../test-utils/get-heading-of-depth') describeRule('headings', (ruleData) => { const { body } = ruleData const headings = getAllHeadingsFromMarkdownBody(body) - const level3Headings = getHeadingOfDepth(headings, 3) - /** * Check for `required` `h2` headings */ diff --git a/test/utils/describe-rule.js b/test/test-utils/describe-rule.js similarity index 81% rename from test/utils/describe-rule.js rename to test/test-utils/describe-rule.js index 1e241d20a7..d213f600f3 100644 --- a/test/utils/describe-rule.js +++ b/test/test-utils/describe-rule.js @@ -1,12 +1,14 @@ -const getRulesData = require('./get-rules-data') -const rules = getRulesData() +const getRules = require('./get-rules') /** - * + * describe rule helper * @param {String} groupName name of the `describe` block * @param {Function} runTests function callback of `describe` block, which executes per rule */ const describeRule = (groupName, runTests) => { + + const rules = getRules() + rules.forEach(ruleData => { const { filename } = ruleData describe(filename, () => { diff --git a/test/utils/get-all-headings-from-markdown-body.js b/test/test-utils/get-all-headings-from-markdown-body.js similarity index 100% rename from test/utils/get-all-headings-from-markdown-body.js rename to test/test-utils/get-all-headings-from-markdown-body.js diff --git a/test/utils/get-heading-of-depth.js b/test/test-utils/get-heading-of-depth.js similarity index 100% rename from test/utils/get-heading-of-depth.js rename to test/test-utils/get-heading-of-depth.js diff --git a/test/test-utils/get-rules.js b/test/test-utils/get-rules.js new file mode 100644 index 0000000000..2551e623f4 --- /dev/null +++ b/test/test-utils/get-rules.js @@ -0,0 +1,14 @@ +const globby = require('globby') +const getMarkdownData = require('../../utils/get-markdown-data') + + +/** + * Read all rules & parse the markdown + */ +const getRules = () => { + return globby.sync([`./_rules/*.md`]).map( + rulePath => getMarkdownData(rulePath) + ) +} + +module.exports = getRules \ No newline at end of file diff --git a/test/utils/get-rules-data.js b/test/utils/get-rules-data.js deleted file mode 100644 index 840be09a54..0000000000 --- a/test/utils/get-rules-data.js +++ /dev/null @@ -1,23 +0,0 @@ -const fs = require('fs') -const path = require('path') -const globby = require('globby') -const fastmatter = require('fastmatter') - -/** - * Read all `markdown` files content from `_rules/*.md` - */ -const getRulesData = () => { - return globby.sync([`./_rules/*.md`]) - .map(rulePath => { - const filename = path.parse(rulePath).base - const fileContents = fs.readFileSync(rulePath, { encoding: 'utf-8' }) - const { attributes: frontmatter, body } = fastmatter(fileContents) - return { - filename, - frontmatter, - body - } - }) -} - -module.exports = getRulesData \ No newline at end of file diff --git a/build/create-file.js b/utils/create-file.js similarity index 100% rename from build/create-file.js rename to utils/create-file.js diff --git a/gatsby/get-all-matches-for-regex.js b/utils/get-all-matches-for-regex.js similarity index 71% rename from gatsby/get-all-matches-for-regex.js rename to utils/get-all-matches-for-regex.js index f2ac6d9e52..7089f52b37 100644 --- a/gatsby/get-all-matches-for-regex.js +++ b/utils/get-all-matches-for-regex.js @@ -1,3 +1,10 @@ +/** + * Given a string, get all values that match specified regular expression + * + * @param {String} regex regular expression + * @param {*} compareString string against which reg exp has to be evaluated + * @param {*} computeIndexes boolean representing if line and column number should be computed + */ const getAllMatchesForRegex = (regex, compareString, computeIndexes = true) => { if (typeof compareString !== 'string') { throw new TypeError('Expected a string to match.') diff --git a/gatsby/get-git-log.js b/utils/get-git-log.js similarity index 100% rename from gatsby/get-git-log.js rename to utils/get-git-log.js diff --git a/utils/get-markdown-data.js b/utils/get-markdown-data.js new file mode 100644 index 0000000000..bf09d73f01 --- /dev/null +++ b/utils/get-markdown-data.js @@ -0,0 +1,16 @@ +const path = require('path') +const fastmatter = require('fastmatter') +const readFile = require('./read-file') + +const getMarkdownData = (markdownPath) => { + const filename = path.parse(markdownPath).base + const fileContents = readFile(markdownPath) + const { attributes: frontmatter, body } = fastmatter(fileContents) + return { + filename, + frontmatter, + body + } +} + +module.exports = getMarkdownData \ No newline at end of file diff --git a/utils/read-file.js b/utils/read-file.js new file mode 100644 index 0000000000..2e3aa96e42 --- /dev/null +++ b/utils/read-file.js @@ -0,0 +1,5 @@ +const fs = require('fs') + +const readFile = (path) => fs.readFileSync(path, { encoding: 'utf-8' }) + +module.exports = readFile \ No newline at end of file diff --git a/gatsby/reg-exps.js b/utils/reg-exps.js similarity index 100% rename from gatsby/reg-exps.js rename to utils/reg-exps.js