Skip to content

Commit

Permalink
feat(commons/standards): create the commons/standards object for help…
Browse files Browse the repository at this point in the history
…er functions against the standards table (#2358)

* feat(commons/standards): create the commons/standards object for helper functions against the standards table

* Update lib/commons/standards/get-html-element-spec.js

Co-authored-by: Wilco Fiers <[email protected]>

* rename

* comments

Co-authored-by: Wilco Fiers <[email protected]>
  • Loading branch information
straker and WilcoFiers authored Jul 10, 2020
1 parent f6b3484 commit 6dce974
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 8 deletions.
4 changes: 3 additions & 1 deletion lib/commons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as color from './color';
import * as dom from './dom';
import * as forms from './forms';
import matches from './matches';
import * as standards from './standards';
import * as table from './table';
import * as text from './text';
import * as utils from '../core/utils';
Expand All @@ -21,9 +22,10 @@ var commons = {
dom,
forms,
matches,
standards,
table,
text,
utils
};

export { aria, color, dom, forms, matches, table, text, utils };
export { aria, color, dom, forms, matches, standards, table, text, utils };
14 changes: 14 additions & 0 deletions lib/commons/standards/get-aria-roles-by-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import standards from '../../standards';

/**
* Return a list of aria roles whose type matches the provided value.
* @param {String} type The desired role type
* @return {String[]} List of all roles matching the type
*/
function getAriaRolesByType(type) {
return Object.keys(standards.ariaRoles).filter(roleName => {
return standards.ariaRoles[roleName].type === type;
});
}

export default getAriaRolesByType;
49 changes: 49 additions & 0 deletions lib/commons/standards/get-element-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import standards from '../../standards';
import matchesFn from '../../commons/matches';

/**
* Return the spec for an HTML element from the standards object. Since the spec is determined by the node and what attributes it has, a node is required.
* @param {VirtualNode} vNode The VirtualNode to get the spec for.
* @return {Object} The standard spec object
*/
function getElementSpec(vNode) {
const standard = standards.htmlElms[vNode.props.nodeName];

if (!standard.variant) {
return standard;
}

// start with the information at the top level
const { variant, ...spec } = standard;

// loop through all variants (excluding default) finding anything
// that matches
for (const variantName in variant) {
if (!variant.hasOwnProperty(variantName) || variantName === 'default') {
continue;
}

const { matches, ...props } = variant[variantName];
if (matchesFn(vNode, matches)) {
for (const propName in props) {
if (props.hasOwnProperty(propName)) {
spec[propName] = props[propName];
}
}
}
}

// apply defaults if properties were not found
for (const propName in variant.default) {
if (
variant.default.hasOwnProperty(propName) &&
typeof spec[propName] === 'undefined'
) {
spec[propName] = variant.default[propName];
}
}

return spec;
}

export default getElementSpec;
13 changes: 13 additions & 0 deletions lib/commons/standards/get-global-aria-attrs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import standards from '../../standards';

/**
* Return a list of global aria attributes.
* @return {String[]} List of all global aria attributes
*/
function getGlobalAriaAttrs() {
return Object.keys(standards.ariaAttrs).filter(attrName => {
return standards.ariaAttrs[attrName].global;
});
}

export default getGlobalAriaAttrs;
8 changes: 8 additions & 0 deletions lib/commons/standards/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Namespace for standards-related utilities.
* @namespace commons.standards
* @memberof axe
*/
export { default as getAriaRolesByType } from './get-aria-roles-by-type';
export { default as getGlobalAriaAttrs } from './get-global-aria-attrs';
export { default as getElementSpec } from './get-element-spec';
29 changes: 22 additions & 7 deletions lib/standards/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import ariaAttrs from './aria-attrs';
import { clone, deepMerge } from '../core/utils';
import ariaRoles from './aria-roles';
import dpubRoles from './dpub-roles';
import htmlElms from './html-elms';
import { deepMerge } from '../core/utils';

const origAriaAttrs = clone(ariaAttrs);
const originals = {
ariaAttrs,
ariaRoles: {
...ariaRoles,
...dpubRoles
},
htmlElms
};
const standards = {
ariaAttrs
...originals
};

export function configureStandards(config) {
if (config.ariaAttrs) {
standards.ariaAttrs = deepMerge(standards.ariaAttrs, config.ariaAttrs);
}
Object.keys(standards).forEach(propName => {
if (config[propName]) {
standards[propName] = deepMerge(standards[propName], config[propName]);
}
});
}

export function resetStandards() {
standards.ariaAttrs = origAriaAttrs;
Object.keys(standards).forEach(propName => {
standards[propName] = originals[propName];
});
}

export default standards;
1 change: 1 addition & 0 deletions test/commons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe('axe.commons', function() {
assert.exists(axe.commons.dom);
assert.exists(axe.commons.forms);
assert.exists(axe.commons.matches);
assert.exists(axe.commons.standards);
assert.exists(axe.commons.table);
assert.exists(axe.commons.text);
assert.exists(axe.commons.utils);
Expand Down
72 changes: 72 additions & 0 deletions test/commons/standards/get-aria-roles-by-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
describe('standards.getAriaRolesByType', function() {
var getAriaRolesByType = axe.commons.standards.getAriaRolesByType;

before(function() {
axe._load({});
});

after(function() {
axe.reset();
});

it('should return a list of role names by type', function() {
// Source: https://www.w3.org/TR/wai-aria-1.1/#document_structure_roles
var structureRoles = getAriaRolesByType('structure');
assert.deepEqual(structureRoles, [
'article',
'cell',
'columnheader',
'definition',
'directory',
'document',
'feed',
'figure',
'group',
'heading',
'img',
'list',
'listitem',
'math',
'note',
'presentation',
'row',
'rowgroup',
'rowheader',
'separator',
'table',
'term',
'toolbar',
'tooltip'
]);
});

it('should return configured roles', function() {
axe.configure({
standards: {
ariaRoles: {
myRole: {
type: 'structure'
}
}
}
});

var structureRoles = getAriaRolesByType('structure');
assert.include(structureRoles, 'myRole');
});

it('should not return role that is configured to not be of the type', function() {
axe.configure({
standards: {
ariaRoles: {
article: {
type: 'notstructure'
}
}
}
});

var structureRoles = getAriaRolesByType('structure');
assert.notInclude(structureRoles, 'article');
});
});
99 changes: 99 additions & 0 deletions test/commons/standards/get-element-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
describe('standards.getElementSpec', function() {
var getElementSpec = axe.commons.standards.getElementSpec;
var queryFixture = axe.testUtils.queryFixture;
var fixture = document.querySelector('#fixture');

before(function() {
axe._load({});
});

afterEach(function() {
fixture.innerHTML = '';
});

after(function() {
axe.reset();
});

it('should return a spec for an element without variants', function() {
axe.configure({
standards: {
htmlElms: {
abbr: {
contentTypes: ['phrasing', 'flow'],
allowedRoles: true
}
}
}
});

var vNode = queryFixture('<abbr id="target"></abbr>');
assert.deepEqual(getElementSpec(vNode), {
contentTypes: ['phrasing', 'flow'],
allowedRoles: true
});
});

describe('variants', function() {
before(function() {
axe.configure({
standards: {
htmlElms: {
abbr: {
variant: {
controls: {
matches: '[controls]',
customProp: 'controls'
},
label: {
matches: '[aria-label]',
anotherProp: 'label'
},
default: {
customProp: 'default',
anotherProp: 'default'
}
},
allowedRoles: false
}
}
}
});
});

it('should return top level properties', function() {
var vNode = queryFixture('<abbr id="target" controls></abbr>');
var spec = getElementSpec(vNode);
assert.equal(spec.allowedRoles, false);
});

it('should return properties from matching variant', function() {
var vNode = queryFixture('<abbr id="target" controls></abbr>');
var spec = getElementSpec(vNode);
assert.equal(spec.customProp, 'controls');
});

it('should return all properties from matching variants', function() {
var vNode = queryFixture(
'<abbr id="target" controls aria-label="foo"></abbr>'
);
var spec = getElementSpec(vNode);
assert.equal(spec.customProp, 'controls');
assert.equal(spec.anotherProp, 'label');
});

it('should return default props in no variants match', function() {
var vNode = queryFixture('<abbr id="target"></abbr>');
var spec = getElementSpec(vNode);
assert.equal(spec.customProp, 'default');
assert.equal(spec.anotherProp, 'default');
});

it('should return default props that were not part of other matches', function() {
var vNode = queryFixture('<abbr id="target" controls></abbr>');
var spec = getElementSpec(vNode);
assert.equal(spec.customProp, 'controls');
assert.equal(spec.anotherProp, 'default');
});
});
});
69 changes: 69 additions & 0 deletions test/commons/standards/get-global-aria-attrs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
describe('standards.getGlobalAriaAttrs', function() {
var getGlobalAriaAttrs = axe.commons.standards.getGlobalAriaAttrs;

before(function() {
axe._load({});
});

after(function() {
axe.reset();
});

it('should return global attrs', function() {
// Source: https://www.w3.org/TR/wai-aria-1.1/#global_states
var globalAttrs = getGlobalAriaAttrs();
assert.deepEqual(globalAttrs, [
'aria-atomic',
'aria-busy',
'aria-controls',
'aria-current',
'aria-describedby',
'aria-details',
'aria-disabled',
'aria-dropeffect',
'aria-errormessage',
'aria-flowto',
'aria-grabbed',
'aria-haspopup',
'aria-hidden',
'aria-invalid',
'aria-keyshortcuts',
'aria-label',
'aria-labelledby',
'aria-live',
'aria-owns',
'aria-relevant',
'aria-roledescription'
]);
});

it('should return configured global attrs', function() {
axe.configure({
standards: {
ariaAttrs: {
myAttr: {
global: true
}
}
}
});

var globalAttrs = getGlobalAriaAttrs();
assert.include(globalAttrs, 'myAttr');
});

it('should not return global attr that is configured to not be global', function() {
axe.configure({
standards: {
ariaAttrs: {
'aria-atomic': {
global: false
}
}
}
});

var globalAttrs = getGlobalAriaAttrs();
assert.notInclude(globalAttrs, 'aria-atomic');
});
});

0 comments on commit 6dce974

Please sign in to comment.