Skip to content

Commit

Permalink
Merged in github (pull request #124)
Browse files Browse the repository at this point in the history
Github
  • Loading branch information
Marcy Sutton committed Apr 24, 2017
2 parents cec54c1 + ad5f80e commit d4bdc58
Show file tree
Hide file tree
Showing 18 changed files with 572 additions and 52 deletions.
3 changes: 2 additions & 1 deletion doc/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ Add your project/integration to this file and submit a pull request.
12. [Vorlon.js Remote Debugger](https://github.com/MicrosoftDX/Vorlonjs)
13. [Selenium IDE aXe Extension](https://github.com/bkardell/selenium-ide-axe)
14. [gulp-axe-webdriver](https://github.com/felixzapata/gulp-axe-webdriver)
15. [AccessLint](https://github.com/accesslint/accesslint.js)
15. [AccessLint](https://accesslint.com/)
16. [Lighthouse](https://github.com/GoogleChrome/lighthouse)
17. [Axegrinder](https://github.com/claflamme/axegrinder)
18. [Ghost-Axe](https://www.npmjs.com/package/ghost-axe)
19. [Protractor accessibility plugin](https://github.com/angular/protractor-accessibility-plugin)
20. [Storybook accessibility addon](https://github.com/jbovenschen/storybook-addon-a11y)
21. [Intern](https://github.com/theintern/intern-a11y)
22. [Protractor-axe-report Plugin](https://github.com/E1Edatatracker/protractor-axe-report-plugin)
1 change: 1 addition & 0 deletions lib/commons/aria/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ aria.allowedAttr = function (role) {
var roles = lookupTables.role[role],
attr = (roles && roles.attributes && roles.attributes.allowed) || [],
requiredAttr = (roles && roles.attributes && roles.attributes.required) || [];

return attr.concat(lookupTables.globalAttributes).concat(requiredAttr);
};

Expand Down
78 changes: 63 additions & 15 deletions lib/commons/aria/roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,25 +101,73 @@ aria.implicitNodes = function (role) {
* @param {HTMLElement} node The node to test
* @return {Mixed} Either the role or `null` if there is none
*/

aria.implicitRole = function (node) {
'use strict';

var role, r, candidate,
roles = lookupTables.role;

for (role in roles) {
if (roles.hasOwnProperty(role)) {
r = roles[role];
if (r.implicit) {
for (var index = 0, length = r.implicit.length; index < length; index++) {
candidate = r.implicit[index];
if (axe.utils.matchesSelector(node, candidate)) {
return role;
}
}
}
/*
* Filter function to reduce a list of roles to a valid list of roles for a nodetype
*/
var isValidImplicitRole = function (set, role) {
var validForNodeType = function (implicitNodeTypeSelector) {
return axe.utils.matchesSelector(node, implicitNodeTypeSelector);
};

if (role.implicit && role.implicit.some(validForNodeType)) {
set.push(role.name);
}

return set;
};

/*
* Score a set of roles and aria-attributes by its optimal score
* E.g. [{score: 2, name: button}, {score: 1, name: main}]
*/
var sortRolesByOptimalAriaContext = function (roles, ariaAttributes) {
var getScore = function (role) {
var allowedAriaAttributes = aria.allowedAttr(role);
return allowedAriaAttributes.reduce( function (score, attribute) {
return score + (ariaAttributes.indexOf(attribute) > -1 ? 1 : 0);
}, 0);
};

var scored = roles.map( function (role) {
return { score: getScore(role), name: role };
});

var sorted = scored.sort( function (scoredRoleA, scoredRoleB) {
return scoredRoleB.score - scoredRoleA.score;
});

return sorted.map( function (sortedRole) {
return sortedRole.name;
});
};

/*
* Create a list of { name / implicit } role mappings to filter on
*/
var roles = Object.keys(lookupTables.role).map( function (role) {
var lookup = lookupTables.role[role];
return { name: role, implicit: lookup && lookup.implicit };
});

/* Build a list of valid implicit roles for this node */
var availableImplicitRoles = roles.reduce(isValidImplicitRole, []);

if (!availableImplicitRoles.length) { return null; }

var nodeAttributes = node.attributes;
var ariaAttributes = [];

/* Get all aria-attributes defined for this node */
/* Should be a helper function somewhere */
for (var i = 0, j = nodeAttributes.length; i < j; i++) {
var attr = nodeAttributes[i];
if (attr.name.match(/^aria-/)) { ariaAttributes.push( attr.name ); }
}

return null;
/* Sort roles by highest score, return the first */
return sortRolesByOptimalAriaContext(availableImplicitRoles, ariaAttributes).shift();
};
1 change: 0 additions & 1 deletion lib/core/public/a11y-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,3 @@ axe.a11yCheck = function (context, options, callback) {
}
}, axe.log);
};

9 changes: 8 additions & 1 deletion lib/core/public/run-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,15 @@ function runRules(context, options, resolve, reject) {
if (options.performanceTimer) {
axe.utils.performanceTimer.auditStart();
}
if (!('iframes' in options)) {
options.iframes = true;
}

if (!('selectors' in options)) {
options.selectors = true;
}

if (context.frames.length) {
if (context.frames.length && options.iframes) {
q.defer(function (res, rej) {
axe.utils.collectResultsFromFrames(context, options, 'rules', null, res, rej);
});
Expand Down
24 changes: 16 additions & 8 deletions lib/core/reporters/helpers/process-aggregate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*global helpers */
function normalizeRelatedNodes(node, xpath) {
function normalizeRelatedNodes(node, options) {
'use strict';
['any','all','none'].forEach((type) => {
if (!Array.isArray(node[type])) {
Expand All @@ -9,16 +9,20 @@ function normalizeRelatedNodes(node, xpath) {
.forEach((checkRes) => {
checkRes.relatedNodes = checkRes.relatedNodes.map((relatedNode) => {
var res = {
html: relatedNode.source,
target: relatedNode.selector
html: relatedNode.source
};
if (xpath) {
if (options.elementRef && !relatedNode.fromFrame) {
res.element = relatedNode.element;
}
if (options.selectors || relatedNode.fromFrame) {
res.target = relatedNode.selector;
}
if (options.xpath) {
res.xpath = relatedNode.xpath;
}
return res;
});
});

});
}

Expand All @@ -37,16 +41,20 @@ helpers.processAggregate = function (results, options) {
ruleResult.nodes = ruleResult.nodes.map(function (subResult) {
if (typeof subResult.node === 'object') {
subResult.html = subResult.node.source;
subResult.target = subResult.node.selector;

if (options.elementRef && !subResult.node.fromFrame) {
subResult.element = subResult.node.element;
}
if (options.selectors || subResult.node.fromFrame) {
subResult.target = subResult.node.selector;
}
if (options.xpath) {
subResult.xpath = subResult.node.xpath;
}
}
delete subResult.result;
delete subResult.node;

normalizeRelatedNodes(subResult, options.xpath);
normalizeRelatedNodes(subResult, options);

return subResult;
});
Expand Down
61 changes: 41 additions & 20 deletions lib/core/utils/dq-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,40 +31,61 @@ function getSource (element) {
*/
function DqElement(element, spec) {
'use strict';
spec = spec || {};

this._fromFrame = !!spec;

/**
* A unique CSS selector for the element
* @type {String}
*/
this.selector = spec.selector || [axe.utils.getSelector(element)];

/**
* Xpath to the element
*/
this.xpath = spec.xpath || [axe.utils.getXpath(element)];
this.spec = spec || {};

/**
* The generated HTML source code of the element
* @type {String}
*/
this.source = spec.source !== undefined ? spec.source : getSource(element);
this.source = this.spec.source !== undefined ? this.spec.source : getSource(element);

/**
* The element which this object is based off or the containing frame, used for sorting.
* Excluded in toJSON method.
* @type {HTMLElement}
*/
this.element = element;
this._element = element;
}

DqElement.prototype.toJSON = function () {
'use strict';
return {
selector: this.selector,
source: this.source,
xpath: this.xpath
};
DqElement.prototype = {
/**
* A unique CSS selector for the element
* @return {String}
*/
get selector() {
return this.spec.selector || [axe.utils.getSelector(this.element)];
},

/**
* Xpath to the element
* @return {String}
*/
get xpath() {
return this.spec.xpath || [axe.utils.getXpath(this.element)];
},

/**
* Direct reference to the `HTMLElement` wrapped by this `DQElement`.
*/
get element() {
return this._element;
},

get fromFrame() {
return this._fromFrame;
},

toJSON: function() {
'use strict';
return {
selector: this.selector,
source: this.source,
xpath: this.xpath
};
}
};

DqElement.fromFrame = function (node, frame) {
Expand Down
44 changes: 42 additions & 2 deletions test/commons/aria/roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,20 +189,60 @@ describe('aria.implicitRole', function () {
axe.commons.aria._lut.role = orig;
});

it('should find the first matching role', function () {
it('should find the first optimal matching role', function () {
var node = document.createElement('div');
node.setAttribute('aria-required', 'true');

node.id = 'cats';
fixture.appendChild(node);

axe.commons.aria._lut.role = {
'cats': {
implicit: ['div[id="cats"]']
},
'dogs': {
implicit: ['div[id="cats"]']
},
'dinosaurs': {
attributes: {
allowed: ['aria-required']
},
implicit: ['div[id="cats"]']
}
};
assert.equal(axe.commons.aria.implicitRole(node), 'cats');

assert.equal(axe.commons.aria.implicitRole(node), 'dinosaurs');
});

it('should find the first optimal matching role when multiple optimal matches are available', function () {
var node = document.createElement('div');
node.setAttribute('aria-required', 'true');

node.id = 'cats';
fixture.appendChild(node);

axe.commons.aria._lut.role = {
'cats': {
implicit: ['div[id="cats"]']
},
'dogs': {
attributes: {
allowed: ['aria-required']
},
implicit: ['div[id="cats"]']
},
'dinosaurs': {
attributes: {
allowed: ['aria-required']
},
implicit: ['div[id="cats"]']
}
};

assert.equal(axe.commons.aria.implicitRole(node), 'dogs');
});


it('should return null if there is no matching implicit role', function () {
var node = document.createElement('div');
node.id = 'cats';
Expand Down
28 changes: 28 additions & 0 deletions test/core/public/run-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,4 +593,32 @@ describe('runRules', function () {
runRules(document, {},resolve, reject);

});

it('should ignore iframes if `iframes` === false', function (done) {
axe._load({ rules: [{
id: 'html',
selector: 'html',
any: ['html']
}], checks: [{
id: 'html',
evaluate: function () {
return true;
}
}], messages: {}});

var frame = document.createElement('iframe');
frame.src = '../mock/frames/frame-frame.html';

frame.addEventListener('load', function () {
setTimeout(function () {
runRules(document, { iframes: false }, function (r) {
assert.lengthOf(r[0].passes, 1);
assert.equal(r[0].passes[0].node.element.ownerDocument, document,
'Result should not be in an iframe');
done();
}, isNotCalled);
}, 500);
});
fixture.appendChild(frame);
});
});
Loading

0 comments on commit d4bdc58

Please sign in to comment.