dashboard/cypress/support/plugins/accessibility/index.ts

231 lines
4.7 KiB
TypeScript

/* eslint-disable no-console */
import * as fs from 'fs';
import * as path from 'path';
import * as sha from 'sha.js';
import { createHtmlReport } from 'axe-html-reporter';
const calcHash = function(str) {
return sha.default('sha256').update(str).digest('hex');
};
function createPath(testPath: string[]) {
const currentSpec = chain[chain.length - 1];
let found = currentSpec;
for (const p of testPath) {
const f = found.children.filter((item) => item.name === p);
if (f.length === 1) {
found = f[0];
} else {
const c = {
name: p,
children: [],
violations: [],
leaf: false,
};
found.children.push(c);
found = c;
}
}
return found;
}
export type TestViolation = {
name: string;
children: TestViolation[];
violations: any[];
leaf: boolean;
screenshot?: string;
};
export type Options = {
violations: any[];
titlePath: string[];
};
type Screenshot = {
name: string;
specName: string;
path: string;
};
// Root chain
const chain: TestViolation[] = [{
name: 'Root',
children: [],
violations: [],
leaf: false,
}];
const allViolations = [] as any[];
const screenshots = [] as Screenshot[];
let folder;
// Tidy up the chain
function tidy(item: TestViolation) {
item.children.forEach((i) => tidy(i));
if (item.violations.length === 0 && item.children.length === 1) {
if (item.children[0].leaf) {
// Collapse up
item.violations = item.children[0].violations;
item.children = [];
}
}
}
export function a11yScreenshot(options: any) {
const { titlePath, props } = options;
const found = createPath(titlePath);
found.screenshot = props.path;
}
function deDuplicate(violations: any[]) {
const result: any[] = [];
const seen: {[key: string]: any} = {};
violations.forEach((item) => {
const copy = JSON.parse(JSON.stringify(item));
delete copy.nodes;
const hash = calcHash(JSON.stringify(copy));
if (!seen[hash]) {
seen[hash] = item;
result.push(item);
} else {
// Merge the nodes
const existing = seen[hash];
item.nodes.forEach((node) => {
const str = JSON.stringify(node);
const exists = existing.nodes.find((n) => JSON.stringify(n) === str);
if (!exists) {
existing.nodes.push(node);
}
});
}
});
return result;
}
function registerHooks(on, config) {
// Get the folder to write the reports into
folder = config.env.a11yFolder;
// fs.rmdirSync(folder);
if (!fs.existsSync(folder)) {
fs.mkdirSync(folder);
}
on('task', {
a11y(options: Options) {
const { violations, titlePath } = options;
const found = createPath(titlePath);
allViolations.push(...violations);
found.violations.push(...violations);
found.leaf = true;
return null;
},
a11yScreenshot(options: any ) {
const { titlePath, name } = options;
const found = createPath(titlePath);
// Move the screenshot to the accessibility folder
const details = screenshots.find((s) => s.name === name);
if (details) {
const screenFolder = path.join(folder, details.specName);
const destFile = path.join(screenFolder, `${ name }.png`);
found.screenshot = path.join(details.specName, `${ name }.png`);
if (!fs.existsSync(screenFolder)) {
fs.mkdirSync(screenFolder);
}
fs.renameSync(details.path, destFile);
}
return null;
}
});
on('task', {
log(message) {
console.log(message);
return null;
},
table(message) {
console.table(message);
return null;
}
});
on('before:spec', (spec) => {
const newSpec = {
name: spec.baseName,
children: [],
violations: [],
leaf: false,
};
chain[0].children.push(newSpec);
// Push the spec onto the chain
chain.push(newSpec);
});
on('after:spec', () => {
// Pop the spec off of the chain
chain.pop();
});
on('after:screenshot', (details) => {
const { name, specName, path } = details;
screenshots.push({
name,
specName,
path
});
});
on('after:run', () => {
const root = chain[0];
tidy(root);
fs.writeFileSync(path.join(folder, 'accessibility.json'), JSON.stringify(root.children, null, 2));
const reportHTML = createHtmlReport({
results: { violations: deDuplicate(allViolations) },
options: {
projectKey: 'Rancher Manager',
doNotCreateReportFile: true,
},
});
fs.writeFileSync(path.join(folder, 'accessibility.html'), reportHTML);
return null;
});
return config;
}
export default registerHooks;